Преглед изворни кода

Merged with master after pull.

Daniel Johansson пре 9 година
родитељ
комит
e530fa644b
100 измењених фајлова са 3190 додато и 2166 уклоњено
  1. 4 1
      .travis.yml
  2. 29 0
      bintray.gradle
  3. 8 2
      build.gradle
  4. 43 18
      common.gradle
  5. 4 1
      gradle.properties
  6. 6 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  7. 83 19
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  8. 100 63
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  9. 4 2
      jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java
  10. 7 11
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  11. 31 0
      jme3-core/src/main/java/com/jme3/app/Application.java
  12. 1 1
      jme3-core/src/main/java/com/jme3/asset/AssetKey.java
  13. 34 4
      jme3-core/src/main/java/com/jme3/audio/AudioNode.java
  14. 7 4
      jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java
  15. 11 0
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  16. 12 3
      jme3-core/src/main/java/com/jme3/input/KeyInput.java
  17. 24 22
      jme3-core/src/main/java/com/jme3/input/KeyNames.java
  18. 7 1
      jme3-core/src/main/java/com/jme3/light/DirectionalLight.java
  19. 4 2
      jme3-core/src/main/java/com/jme3/light/Light.java
  20. 8 1
      jme3-core/src/main/java/com/jme3/light/PointLight.java
  21. 9 1
      jme3-core/src/main/java/com/jme3/light/SpotLight.java
  22. 32 3
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  23. 2 0
      jme3-core/src/main/java/com/jme3/material/Material.java
  24. 34 95
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  25. 1 1
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  26. 1 1
      jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java
  27. 5 0
      jme3-core/src/main/java/com/jme3/scene/Node.java
  28. 1 1
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  29. 2 1
      jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java
  30. 1 1
      jme3-core/src/main/java/com/jme3/system/AppSettings.java
  31. 34 27
      jme3-core/src/main/java/com/jme3/util/BufferUtils.java
  32. 97 0
      jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java
  33. 100 0
      jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java
  34. 1717 0
      jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java
  35. 4 3
      jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert
  36. 19 0
      jme3-core/src/main/resources/joystick-mapping.properties
  37. 1 1
      jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java
  38. 1 1
      jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java
  39. 1 1
      jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
  40. 9 5
      jme3-examples/build.gradle
  41. 72 0
      jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java
  42. 1 1
      jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java
  43. 19 18
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java
  44. 53 52
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  45. 7 7
      jme3-lwjgl3/build.gradle
  46. BIN
      jme3-lwjgl3/lib/lwjgl-3.0.0b-35-natives.jar
  47. BIN
      jme3-lwjgl3/lib/lwjgl-3.0.0b-35.jar
  48. 22 18
      jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java
  49. 2 1
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java
  50. 171 0
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java
  51. 91 47
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java
  52. 27 30
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  53. 99 56
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java
  54. 31 22
      jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
  55. 1 1
      jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
  56. 7 3
      jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java
  57. 1 1
      jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java
  58. 12 0
      jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java
  59. 4 9
      jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java
  60. 5 10
      jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java
  61. 5 3
      jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java
  62. 18 2
      jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java
  63. 3 2
      jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java
  64. 34 5
      jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java
  65. 5 0
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java
  66. 1 1
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java
  67. 1 1
      jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java
  68. 0 1
      sdk/BasicGameTemplate/MANIFEST.MF
  69. 0 0
      sdk/BasicGameTemplate/assets/Interface/.keep
  70. 0 0
      sdk/BasicGameTemplate/assets/MatDefs/.keep
  71. 0 0
      sdk/BasicGameTemplate/assets/Materials/.keep
  72. 0 0
      sdk/BasicGameTemplate/assets/Models/.keep
  73. 0 0
      sdk/BasicGameTemplate/assets/Scenes/.keep
  74. 0 0
      sdk/BasicGameTemplate/assets/Shaders/.keep
  75. 0 0
      sdk/BasicGameTemplate/assets/Sounds/.keep
  76. 0 0
      sdk/BasicGameTemplate/assets/Textures/.keep
  77. 0 76
      sdk/BasicGameTemplate/build.xml
  78. 0 22
      sdk/BasicGameTemplate/master-application.jnlp
  79. 0 8
      sdk/BasicGameTemplate/nbproject/genfiles.properties
  80. 0 92
      sdk/BasicGameTemplate/nbproject/project.properties
  81. 0 18
      sdk/BasicGameTemplate/nbproject/project.xml
  82. 0 43
      sdk/BasicGameTemplate/src/mygame/Main.java
  83. 0 73
      sdk/JME3TestsTemplate/build.xml
  84. 0 880
      sdk/JME3TestsTemplate/nbproject/build-impl.xml
  85. 0 8
      sdk/JME3TestsTemplate/nbproject/genfiles.properties
  86. 0 83
      sdk/JME3TestsTemplate/nbproject/project.properties
  87. 0 13
      sdk/JME3TestsTemplate/nbproject/project.xml
  88. 0 1
      sdk/JME3TestsTemplateAndroid/MANIFEST.MF
  89. 0 76
      sdk/JME3TestsTemplateAndroid/build.xml
  90. 0 22
      sdk/JME3TestsTemplateAndroid/master-application.jnlp
  91. 0 15
      sdk/JME3TestsTemplateAndroid/mobile/AndroidManifest.xml
  92. 0 17
      sdk/JME3TestsTemplateAndroid/mobile/ant.properties
  93. 0 92
      sdk/JME3TestsTemplateAndroid/mobile/build.xml
  94. 0 20
      sdk/JME3TestsTemplateAndroid/mobile/proguard-project.txt
  95. 0 14
      sdk/JME3TestsTemplateAndroid/mobile/project.properties
  96. BIN
      sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256.png
  97. BIN
      sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256_9.9.png
  98. BIN
      sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512.png
  99. BIN
      sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512_9.9.png
  100. BIN
      sdk/JME3TestsTemplateAndroid/mobile/res/drawable/nonselected.png

+ 4 - 1
.travis.yml

@@ -25,7 +25,6 @@ install:
 script:
   - ./gradlew check
   - ./gradlew createZipDistribution
-  - "[ $TRAVIS_BRANCH == 'master' ] && [ $TRAVIS_PULL_REQUEST == 'false' ] && ./gradlew uploadArchives || :"
 
 before_deploy:
   - export RELEASE_DIST=$(ls build/distributions/*.zip)
@@ -54,3 +53,7 @@ before_install:
   # wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin -O ndk.bin
   # 7z x ndk.bin -y > /dev/null
   # export ANDROID_NDK=`pwd`/android-ndk-r10c
+
+after_success:
+  - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :'
+  - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives bintrayUpload || :'

+ 29 - 0
bintray.gradle

@@ -0,0 +1,29 @@
+//
+// This file is to be applied to some subproject.
+//
+
+apply plugin: 'com.jfrog.bintray'
+
+bintray {
+    user = bintray_user
+    key = bintray_api_key
+    configurations = ['archives']
+    dryRun = false 
+    pkg {
+        repo = 'org.jmonkeyengine'
+        userOrg = 'jmonkeyengine'
+        name = project.name
+        desc = POM_DESCRIPTION
+        websiteUrl = POM_URL
+        licenses = ['BSD New']
+        vcsUrl = POM_SCM_URL
+        labels = ['jmonkeyengine']
+    }
+}
+
+bintrayUpload.dependsOn(writeFullPom)
+
+bintrayUpload.onlyIf {
+    (bintray_api_key.length() > 0) &&
+    !(version ==~ /.*SNAPSHOT/)
+}

+ 8 - 2
build.gradle

@@ -2,10 +2,11 @@ import org.gradle.api.artifacts.*
 
 buildscript {
     repositories {
-        mavenCentral()
+        jcenter()
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:1.1.0'
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.5'
     }
 }
 
@@ -17,6 +18,9 @@ apply from: file('upload.gradle')
 subprojects {
     if(!project.name.equals('jme3-android-examples')) {
         apply from: rootProject.file('common.gradle')
+        if (!['jme3-testdata', 'sdk'].contains(project.name)) {
+            apply from: rootProject.file('bintray.gradle')
+        }
     } else {
         apply from: rootProject.file('common-android-app.gradle')
     }
@@ -86,6 +90,8 @@ task dist(dependsOn: [':jme3-examples:dist', 'mergedJavadoc']){
 task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the projects.') {
     title = 'jMonkeyEngine3'
     destinationDir = mkdir("dist/javadoc")
+    
+    options.encoding = 'UTF-8'
 
     // Allows Javadoc to be generated on Java 8 despite doclint errors.
     if (JavaVersion.current().isJava8Compatible()) {
@@ -174,4 +180,4 @@ task configureAndroidNDK {
 //    tasks.withType(Test) {
 //        enableAssertions = true // true by default
 //    }
-//}
+//}

+ 43 - 18
common.gradle

@@ -5,7 +5,7 @@
 apply plugin: 'java'
 apply plugin: 'maven'
 
-group = 'com.jme3'
+group = 'org.jmonkeyengine'
 version = jmePomVersion
 
 sourceCompatibility = '1.6'
@@ -61,12 +61,53 @@ task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from
     from javadoc.destinationDir
 }
 
+def pomConfig = {
+    name POM_NAME
+    description POM_DESCRIPTION
+    url POM_URL
+    inceptionYear '2016'
+    scm {
+        url POM_SCM_URL
+        connection POM_SCM_CONNECTION
+        developerConnection POM_SCM_DEVELOPER_CONNECTION
+    }
+    licenses {
+        license {
+            name POM_LICENSE_NAME
+            url POM_LICENSE_URL
+            distribution POM_LICENSE_DISTRIBUTION
+        }
+    }
+    // from http://hub.jmonkeyengine.org/introduction/team/
+    developers {
+        developer {
+            name 'jMonkeyEngine Team'
+            id 'jMonkeyEngine'
+        }
+    }
+}
+
+// workaround to be able to use same custom pom with 'maven' and 'bintray' plugin
+task writeFullPom {
+    ext.pomFile = "$mavenPomDir/${project.name}-${project.version}.pom"
+    outputs.file pomFile
+    doLast {
+        pom {
+            project pomConfig
+        }.writeTo(pomFile)
+    }
+}
+assemble.dependsOn(writeFullPom)
+install.dependsOn(writeFullPom)
+uploadArchives.dependsOn(writeFullPom)
+
 artifacts {
     archives jar
     archives sourcesJar
     if(buildJavaDoc == "true"){
         archives javadocJar
     }
+    archives writeFullPom.outputs.files[0]
 }
 
 uploadArchives {
@@ -80,23 +121,7 @@ uploadArchives {
              authentication(userName: "www-updater", privateKey: "private/www-updater.key")
         }
         
-        pom.project {
-            name POM_NAME
-            description POM_DESCRIPTION
-            url POM_URL
-            scm {
-                url POM_SCM_URL
-                connection POM_SCM_CONNECTION
-                developerConnection POM_SCM_DEVELOPER_CONNECTION
-            }
-            licenses {
-                license {
-                    name POM_LICENSE_NAME
-                    url POM_LICENSE_URL
-                    distribution POM_LICENSE_DISTRIBUTION
-                }
-            }
-        }
+        pom.project pomConfig
     }
 }
 

+ 4 - 1
gradle.properties

@@ -11,7 +11,6 @@ jmeVersionTagID = 0
 buildJavaDoc = true
 
 # specify if SDK and Native libraries get built
-buildSdkProject = true
 buildNativeProjects = false
 buildAndroidExamples = false
 
@@ -37,3 +36,7 @@ POM_SCM_DEVELOPER_CONNECTION=scm:git:[email protected]:jMonkeyEngine/jmonkeyengine.
 POM_LICENSE_NAME=New BSD (3-clause) License
 POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause
 POM_LICENSE_DISTRIBUTION=repo
+
+# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline
+bintray_user=
+bintray_api_key=

+ 6 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -59,6 +59,7 @@ import com.jme3.scene.plugins.blender.file.FileBlockHeader;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.texture.Texture;
 
 /**
@@ -389,11 +390,11 @@ public class BlenderContext {
                     }
                 }
             } else if("ME".equals(namePrefix)) {
-                List<Node> features = (List<Node>) linkedFeatures.get("meshes");
-                if(features != null) {
-                    for(Node feature : features) {
-                        if(featureName.equals(feature.getName())) {
-                            return feature;
+                List<TemporalMesh> temporalMeshes = (List<TemporalMesh>) linkedFeatures.get("meshes");
+                if(temporalMeshes != null) {
+                    for(TemporalMesh temporalMesh : temporalMeshes) {
+                        if(featureName.equals(temporalMesh.getName())) {
+                            return temporalMesh;
                         }
                     }
                 }

+ 83 - 19
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java

@@ -10,6 +10,7 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.math.Vector3d;
 import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
 
 /**
@@ -24,6 +25,8 @@ public class Edge {
 
     /** The vertices indexes. */
     private int                 index1, index2;
+    /** The vertices that can be set if we need and abstract edge outside the mesh (for computations). */
+    private Vector3f 			v1, v2;
     /** The weight of the edge. */
     private float               crease;
     /** A variable that indicates if this edge belongs to any face or not. */
@@ -31,6 +34,13 @@ public class Edge {
     /** The mesh that owns the edge. */
     private TemporalMesh        temporalMesh;
 
+    public Edge(Vector3f v1, Vector3f v2) {
+		this.v1 = v1 == null ? new Vector3f() : v1;
+		this.v2 = v2 == null ? new Vector3f() : v2;
+		index1 = 0;
+		index2 = 1;
+	}
+    
     /**
      * This constructor only stores the indexes of the vertices. The position vertices should be stored
      * outside this class.
@@ -74,14 +84,14 @@ public class Edge {
      * @return the first vertex of the edge
      */
     public Vector3f getFirstVertex() {
-        return temporalMesh.getVertices().get(index1);
+        return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1);
     }
 
     /**
      * @return the second vertex of the edge
      */
     public Vector3f getSecondVertex() {
-        return temporalMesh.getVertices().get(index2);
+        return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2);
     }
 
     /**
@@ -188,28 +198,82 @@ public class Edge {
      * @return <b>true</b> if the edges cross and false otherwise
      */
     public boolean cross(Edge edge) {
-        Vector3f P1 = this.getFirstVertex();
-        Vector3f P2 = edge.getFirstVertex();
-        Vector3f u = this.getSecondVertex().subtract(P1);
-        Vector3f v = edge.getSecondVertex().subtract(P2);
-        float t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
-        float t1 = (P2.x - P1.x + v.x * t2) / u.x;
-        Vector3f p1 = P1.add(u.mult(t1));
-        Vector3f p2 = P2.add(v.mult(t2));
+        return this.getCrossPoint(edge) != null;
+    }
+    
+    /**
+	 * The method computes the crossing pint of this edge and another edge. If
+	 * there is no crossing then null is returned.
+	 * 
+	 * @param edge
+	 *            the edge to compute corss point with
+	 * @return cross point on null if none exist
+	 */
+	public Vector3f getCrossPoint(Edge edge) {
+		return this.getCrossPoint(edge, false, false);
+	}
+    
+	/**
+	 * The method computes the crossing pint of this edge and another edge. If
+	 * there is no crossing then null is returned. This method also allows to
+	 * get the crossing point of the straight lines that contain these edges if
+	 * you set the 'extend' parameter to true.
+	 * 
+	 * @param edge
+	 *            the edge to compute corss point with
+	 * @param extendThisEdge
+	 *            set to <b>true</b> to find a crossing point along the whole
+	 *            straight that contains the current edge
+	 * @param extendSecondEdge
+	 *            set to <b>true</b> to find a crossing point along the whole
+	 *            straight that contains the given edge
+	 * @return cross point on null if none exist
+	 */
+	public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
+		Vector3d P1 = new Vector3d(this.getFirstVertex());
+		Vector3d P2 = new Vector3d(edge.getFirstVertex());
+		Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
+		Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
+		
+		double t1 = 0, t2 = 0;
+		if(u.x == 0 && v.x == 0) {
+			t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
+	        t1 = (P2.z - P1.z + v.z * t2) / u.z;
+		} else if(u.y == 0 && v.y == 0) {
+			t2 = (u.x * (P2.z - P1.z) - u.z * (P2.x - P1.x)) / (u.z * v.x - u.x * v.z);
+	        t1 = (P2.x - P1.x + v.x * t2) / u.x;
+		} else if(u.z == 0 && v.z == 0) {
+			t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
+	        t1 = (P2.x - P1.x + v.x * t2) / u.x;
+		} else {
+			t2 = (P1.y * u.x - P1.x * u.y + P2.x * u.y - P2.y * u.x) / (v.y * u.x - u.y * v.x);
+			t1 = (P2.x - P1.x + v.x * t2) / u.x;
+			if(Math.abs(P1.z - P2.z + u.z * t1 - v.z * t2) > FastMath.FLT_EPSILON) {
+				return null;
+			}
+		}
+		Vector3d p1 = P1.add(u.mult(t1));
+        Vector3d p2 = P2.add(v.mult(t2));
 
-        if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
-            // the lines cross, check if p1 and p2 are within the edges
-            Vector3f p = p1.subtract(P1);
-            float cos = p.dot(u) / (p.length() * u.length());
-            if (cos > 0 && p.length() <= u.length()) {
+		if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
+			if(extendThisEdge && extendSecondEdge) {
+				return p1.toVector3f();
+			}
+			// the lines cross, check if p1 and p2 are within the edges
+            Vector3d p = p1.subtract(P1);
+            double cos = p.dot(u) / p.length();
+            if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) {
                 // p1 is inside the first edge, lets check the other edge now
                 p = p2.subtract(P2);
-                cos = p.dot(v) / (p.length() * v.length());
-                return cos > 0 && p.length() <= u.length();
+                cos = p.dot(v) / p.length();
+                if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) {
+                	return p1.toVector3f();
+                }
             }
         }
-        return false;
-    }
+		
+		return null;
+	}
 
     @Override
     public String toString() {

+ 100 - 63
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java

@@ -276,30 +276,45 @@ public class Face implements Comparator<Integer> {
             List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
             while (facesToTriangulate.size() > 0) {
                 Face face = facesToTriangulate.remove(0);
-                int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
-                while (face.vertexCount() > 0) {
-                    indexes[0] = face.getIndex(0);
-                    indexes[1] = face.findClosestVertex(indexes[0], -1);
-                    indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
-
-                    LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
-                    if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) {
-                        throw new BlenderFileException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!");
-                    }
-                    if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) {
-                        throw new BlenderFileException("Infinite loop detected during triangulation of mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!");
-                    }
-                    previousIndex1 = indexes[0];
-                    previousIndex2 = indexes[1];
-                    previousIndex3 = indexes[2];
+                // two special cases will improve the computations speed
+                if(face.getIndexes().size() == 3) {
+                	triangulatedFaces.add(face.getIndexes().clone());
+                } else if(face.getIndexes().size() == 4) {
+                	// in case face has 4 verts we use the plain triangulation
+                	indexes[0] = face.getIndex(0);
+                    indexes[1] = face.getIndex(1);
+                    indexes[2] = face.getIndex(2);
+                	triangulatedFaces.add(new IndexesLoop(indexes));
+                	
+                    indexes[1] = face.getIndex(2);
+                    indexes[2] = face.getIndex(3);
+                	triangulatedFaces.add(new IndexesLoop(indexes));
+                } else {
+                	int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
+                    while (face.vertexCount() > 0) {
+                        indexes[0] = face.getIndex(0);
+                        indexes[1] = face.findClosestVertex(indexes[0], -1);
+                        indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
+
+                        LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
+                        if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) {
+                            throw new BlenderFileException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!");
+                        }
+                        if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) {
+                            throw new BlenderFileException("Infinite loop detected during triangulation of mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!");
+                        }
+                        previousIndex1 = indexes[0];
+                        previousIndex2 = indexes[1];
+                        previousIndex3 = indexes[2];
 
-                    Arrays.sort(indexes, this);
-                    facesToTriangulate.addAll(face.detachTriangle(indexes));
-                    triangulatedFaces.add(new IndexesLoop(indexes));
+                        Arrays.sort(indexes, this);
+                        facesToTriangulate.addAll(face.detachTriangle(indexes));
+                        triangulatedFaces.add(new IndexesLoop(indexes));
+                    }
                 }
             }
         } catch (BlenderFileException e) {
-            LOGGER.log(Level.WARNING, "Errors occured during face triangulation: {0}. The face will be triangulated with the most direct algorithm, " + "but the results might not be identical to blender.", e.getLocalizedMessage());
+            LOGGER.log(Level.WARNING, "Errors occured during face triangulation: {0}. The face will be triangulated with the most direct algorithm, but the results might not be identical to blender.", e.getLocalizedMessage());
             indexes[0] = this.getIndex(0);
             for (int i = 1; i < this.vertexCount() - 1; ++i) {
                 indexes[1] = this.getIndex(i);
@@ -308,7 +323,7 @@ public class Face implements Comparator<Integer> {
             }
         }
     }
-
+    
     /**
      * @return <b>true</b> if the face is smooth and <b>false</b> otherwise
      */
@@ -335,17 +350,23 @@ public class Face implements Comparator<Integer> {
         return "Face " + indexes;
     }
 
-    /**
-     * The method finds the closest vertex to the one specified by <b>index</b>.
-     * If the vertexToIgnore is positive than it will be ignored in the result.
-     * The closes vertex must be able to create an edge that is fully contained within the face and does not cross
-     * any other edges.
-     * @param index
-     *            the index of the vertex that needs to have found the nearest neighbour
-     * @param indexToIgnore
-     *            the index to ignore in the result (pass -1 if none is to be ignored)
-     * @return the index of the closest vertex to the given one
-     */
+	/**
+	 * The method finds the closest vertex to the one specified by <b>index</b>.
+	 * If the vertexToIgnore is positive than it will be ignored in the result.
+	 * The closest vertex must be able to create an edge that is fully contained
+	 * within the face and does not cross any other edges. Also if the
+	 * vertexToIgnore is not negative then the condition that the edge between
+	 * the found index and the one to ignore is inside the face must also be
+	 * met.
+	 * 
+	 * @param index
+	 *            the index of the vertex that needs to have found the nearest
+	 *            neighbour
+	 * @param indexToIgnore
+	 *            the index to ignore in the result (pass -1 if none is to be
+	 *            ignored)
+	 * @return the index of the closest vertex to the given one
+	 */
     private int findClosestVertex(int index, int indexToIgnore) {
         int result = -1;
         List<Vector3f> vertices = temporalMesh.getVertices();
@@ -355,7 +376,7 @@ public class Face implements Comparator<Integer> {
             if (i != index && i != indexToIgnore) {
                 Vector3f v2 = vertices.get(i);
                 float d = v2.distance(v1);
-                if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh))) {
+                if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh)) && (indexToIgnore < 0 || this.contains(new Edge(indexToIgnore, i, 0, true, temporalMesh)))) {
                     result = i;
                     distance = d;
                 }
@@ -376,11 +397,9 @@ public class Face implements Comparator<Integer> {
         int index2 = edge.getSecondIndex();
         // check if the line between the vertices is not a border edge of the face
         if (!indexes.areNeighbours(index1, index2)) {
-            List<Vector3f> vertices = temporalMesh.getVertices();
-
             for (int i = 0; i < indexes.size(); ++i) {
-                int i1 = this.getIndex(i);
-                int i2 = this.getIndex(i + 1);
+                int i1 = this.getIndex(i - 1);
+                int i2 = this.getIndex(i);
                 // check if the edges have no common verts (because if they do, they cannot cross)
                 if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
                     if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) {
@@ -389,35 +408,53 @@ public class Face implements Comparator<Integer> {
                 }
             }
 
-            // the edge does NOT cross any of other edges, so now we need to verify if it is inside the face or outside
-            // we check it by comparing the angle that is created by vertices: [index1 - 1, index1, index1 + 1]
-            // with the one creaded by vertices: [index1 - 1, index1, index2]
-            // if the latter is greater than it means that the edge is outside the face
-            // IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face)
-            int indexOfIndex1 = indexes.indexOf(index1);
-            int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1);
-            int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1);
-
-            Vector3f edge1 = vertices.get(indexMinus1).subtract(vertices.get(index1)).normalizeLocal();
-            Vector3f edge2 = vertices.get(indexPlus1).subtract(vertices.get(index1)).normalizeLocal();
-            Vector3f newEdge = vertices.get(index2).subtract(vertices.get(index1)).normalizeLocal();
-
-            // verify f the later computed angle is inside or outside the face
-            Vector3f direction1 = edge1.cross(edge2).normalizeLocal();
-            Vector3f direction2 = edge1.cross(newEdge).normalizeLocal();
-            Vector3f normal = temporalMesh.getNormals().get(index1);
-
-            boolean isAngle1Interior = normal.dot(direction1) < 0;
-            boolean isAngle2Interior = normal.dot(direction2) < 0;
-
-            float angle1 = isAngle1Interior ? edge1.angleBetween(edge2) : FastMath.TWO_PI - edge1.angleBetween(edge2);
-            float angle2 = isAngle2Interior ? edge1.angleBetween(newEdge) : FastMath.TWO_PI - edge1.angleBetween(newEdge);
-
-            return angle1 >= angle2;
+            // computing the edge's middle point
+            Vector3f edgeMiddlePoint = edge.computeCentroid();
+            // computing the edge that is perpendicular to the given edge and has a length of 1 (length actually does not matter)
+            Vector3f edgeVector = edge.getSecondVertex().subtract(edge.getFirstVertex());
+            Vector3f edgeNormal = temporalMesh.getNormals().get(index1).cross(edgeVector).normalizeLocal();
+            Edge e = new Edge(edgeMiddlePoint, edgeNormal.add(edgeMiddlePoint));
+            // compute the vectors from the middle point to the crossing between the extended edge 'e' and other edges of the face
+            List<Vector3f> crossingVectors = new ArrayList<Vector3f>();
+            for (int i = 0; i < indexes.size(); ++i) {
+                int i1 = this.getIndex(i);
+                int i2 = this.getIndex(i + 1);
+            	Vector3f crossPoint = e.getCrossPoint(new Edge(i1, i2, 0, false, temporalMesh), true, false);
+                if(crossPoint != null) {
+                	crossingVectors.add(crossPoint.subtractLocal(edgeMiddlePoint));
+                }
+            }
+            if(crossingVectors.size() == 0) {
+            	return false;// edges do not cross
+            }
+            
+            // use only distinct vertices (doubles may appear if the crossing point is a vertex)
+            List<Vector3f> distinctCrossingVectors = new ArrayList<Vector3f>();
+            for(Vector3f cv : crossingVectors) {
+        		double minDistance = Double.MAX_VALUE;
+        		for(Vector3f dcv : distinctCrossingVectors) {
+        			minDistance = Math.min(minDistance, dcv.distance(cv));
+        		}
+        		if(minDistance > FastMath.FLT_EPSILON) {
+        			distinctCrossingVectors.add(cv);
+        		}
+            }
+            
+            if(distinctCrossingVectors.size() == 0) {
+            	throw new IllegalStateException("There MUST be at least 2 crossing vertices!");
+            }
+            // checking if all crossing vectors point to the same direction (if yes then the edge is outside the face)
+            float direction = Math.signum(distinctCrossingVectors.get(0).dot(edgeNormal));// if at least one vector has different direction that this - it means that the edge is inside the face
+            for(int i=1;i<distinctCrossingVectors.size();++i) {
+            	if(direction != Math.signum(distinctCrossingVectors.get(i).dot(edgeNormal))) {
+            		return true;
+            	}
+            }
+            return false;
         }
         return true;
     }
-
+    
     @Override
     public int hashCode() {
         final int prime = 31;

+ 4 - 2
jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java

@@ -82,7 +82,9 @@ public class SphereCollisionShape extends CollisionShape {
      */
     @Override
     public void setScale(Vector3f scale) {
-        Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "SphereCollisionShape cannot be scaled");
+        if (!scale.equals(Vector3f.UNIT_XYZ)) {
+            Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "SphereCollisionShape cannot be scaled");
+        }
     }
 
     protected void createShape() {
@@ -91,7 +93,7 @@ public class SphereCollisionShape extends CollisionShape {
 //        new SphereShape(radius);
 //        objectId.setLocalScaling(Converter.convert(getScale()));
 //        objectId.setMargin(margin);
-        setScale(scale);
+        setScale(scale); // Set the scale to 1
         setMargin(margin);
     }
     

+ 7 - 11
jme3-core/src/main/java/com/jme3/animation/Bone.java

@@ -139,11 +139,7 @@ public final class Bone implements Savable {
     /**
      * Special-purpose copy constructor. 
      * <p>
-     * Only copies the name and bind pose from the original.
-     * <p>
-     * WARNING: Local bind pose and world inverse bind pose transforms shallow 
-     * copied. Modifying that data on the original bone will cause it to
-     * be recomputed on any cloned bones.
+     * Only copies the name, user control state and bind pose transforms from the original.
      * <p>
      * The rest of the data is <em>NOT</em> copied, as it will be
      * generated automatically when the bone is animated.
@@ -155,13 +151,13 @@ public final class Bone implements Savable {
 
         userControl = source.userControl;
 
-        bindPos = source.bindPos;
-        bindRot = source.bindRot;
-        bindScale = source.bindScale;
+        bindPos = source.bindPos.clone();
+        bindRot = source.bindRot.clone();
+        bindScale = source.bindScale.clone();
 
-        modelBindInversePos = source.modelBindInversePos;
-        modelBindInverseRot = source.modelBindInverseRot;
-        modelBindInverseScale = source.modelBindInverseScale;
+        modelBindInversePos = source.modelBindInversePos.clone();
+        modelBindInverseRot = source.modelBindInverseRot.clone();
+        modelBindInverseScale = source.modelBindInverseScale.clone();
 
         // parent and children will be assigned manually..
     }

+ 31 - 0
jme3-core/src/main/java/com/jme3/app/Application.java

@@ -650,12 +650,28 @@ public class Application implements SystemListener {
      * Callables are executed right at the beginning of the main loop.
      * They are executed even if the application is currently paused
      * or out of focus.
+     * 
+     * @param callable The callable to run in the main jME3 thread
      */
     public <V> Future<V> enqueue(Callable<V> callable) {
         AppTask<V> task = new AppTask<V>(callable);
         taskQueue.add(task);
         return task;
     }
+    
+    /**
+     * Enqueues a runnable object to execute in the jME3
+     * rendering thread.
+     * <p>
+     * Runnables are executed right at the beginning of the main loop.
+     * They are executed even if the application is currently paused
+     * or out of focus.
+     * 
+     * @param runnable The runnable to run in the main jME3 thread
+     */    
+    public void enqueue(Runnable runnable){
+        enqueue(new RunnableWrapper(runnable));
+    }
 
     /**
      * Runs tasks enqueued via {@link #enqueue(Callable)}
@@ -740,4 +756,19 @@ public class Application implements SystemListener {
         return viewPort;
     }
 
+    private class RunnableWrapper implements Callable{
+        private final Runnable runnable;
+
+        public RunnableWrapper(Runnable runnable){
+            this.runnable = runnable;
+        }
+
+        @Override
+        public Object call(){
+            runnable.run();
+            return null;
+        }
+        
+    }
+    
 }

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

@@ -156,7 +156,7 @@ public class AssetKey<T> implements Savable, Cloneable {
                     list.removeLast();
                 } else {
                     list.add("..");
-                    Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path is outside assetmanager root");
+                    Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path \"{0}\" is outside assetmanager root", path);
                 }
             } else {
                 list.add(string);

+ 34 - 4
jme3-core/src/main/java/com/jme3/audio/AudioNode.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2012, 2016 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,6 +33,7 @@ package com.jme3.audio;
 
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetNotFoundException;
+import com.jme3.audio.AudioData.DataType;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
@@ -127,6 +128,17 @@ public class AudioNode extends Node implements AudioSource {
     public AudioNode(AudioData audioData, AudioKey audioKey) {
         setAudioData(audioData, audioKey);
     }
+    
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * @param type The type. If <code>{@link com.jme3.audio.AudioData.DataType}.Stream</code>, the audio will be streamed gradually from disk,
+     *             otherwise it will be buffered (<code>{@link com.jme3.audio.AudioData.DataType}.Buffer</code>)
+     */
+    public AudioNode(AssetManager assetManager, String name, DataType type) {
+        this(assetManager, name, type == DataType.Stream, true);
+    }
 
     /**
      * Creates a new <code>AudioNode</code> with the given audio file.
@@ -139,6 +151,8 @@ public class AudioNode extends Node implements AudioSource {
      * the stream cache is used. When enabled, the audio stream will
      * be read entirely but not decoded, allowing features such as 
      * seeking, looping and determining duration.
+     * 
+     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
      */
     public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
         this.audioKey = new AudioKey(name, stream, streamCache);
@@ -152,9 +166,11 @@ public class AudioNode extends Node implements AudioSource {
      * @param name The filename of the audio file
      * @param stream If true, the audio will be streamed gradually from disk, 
      *               otherwise, it will be buffered.
+     * 
+     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
      */
     public AudioNode(AssetManager assetManager, String name, boolean stream) {
-        this(assetManager, name, stream, false);
+        this(assetManager, name, stream, true); // Always streamCached
     }
 
     /**
@@ -167,7 +183,7 @@ public class AudioNode extends Node implements AudioSource {
      * @deprecated AudioRenderer parameter is ignored.
      */
     public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
-        this(assetManager, name, false);
+        this(assetManager, name, DataType.Buffer);
     }
     
     /**
@@ -175,9 +191,10 @@ public class AudioNode extends Node implements AudioSource {
      * 
      * @param assetManager The asset manager to use to load the audio file
      * @param name The filename of the audio file
+     * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead
      */
     public AudioNode(AssetManager assetManager, String name) {
-        this(assetManager, name, false);
+        this(assetManager, name, DataType.Buffer);
     }
     
     protected AudioRenderer getRenderer() {
@@ -310,6 +327,19 @@ public class AudioNode extends Node implements AudioSource {
         this.status = status;
     }
 
+    /**
+     * Get the Type of the underlying AudioData to see if it's streamed or buffered.
+     * This is a shortcut to getAudioData().getType()
+     * <b>Warning</b>: Can return null!
+     * @return The {@link com.jme3.audio.AudioData.DataType} of the audio node.
+     */
+    public DataType getType() {
+        if (data == null)
+            return null;
+        else
+            return data.getDataType();
+    }
+    
     /**
      * @return True if the audio will keep looping after it is done playing,
      * otherwise, false.

+ 7 - 4
jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java

@@ -109,8 +109,11 @@ public class BIHTree implements CollisionData {
         this.mesh = mesh;
         this.maxTrisPerNode = maxTrisPerNode;
 
-        if (maxTrisPerNode < 1 || mesh == null) {
-            throw new IllegalArgumentException();
+        if (maxTrisPerNode < 1) {
+            throw new IllegalArgumentException("maxTrisPerNode cannot be less than 1");
+        }
+        if (mesh == null) {
+            throw new IllegalArgumentException("Mesh cannot be null");
         }
 
         bihSwapTmp = new float[9];
@@ -451,7 +454,7 @@ public class BIHTree implements CollisionData {
         } else if (bv instanceof BoundingBox) {
             bbox = new BoundingBox((BoundingBox) bv);
         } else {
-            throw new UnsupportedCollisionException();
+            throw new UnsupportedCollisionException("BoundingVolume:" + bv);
         }
 
         bbox.transform(worldMatrix.invert(), bbox);
@@ -470,7 +473,7 @@ public class BIHTree implements CollisionData {
             BoundingVolume bv = (BoundingVolume) other;
             return collideWithBoundingVolume(bv, worldMatrix, results);
         } else {
-            throw new UnsupportedCollisionException();
+            throw new UnsupportedCollisionException("Collidable:" + other);
         }
     }
 

+ 11 - 0
jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java

@@ -106,6 +106,7 @@ public class ParticleEmitter extends Geometry {
     private boolean worldSpace = true;
     //variable that helps with computations
     private transient Vector3f temp = new Vector3f();
+    private transient Vector3f lastPos;
 
     public static class ParticleEmitterControl implements Control {
 
@@ -1013,12 +1014,16 @@ public class ParticleEmitter extends Geometry {
         
         // Spawns particles within the tpf timeslot with proper age
         float interval = 1f / particlesPerSec;
+        float originalTpf = tpf;
         tpf += timeDifference;
         while (tpf > interval){
             tpf -= interval;
             Particle p = emitParticle(min, max);
             if (p != null){
                 p.life -= tpf;
+                if (lastPos != null && isInWorldSpace()) {
+                    p.position.interpolateLocal(lastPos, 1 - tpf / originalTpf);
+                }
                 if (p.life <= 0){
                     freeParticle(lastUsed);
                 }else{
@@ -1028,6 +1033,12 @@ public class ParticleEmitter extends Geometry {
         }
         timeDifference = tpf;
 
+        if (lastPos == null) {
+            lastPos = new Vector3f();
+        }
+
+        lastPos.set(getWorldTranslation());
+
         BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
         bbox.setMinMax(min, max);
         this.setBoundRefresh();

+ 12 - 3
jme3-core/src/main/java/com/jme3/input/KeyInput.java

@@ -36,6 +36,11 @@ package com.jme3.input;
  */
 public interface KeyInput extends Input {
 
+    /**
+     * unmapped key.
+     */
+    public static final int KEY_UNKNOWN = 0x00;
+
     /**
      * escape key.
      */
@@ -518,17 +523,17 @@ public interface KeyInput extends Input {
      * delete key.
      */
     public static final int KEY_DELETE = 0xD3;
-    
+
     /**
      * Left "Windows" key on PC keyboards, left "Option" key on Mac keyboards.
      */
     public static final int KEY_LMETA  = 0xDB;
-    
+
     /**
      * Right "Windows" key on PC keyboards, right "Option" key on Mac keyboards.
      */
     public static final int KEY_RMETA = 0xDC;
-    
+
     public static final int KEY_APPS = 0xDD;
     /**
      * power key.
@@ -539,4 +544,8 @@ public interface KeyInput extends Input {
      */
     public static final int KEY_SLEEP = 0xDF;
 
+    /**
+     * the last key.
+     */
+    public static final int KEY_LAST = 0xE0;
 }

+ 24 - 22
jme3-core/src/main/java/com/jme3/input/KeyNames.java

@@ -34,10 +34,11 @@ package com.jme3.input;
 import static com.jme3.input.KeyInput.*;
 
 public class KeyNames {
-    
+
     private static final String[] KEY_NAMES = new String[0xFF];
-    
+
     static {
+        KEY_NAMES[KEY_UNKNOWN] = "Unknown";
         KEY_NAMES[KEY_0] = "0";
         KEY_NAMES[KEY_1] = "1";
         KEY_NAMES[KEY_2] = "2";
@@ -48,7 +49,7 @@ public class KeyNames {
         KEY_NAMES[KEY_7] = "7";
         KEY_NAMES[KEY_8] = "8";
         KEY_NAMES[KEY_9] = "9";
-        
+
         KEY_NAMES[KEY_Q] = "Q";
         KEY_NAMES[KEY_W] = "W";
         KEY_NAMES[KEY_E] = "E";
@@ -75,7 +76,7 @@ public class KeyNames {
         KEY_NAMES[KEY_B] = "B";
         KEY_NAMES[KEY_N] = "N";
         KEY_NAMES[KEY_M] = "M";
-        
+
         KEY_NAMES[KEY_F1] = "F1";
         KEY_NAMES[KEY_F2] = "F2";
         KEY_NAMES[KEY_F3] = "F3";
@@ -91,7 +92,7 @@ public class KeyNames {
         KEY_NAMES[KEY_F13] = "F13";
         KEY_NAMES[KEY_F14] = "F14";
         KEY_NAMES[KEY_F15] = "F15";
-        
+
         KEY_NAMES[KEY_NUMPAD0] = "Numpad 0";
         KEY_NAMES[KEY_NUMPAD1] = "Numpad 1";
         KEY_NAMES[KEY_NUMPAD2] = "Numpad 2";
@@ -102,25 +103,26 @@ public class KeyNames {
         KEY_NAMES[KEY_NUMPAD7] = "Numpad 7";
         KEY_NAMES[KEY_NUMPAD8] = "Numpad 8";
         KEY_NAMES[KEY_NUMPAD9] = "Numpad 9";
-        
+
         KEY_NAMES[KEY_NUMPADEQUALS] = "Numpad =";
         KEY_NAMES[KEY_NUMPADENTER] = "Numpad Enter";
-        KEY_NAMES[KEY_NUMPADCOMMA] = "Numpad .";
+        KEY_NAMES[KEY_NUMPADCOMMA] = "Numpad ,";
         KEY_NAMES[KEY_DIVIDE] = "Numpad /";
-        
-        
+        KEY_NAMES[KEY_SUBTRACT] = "Numpad -";
+        KEY_NAMES[KEY_DECIMAL] = "Numpad .";
+
         KEY_NAMES[KEY_LMENU] = "Left Alt";
         KEY_NAMES[KEY_RMENU] = "Right Alt";
-        
+
         KEY_NAMES[KEY_LCONTROL] = "Left Ctrl";
         KEY_NAMES[KEY_RCONTROL] = "Right Ctrl";
-        
+
         KEY_NAMES[KEY_LSHIFT] = "Left Shift";
         KEY_NAMES[KEY_RSHIFT] = "Right Shift";
-        
+
         KEY_NAMES[KEY_LMETA] = "Left Option";
         KEY_NAMES[KEY_RMETA] = "Right Option";
-        
+
         KEY_NAMES[KEY_MINUS] = "-";
         KEY_NAMES[KEY_EQUALS] = "=";
         KEY_NAMES[KEY_LBRACKET] = "[";
@@ -137,37 +139,37 @@ public class KeyNames {
         KEY_NAMES[KEY_COLON] = ":";
         KEY_NAMES[KEY_UNDERLINE] = "_";
         KEY_NAMES[KEY_AT] = "@";
-        
+
         KEY_NAMES[KEY_APPS] = "Apps";
         KEY_NAMES[KEY_POWER] = "Power";
         KEY_NAMES[KEY_SLEEP] = "Sleep";
-        
+
         KEY_NAMES[KEY_STOP] = "Stop";
         KEY_NAMES[KEY_ESCAPE] = "Esc";
         KEY_NAMES[KEY_RETURN] = "Enter";
         KEY_NAMES[KEY_SPACE] = "Space";
         KEY_NAMES[KEY_BACK] = "Backspace";
         KEY_NAMES[KEY_TAB] = "Tab";
-        
+
         KEY_NAMES[KEY_SYSRQ] = "SysRq";
         KEY_NAMES[KEY_PAUSE] = "Pause";
-        
+
         KEY_NAMES[KEY_HOME] = "Home";
         KEY_NAMES[KEY_PGUP] = "Page Up";
         KEY_NAMES[KEY_PGDN] = "Page Down";
         KEY_NAMES[KEY_END] = "End";
         KEY_NAMES[KEY_INSERT] = "Insert";
         KEY_NAMES[KEY_DELETE] = "Delete";
-        
+
         KEY_NAMES[KEY_UP] = "Up";
         KEY_NAMES[KEY_LEFT] = "Left";
         KEY_NAMES[KEY_RIGHT] = "Right";
         KEY_NAMES[KEY_DOWN] = "Down";
-        
+
         KEY_NAMES[KEY_NUMLOCK] = "Num Lock";
         KEY_NAMES[KEY_CAPITAL] = "Caps Lock";
         KEY_NAMES[KEY_SCROLL] = "Scroll Lock";
-        
+
         KEY_NAMES[KEY_KANA] = "Kana";
         KEY_NAMES[KEY_CONVERT] = "Convert";
         KEY_NAMES[KEY_NOCONVERT] = "No Convert";
@@ -177,8 +179,8 @@ public class KeyNames {
         KEY_NAMES[KEY_AX] = "Ax";
         KEY_NAMES[KEY_UNLABELED] = "Unlabeled";
     }
-    
-    public String getName(int keyId){
+
+    public static String getName(int keyId) {
         return KEY_NAMES[keyId];
     }
 }

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

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012, 2015 jMonkeyEngine
+ * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -146,4 +146,10 @@ public class DirectionalLight extends Light {
         direction = (Vector3f) ic.readSavable("direction", null);
     }
 
+    @Override
+    public DirectionalLight clone() {
+        DirectionalLight l = (DirectionalLight)super.clone();
+        l.direction = direction.clone();
+        return l;
+    }
 }

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

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012, 2015 jMonkeyEngine
+ * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -228,7 +228,9 @@ public abstract class Light implements Savable, Cloneable {
     @Override
     public Light clone(){
         try {
-            return (Light) super.clone();
+            Light l = (Light) super.clone();
+            l.color = color.clone();
+            return l;
         } catch (CloneNotSupportedException ex) {
             throw new AssertionError();
         }

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

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012, 2015 jMonkeyEngine
+ * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -241,4 +241,11 @@ public class PointLight extends Light {
             this.invRadius = 0;
         }
     }
+
+    @Override
+    public PointLight clone() {
+        PointLight p = (PointLight)super.clone();
+        p.position = position.clone();
+        return p;
+    }
 }

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

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012, 2015 jMonkeyEngine
+ * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -448,5 +448,13 @@ public class SpotLight extends Light {
             this.invSpotRange = 0;
         }
     }
+
+    @Override
+    public SpotLight clone() {
+        SpotLight s = (SpotLight)super.clone();
+        s.direction = direction.clone();
+        s.position = position.clone();
+        return s;
+    }
 }
 

+ 32 - 3
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -248,16 +248,45 @@ When arrays can be inserted in J3M files
                 if (texKey.isFlipY()) {
                     ret += "Flip ";
                 }
-                if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
-                    ret += "Repeat ";
+
+                //Wrap mode
+                ret += getWrapMode(texVal, Texture.WrapAxis.S);
+                ret += getWrapMode(texVal, Texture.WrapAxis.T);
+                ret += getWrapMode(texVal, Texture.WrapAxis.R);
+
+                //Min and Mag filter
+                Texture.MinFilter def =  Texture.MinFilter.BilinearNoMipMaps;
+                if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
+                    def = Texture.MinFilter.Trilinear;
+                }
+                if(texVal.getMinFilter() != def){
+                    ret += "Min" + texVal.getMinFilter().name()+ " ";
+                }
+
+                if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
+                    ret += "Mag" + texVal.getMagFilter().name()+ " ";
                 }
 
-                return ret + texKey.getName();
+                return ret + "\"" + texKey.getName() + "\"";
             default:
                 return null; // parameter type not supported in J3M
         }
     }
 
+    private String getWrapMode(Texture texVal, Texture.WrapAxis axis) {
+        WrapMode mode = WrapMode.EdgeClamp;
+        try{
+            mode = texVal.getWrap(axis);
+        }catch (IllegalArgumentException e){
+            //this axis doesn't exist on the texture
+            return "";
+        }
+        if(mode != WrapMode.EdgeClamp){
+            return"Wrap"+ mode.name() + "_" + axis.name() + " ";
+        }
+        return "";
+    }
+
     @Override
     public MatParam clone() {
         try {

+ 2 - 0
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -1236,12 +1236,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         oc.write(def.getAssetName(), "material_def", null);
         oc.write(additionalState, "render_state", null);
         oc.write(transparent, "is_transparent", false);
+        oc.write(name, "name", null);
         oc.writeStringSavableMap(paramValues, "parameters", null);
     }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
 
+        name = ic.readString("name", null);
         additionalState = (RenderState) ic.readSavable("render_state", null);
         transparent = ic.readBoolean("is_transparent", false);
 

+ 34 - 95
jme3-core/src/main/java/com/jme3/scene/BatchNode.java

@@ -31,14 +31,6 @@
  */
 package com.jme3.scene;
 
-import com.jme3.export.*;
-import com.jme3.material.Material;
-import com.jme3.math.Matrix4f;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.mesh.IndexBuffer;
-import com.jme3.util.SafeArrayList;
-import com.jme3.util.TempVars;
-import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.FloatBuffer;
 import java.util.ArrayList;
@@ -48,13 +40,22 @@ import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.SafeArrayList;
+import com.jme3.util.TempVars;
+
 /**
  * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph.
  * There is one geometry per different material in the sub tree.
  * The geometries are directly attached to the node in the scene graph.
  * Usage is like any other node except you have to call the {@link #batch()} method once all the geometries have been attached to the sub scene graph and their material set
  * (see todo more automagic for further enhancements)
- * All the geometries that have been batched are set to {@link CullHint#Always} to not render them.
+ * All the geometries that have been batched are set to not be rendered - {@link CullHint} is left intact.
  * The sub geometries can be transformed as usual, their transforms are used to update the mesh of the geometryBatch.
  * Sub geoms can be removed but it may be slower than the normal spatial removing
  * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
@@ -72,7 +73,7 @@ public class BatchNode extends GeometryGroupNode {
      */
     protected SafeArrayList<Batch> batches = new SafeArrayList<Batch>(Batch.class);
     /**
-     * a map storing he batches by geometry to quickly acces the batch when updating
+     * a map for storing the batches by geometry to quickly access the batch when updating
      */
     protected Map<Geometry, Batch> batchesByGeom = new HashMap<Geometry, Batch>();
     /**
@@ -115,10 +116,9 @@ public class BatchNode extends GeometryGroupNode {
     }
 
     @Override
-    public void onGeoemtryUnassociated(Geometry geom) {
+    public void onGeometryUnassociated(Geometry geom) {
         setNeedsFullRebatch(true);
     }
-    
 
     protected Matrix4f getTransformMatrix(Geometry g){
         return g.cachedWorldMat;
@@ -166,7 +166,7 @@ public class BatchNode extends GeometryGroupNode {
      */
     public void batch() {
         doBatch();
-        //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice        
+        //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
         for (Batch batch : batches.getArray()) {
             batch.geometry.setIgnoreTransform(true);
             batch.geometry.setUserData(UserData.JME_PHYSICSIGNORE, true);
@@ -174,10 +174,10 @@ public class BatchNode extends GeometryGroupNode {
     }
 
     protected void doBatch() {
-        Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();    
+        Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
         int nbGeoms = 0;
 
-        gatherGeomerties(matMap, this, needsFullRebatch);
+        gatherGeometries(matMap, this, needsFullRebatch);
         if (needsFullRebatch) {
             for (Batch batch : batches.getArray()) {
                 batch.geometry.removeFromParent();
@@ -221,7 +221,7 @@ public class BatchNode extends GeometryGroupNode {
 
             batch.geometry.setMesh(m);
             batch.geometry.getMesh().updateCounts();
-            batch.geometry.updateModelBound();            
+            batch.geometry.updateModelBound();
             batches.add(batch);
         }
         if (batches.size() > 0) {
@@ -271,7 +271,7 @@ public class BatchNode extends GeometryGroupNode {
     }
     
     
-    private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
+    private void gatherGeometries(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
 
         if (n instanceof Geometry) {
 
@@ -304,7 +304,7 @@ public class BatchNode extends GeometryGroupNode {
                 if (child instanceof BatchNode) {
                     continue;
                 }
-                gatherGeomerties(map, child, rebatch);
+                gatherGeometries(map, child, rebatch);
             }
         }
 
@@ -319,7 +319,7 @@ public class BatchNode extends GeometryGroupNode {
         return null;
     }
 
-    private boolean isBatch(Spatial s) {
+    public final boolean isBatch(Spatial s) {
         for (Batch batch : batches.getArray()) {
             if (batch.geometry == s) {
                 return true;
@@ -336,9 +336,6 @@ public class BatchNode extends GeometryGroupNode {
      */
     @Override
     public void setMaterial(Material material) {
-//        for (Batch batch : batches.values()) {
-//            batch.geometry.setMaterial(material);
-//        }
         throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
     }
 
@@ -356,74 +353,7 @@ public class BatchNode extends GeometryGroupNode {
             Batch b = batches.iterator().next();
             return b.geometry.getMaterial();
         }
-        return null;//material;
-    }
-
-//    /**
-//     * Sets the material to the a specific batch of this BatchNode
-//     * 
-//     * 
-//     * @param material the material to use for this geometry
-//     */   
-//    public void setMaterial(Material material,int batchIndex) {
-//        if (!batches.isEmpty()) {
-//            
-//        }
-//        
-//    }
-//
-//    /**
-//     * Returns the material that is used for the first batch of this BatchNode
-//     * 
-//     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
-//     * 
-//     * @return the material that is used for the first batch of this BatchNode
-//     * 
-//     * @see #setMaterial(com.jme3.material.Material) 
-//     */
-//    public Material getMaterial(int batchIndex) {
-//        if (!batches.isEmpty()) {
-//            Batch b = batches.get(batches.keySet().iterator().next());
-//            return b.geometry.getMaterial();
-//        }
-//        return null;//material;
-//    }
-    @Override
-    public void write(JmeExporter ex) throws IOException {
-        super.write(ex);
-        OutputCapsule oc = ex.getCapsule(this);
-//
-//        if (material != null) {
-//            oc.write(material.getAssetName(), "materialName", null);
-//        }
-//        oc.write(material, "material", null);
-
-    }
-
-    @Override
-    public void read(JmeImporter im) throws IOException {
-        super.read(im);
-        InputCapsule ic = im.getCapsule(this);
-
-
-//        material = null;
-//        String matName = ic.readString("materialName", null);
-//        if (matName != null) {
-//            // Material name is set,
-//            // Attempt to load material via J3M
-//            try {
-//                material = im.getAssetManager().loadMaterial(matName);
-//            } catch (AssetNotFoundException ex) {
-//                // Cannot find J3M file.
-//                logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
-//                        matName);
-//            }
-//        }
-//        // If material is NULL, try to load it from the geometry
-//        if (material == null) {
-//            material = (Material) ic.readSavable("material", null);
-//        }
-
+        return null;
     }
 
     /**
@@ -494,7 +424,7 @@ public class BatchNode extends GeometryGroupNode {
             if (mode != null && mode != listMode) {
                 throw new UnsupportedOperationException("Cannot combine different"
                         + " primitive types: " + mode + " != " + listMode);
-            }            
+            }
             mode = listMode;
             if (mode == Mesh.Mode.Lines) {
                 if (lineWidth != 1f && listLineWidth != lineWidth) {
@@ -510,8 +440,7 @@ public class BatchNode extends GeometryGroupNode {
         outMesh.setMode(mode);
         outMesh.setLineWidth(lineWidth);
         if (totalVerts >= 65536) {
-            // make sure we create an UnsignedInt buffer so
-            // we can fit all of the meshes
+            // make sure we create an UnsignedInt buffer so we can fit all of the meshes
             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
         } else {
             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
@@ -733,7 +662,6 @@ public class BatchNode extends GeometryGroupNode {
     }
 
     protected class Batch {
-
         /**
          * update the batchesByGeom map for this batch with the given List of geometries
          * @param list 
@@ -745,7 +673,7 @@ public class BatchNode extends GeometryGroupNode {
                 }
             }
         }
-        Geometry geometry;        
+        Geometry geometry;
     }
 
     protected void setNeedsFullRebatch(boolean needsFullRebatch) {
@@ -771,4 +699,15 @@ public class BatchNode extends GeometryGroupNode {
         }
         return clone;
     }
+    
+    @Override
+    public int collideWith(Collidable other, CollisionResults results) {
+        int total = 0;
+        for (Spatial child : children.getArray()){
+            if (!isBatch(child)) {
+                total += child.collideWith(other, results);
+            }
+        }
+        return total;
+    }
 }

+ 1 - 1
jme3-core/src/main/java/com/jme3/scene/Geometry.java

@@ -344,7 +344,7 @@ public class Geometry extends Spatial {
         if (groupNode != null) {
             // Once the geometry is removed 
             // from the parent, the group node needs to be updated.
-            groupNode.onGeoemtryUnassociated(this);
+            groupNode.onGeometryUnassociated(this);
             groupNode = null;
             
             // change the default to -1 to make error detection easier

+ 1 - 1
jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java

@@ -83,5 +83,5 @@ public abstract class GeometryGroupNode extends Node {
      * 
      * @param geom The Geometry which is being unassociated.
      */
-    public abstract void onGeoemtryUnassociated(Geometry geom);
+    public abstract void onGeometryUnassociated(Geometry geom);
 }

+ 5 - 0
jme3-core/src/main/java/com/jme3/scene/Node.java

@@ -687,6 +687,11 @@ public class Node extends Spatial {
 //            childClone.parent = nodeClone;
 //            nodeClone.children.add(childClone);
 //        }
+
+        // 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;
     }
 

+ 1 - 1
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java

@@ -329,7 +329,7 @@ public class InstancedNode extends GeometryGroupNode {
     }
 
     @Override
-    public void onGeoemtryUnassociated(Geometry geom) {
+    public void onGeometryUnassociated(Geometry geom) {
         removeFromInstancedGeometry(geom);
     }
 }

+ 2 - 1
jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java

@@ -211,7 +211,7 @@ public class Cylinder extends Mesh {
      */
     public void updateGeometry(int axisSamples, int radialSamples,
             float radius, float radius2, float height, boolean closed, boolean inverted) {
-        this.axisSamples = axisSamples + (closed ? 2 : 0);
+        this.axisSamples = axisSamples;
         this.radialSamples = radialSamples;
         this.radius = radius;
         this.radius2 = radius2;
@@ -222,6 +222,7 @@ public class Cylinder extends Mesh {
 //        VertexBuffer pvb = getBuffer(Type.Position);
 //        VertexBuffer nvb = getBuffer(Type.Normal);
 //        VertexBuffer tvb = getBuffer(Type.TexCoord);
+        axisSamples += (closed ? 2 : 0);
 
         // Vertices
         int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);

+ 1 - 1
jme3-core/src/main/java/com/jme3/system/AppSettings.java

@@ -963,7 +963,7 @@ public final class AppSettings extends HashMap<String, Object> {
         return getString("SettingsDialogImage");
     }
 
-    public boolean getGammaCorrection() {
+    public boolean isGammaCorrection() {
         return getBoolean("GammaCorrection");
     }
     

+ 34 - 27
jme3-core/src/main/java/com/jme3/util/BufferUtils.java

@@ -51,7 +51,6 @@ import java.nio.IntBuffer;
 import java.nio.LongBuffer;
 import java.nio.ShortBuffer;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -401,6 +400,25 @@ public final class BufferUtils {
         vector.z = buf.get(index * 3 + 2);
     }
 
+    /**
+     * Updates the values of the given vector from the specified buffer at the
+     * index provided.
+     * 
+     * @param vector
+     *            the vector to set data on
+     * @param buf
+     *            the buffer to read from
+     * @param index
+     *            the position (in terms of vectors, not floats) to read from
+     *            the buf
+     */
+    public static void populateFromBuffer(Vector4f vector, FloatBuffer buf, int index) {
+        vector.x = buf.get(index * 4);
+        vector.y = buf.get(index * 4 + 1);
+        vector.z = buf.get(index * 4 + 2);
+        vector.w = buf.get(index * 4 + 3);
+    }
+
     /**
      * Generates a Vector3f array from the given FloatBuffer.
      * 
@@ -1249,7 +1267,6 @@ public final class BufferUtils {
             System.out.println(store.toString());
         }
     }
-    private static final AtomicBoolean loadedMethods = new AtomicBoolean(false);
     private static Method cleanerMethod = null;
     private static Method cleanMethod = null;
     private static Method viewedBufferMethod = null;
@@ -1269,31 +1286,23 @@ public final class BufferUtils {
         }
     }
 
-    private static void loadCleanerMethods() {
-        // If its already true, exit, if not, set it to true.
-        if (BufferUtils.loadedMethods.getAndSet(true)) {
-            return;
+    static {
+        // Oracle JRE / OpenJDK
+        cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner");
+        cleanMethod = loadMethod("sun.misc.Cleaner", "clean");
+        viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer");
+        if (viewedBufferMethod == null) {
+            // They changed the name in Java 7 (???)
+            viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment");
         }
-        // This could potentially be called many times if used from multiple
-        // threads
-        synchronized (BufferUtils.loadedMethods) {
-            // Oracle JRE / OpenJDK
-            cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner");
-            cleanMethod = loadMethod("sun.misc.Cleaner", "clean");
-            viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer");
-            if (viewedBufferMethod == null) {
-                // They changed the name in Java 7 (???)
-                viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment");
-            }
 
-            // Apache Harmony
-            ByteBuffer bb = BufferUtils.createByteBuffer(1);
-            Class<?> clazz = bb.getClass();
-            try {
-                freeMethod = clazz.getMethod("free");
-            } catch (NoSuchMethodException ex) {
-            } catch (SecurityException ex) {
-            }
+        // Apache Harmony
+        ByteBuffer bb = BufferUtils.createByteBuffer(1);
+        Class<?> clazz = bb.getClass();
+        try {
+            freeMethod = clazz.getMethod("free");
+        } catch (NoSuchMethodException ex) {
+        } catch (SecurityException ex) {
         }
     }
 
@@ -1314,8 +1323,6 @@ public final class BufferUtils {
             return;
         }
 
-        BufferUtils.loadCleanerMethods();
-
         try {
             if (freeMethod != null) {
                 freeMethod.invoke(toBeDestroyed);

+ 97 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java

@@ -0,0 +1,97 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface MikkTSpaceContext {
+
+    /**
+     * Returns the number of faces (triangles/quads) on the mesh to be
+     * processed.
+     *
+     * @return
+     */
+    public int getNumFaces();
+
+    /**
+     * Returns the number of vertices on face number iFace iFace is a number in
+     * the range {0, 1, ..., getNumFaces()-1}
+     *
+     * @param face
+     * @return
+     */
+    public int getNumVerticesOfFace(int face);
+
+    /**
+     * returns the position/normal/texcoord of the referenced face of vertex
+     * number iVert. iVert is in the range {0,1,2} for triangles and {0,1,2,3}
+     * for quads.
+     *
+     * @param posOut
+     * @param face
+     * @param vert
+     */
+    public void getPosition(float posOut[], int face, int vert);
+
+    public void getNormal(float normOut[], int face, int vert);
+
+    public void getTexCoord(float texOut[], int face, int vert);
+
+    /**
+     * The call-backsetTSpaceBasic() is sufficient for basic normal mapping.
+     * This function is used to return the tangent and sign to the application.
+     * tangent is a unit length vector. For normal maps it is sufficient to use
+     * the following simplified version of the bitangent which is generated at
+     * pixel/vertex level.
+     *
+     * bitangent = fSign * cross(vN, tangent);
+     *
+     * Note that the results are returned unindexed. It is possible to generate
+     * a new index list But averaging/overwriting tangent spaces by using an
+     * already existing index list WILL produce INCRORRECT results. DO NOT! use
+     * an already existing index list.
+     *
+     * @param tangent
+     * @param sign
+     * @param face
+     * @param vert
+     */
+    public void setTSpaceBasic(float tangent[], float sign, int face, int vert);
+
+    /**
+     * This function is used to return tangent space results to the application.
+     * tangent and biTangent are unit length vectors and fMagS and fMagT are
+     * their true magnitudes which can be used for relief mapping effects.
+     *
+     * biTangent is the "real" bitangent and thus may not be perpendicular to
+     * tangent. However, both are perpendicular to the vertex normal. For normal
+     * maps it is sufficient to use the following simplified version of the
+     * bitangent which is generated at pixel/vertex level.
+     *
+     * <pre>
+     * fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
+     * bitangent = fSign * cross(vN, tangent);
+     * </pre>
+     *
+     * Note that the results are returned unindexed. It is possible to generate
+     * a new index list. But averaging/overwriting tangent spaces by using an
+     * already existing index list WILL produce INCRORRECT results. DO NOT! use
+     * an already existing index list.
+     *
+     * @param tangent
+     * @param biTangent
+     * @param magS
+     * @param magT
+     * @param isOrientationPreserving
+     * @param face
+     * @param vert
+     */
+    void setTSpace(float tangent[], float biTangent[], float magS, float magT,
+            boolean isOrientationPreserving, int face, int vert);
+}

+ 100 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java

@@ -0,0 +1,100 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ *
+ * @author Nehon
+ */
+public class MikkTSpaceImpl implements MikkTSpaceContext {
+
+    Mesh mesh;
+
+    public MikkTSpaceImpl(Mesh mesh) {
+        this.mesh = mesh;
+        VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent);
+        if(tangentBuffer == null){
+            FloatBuffer fb = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 4);
+            mesh.setBuffer(VertexBuffer.Type.Tangent, 4, fb);            
+        }
+        
+        //TODO ensure the Tangent buffer exists, else create one.
+    }
+
+    @Override
+    public int getNumFaces() {
+        return mesh.getTriangleCount();        
+    }
+
+    @Override
+    public int getNumVerticesOfFace(int face) {
+        return 3;
+    }
+
+    @Override
+    public void getPosition(float[] posOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer pos = (FloatBuffer) position.getData();
+        pos.position(vertIndex * 3);
+        posOut[0] = pos.get();
+        posOut[1] = pos.get();
+        posOut[2] = pos.get();
+    }
+
+    @Override
+    public void getNormal(float[] normOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer normal = mesh.getBuffer(VertexBuffer.Type.Normal);
+        FloatBuffer norm = (FloatBuffer) normal.getData();
+        norm.position(vertIndex * 3);
+        normOut[0] = norm.get();
+        normOut[1] = norm.get();
+        normOut[2] = norm.get();
+    }
+
+    @Override
+    public void getTexCoord(float[] texOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer texCoord = mesh.getBuffer(VertexBuffer.Type.TexCoord);
+        FloatBuffer tex = (FloatBuffer) texCoord.getData();
+        tex.position(vertIndex * 2);
+        texOut[0] = tex.get();
+        texOut[1] = tex.get();        
+    }
+
+    @Override
+    public void setTSpaceBasic(float[] tangent, float sign, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent);
+        FloatBuffer tan = (FloatBuffer) tangentBuffer.getData();
+        
+        tan.position(vertIndex * 4);
+        tan.put(tangent);
+        tan.put(sign);
+        
+        tan.rewind();
+        tangentBuffer.setUpdateNeeded();
+    }
+
+    @Override
+    public void setTSpace(float[] tangent, float[] biTangent, float magS, float magT, boolean isOrientationPreserving, int face, int vert) {
+        //Do nothing
+    }
+
+    private int getIndex(int face, int vert) {
+        IndexBuffer index = mesh.getIndexBuffer();
+        int vertIndex = index.get(face * 3 + vert);
+        return vertIndex;
+    }
+
+}

+ 1717 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java

@@ -0,0 +1,1717 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This tangent generator is Highly experimental.
+ * This is the Java translation of The mikktspace generator made by Morten S. Mikkelsen
+ * C Source code can be found here
+ * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c
+ * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h
+ * 
+ * MikkTspace looks like the new standard of tengent generation in 3D softwares.
+ * Xnormal, Blender, Substance painter, and many more use it.
+ * 
+ * Usage is :
+ * MikkTSpaceTangentGenerator.generate(spatial);
+ * 
+ * 
+ * 
+ * @author Nehon
+ */
+public class MikktspaceTangentGenerator {
+
+    private final static int MARK_DEGENERATE = 1;
+    private final static int QUAD_ONE_DEGEN_TRI = 2;
+    private final static int GROUP_WITH_ANY = 4;
+    private final static int ORIENT_PRESERVING = 8;
+    private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL;
+    static final int CELLS = 2048;
+
+    static int makeIndex(final int face, final int vert) {
+        assert (vert >= 0 && vert < 4 && face >= 0);
+        return (face << 2) | (vert & 0x3);
+    }
+
+    private static void indexToData(int[] face, int[] vert, final int indexIn) {
+        vert[0] = indexIn & 0x3;
+        face[0] = indexIn >> 2;
+    }
+
+    static TSpace avgTSpace(final TSpace tS0, final TSpace tS1) {
+        TSpace tsRes = new TSpace();
+
+        // this if is important. Due to floating point precision
+        // averaging when s0 == s1 will cause a slight difference
+        // which results in tangent space splits later on
+        if (tS0.magS == tS1.magS && tS0.magT == tS1.magT && tS0.os.equals(tS1.os) && tS0.ot.equals(tS1.ot)) {
+            tsRes.magS = tS0.magS;
+            tsRes.magT = tS0.magT;
+            tsRes.os.set(tS0.os);
+            tsRes.ot.set(tS0.ot);
+        } else {
+            tsRes.magS = 0.5f * (tS0.magS + tS1.magS);
+            tsRes.magT = 0.5f * (tS0.magT + tS1.magT);
+            tsRes.os.set(tS0.os).addLocal(tS1.os).normalizeLocal();
+            tsRes.ot.set(tS0.ot).addLocal(tS1.ot).normalizeLocal();
+        }
+        return tsRes;
+    }
+
+    public static void generate(Spatial s){
+        if(s instanceof Node){
+            Node n = (Node)s;
+            for (Spatial child : n.getChildren()) {
+                generate(child);
+            }
+        } else if (s instanceof Geometry){
+            Geometry g = (Geometry)s;
+            MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh());
+            if(!genTangSpaceDefault(context)){
+                Logger.getLogger(MikktspaceTangentGenerator.class.getName()).log(Level.SEVERE, "Failed to generate tangents for geometry " + g.getName());
+            }
+        }
+    }
+    
+    public static boolean genTangSpaceDefault(MikkTSpaceContext mikkTSpace) {
+        return genTangSpace(mikkTSpace, 180.0f);
+    }
+
+    public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float angularThreshold) {
+
+        // count nr_triangles
+        int[] piTriListIn;
+        int[] piGroupTrianglesBuffer;
+        TriInfo[] pTriInfos;
+        Group[] pGroups;
+        TSpace[] psTspace;
+        int iNrTrianglesIn = 0;
+        int iNrTSPaces, iTotTris, iDegenTriangles, iNrMaxGroups;
+        int iNrActiveGroups, index;
+        final int iNrFaces = mikkTSpace.getNumFaces();
+        //boolean bRes = false;
+        final float fThresCos = (float) FastMath.cos((angularThreshold * (float) FastMath.PI) / 180.0f);
+
+        // count triangles on supported faces
+        for (int f = 0; f < iNrFaces; f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts == 3) {
+                ++iNrTrianglesIn;
+            } else if (verts == 4) {
+                iNrTrianglesIn += 2;
+            }
+        }
+        if (iNrTrianglesIn <= 0) {
+            return false;
+        }
+
+        piTriListIn = new int[3 * iNrTrianglesIn];
+        pTriInfos = new TriInfo[iNrTrianglesIn];
+
+        // make an initial triangle -. face index list
+        iNrTSPaces = generateInitialVerticesIndexList(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // make a welded index list of identical positions and attributes (pos, norm, texc)        
+        generateSharedVerticesIndexList(piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // Mark all degenerate triangles
+        iTotTris = iNrTrianglesIn;
+        iDegenTriangles = 0;
+        for (int t = 0; t < iTotTris; t++) {
+            final int i0 = piTriListIn[t * 3 + 0];
+            final int i1 = piTriListIn[t * 3 + 1];
+            final int i2 = piTriListIn[t * 3 + 2];
+            final Vector3f p0 = getPosition(mikkTSpace, i0);
+            final Vector3f p1 = getPosition(mikkTSpace, i1);
+            final Vector3f p2 = getPosition(mikkTSpace, i2);
+            if (p0.equals(p1) || p0.equals(p2) || p1.equals(p2)) {// degenerate
+                pTriInfos[t].flag |= MARK_DEGENERATE;
+                ++iDegenTriangles;
+            }
+        }
+        iNrTrianglesIn = iTotTris - iDegenTriangles;
+
+        // mark all triangle pairs that belong to a quad with only one
+        // good triangle. These need special treatment in DegenEpilogue().
+        // Additionally, move all good triangles to the start of
+        // pTriInfos[] and piTriListIn[] without changing order and
+        // put the degenerate triangles last.
+        degenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);
+
+        // evaluate triangle level attributes and neighbor list        
+        initTriInfo(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // based on the 4 rules, identify groups based on connectivity
+        iNrMaxGroups = iNrTrianglesIn * 3;
+        pGroups = new Group[iNrMaxGroups];
+        piGroupTrianglesBuffer = new int[iNrTrianglesIn * 3];
+
+        iNrActiveGroups
+                = build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn);
+
+        psTspace = new TSpace[iNrTSPaces];
+
+        for (int t = 0; t < iNrTSPaces; t++) {
+            TSpace tSpace = new TSpace();
+            tSpace.os.set(1.0f, 0.0f, 0.0f);
+            tSpace.magS = 1.0f;
+            tSpace.ot.set(0.0f, 1.0f, 0.0f);
+            tSpace.magT = 1.0f;
+            psTspace[t] = tSpace;
+        }
+
+        // make tspaces, each group is split up into subgroups if necessary
+        // based on fAngularThreshold. Finally a tangent space is made for
+        // every resulting subgroup
+        generateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, mikkTSpace);
+
+        // degenerate quads with one good triangle will be fixed by copying a space from
+        // the good triangle to the coinciding vertex.
+        // all other degenerate triangles will just copy a space from any good triangle
+        // with the same welded index in piTriListIn[].
+        DegenEpilogue(psTspace, pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn, iTotTris);
+
+        index = 0;
+        for (int f = 0; f < iNrFaces; f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts != 3 && verts != 4) {
+                continue;
+            }
+
+            // I've decided to let degenerate triangles and group-with-anythings
+            // vary between left/right hand coordinate systems at the vertices.
+            // All healthy triangles on the other hand are built to always be either or.
+
+            /*// force the coordinate system orientation to be uniform for every face.
+             // (this is already the case for good triangles but not for
+             // degenerate ones and those with bGroupWithAnything==true)
+             bool bOrient = psTspace[index].bOrient;
+             if (psTspace[index].iCounter == 0)  // tspace was not derived from a group
+             {
+             // look for a space created in GenerateTSpaces() by iCounter>0
+             bool bNotFound = true;
+             int i=1;
+             while (i<verts && bNotFound)
+             {
+             if (psTspace[index+i].iCounter > 0) bNotFound=false;
+             else ++i;
+             }
+             if (!bNotFound) bOrient = psTspace[index+i].bOrient;
+             }*/
+            // set data
+            for (int i = 0; i < verts; i++) {
+                final TSpace pTSpace = psTspace[index];
+                float tang[] = {pTSpace.os.x, pTSpace.os.y, pTSpace.os.z};
+                float bitang[] = {pTSpace.ot.x, pTSpace.ot.y, pTSpace.ot.z};
+                mikkTSpace.setTSpace(tang, bitang, pTSpace.magS, pTSpace.magT, pTSpace.orient, f, i);
+                mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? 1.0f : (-1.0f), f, i);
+                ++index;
+            }
+        }
+
+        return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // it is IMPORTANT that this function is called to evaluate the hash since
+    // inlining could potentially reorder instructions and generate different
+    // results for the same effective input value fVal.
+    //TODO nehon: Wuuttt? something is fishy about this. How the fuck inlining can reorder instructions? Is that a C thing?
+    static int findGridCell(final float min, final float max, final float val) {
+        final float fIndex = CELLS * ((val - min) / (max - min));
+        final int iIndex = (int) fIndex;
+        return iIndex < CELLS ? (iIndex >= 0 ? iIndex : 0) : (CELLS - 1);
+    }
+
+    static void generateSharedVerticesIndexList(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+
+        // Generate bounding box
+        TmpVert[] pTmpVert;
+        Vector3f vMin = getPosition(mikkTSpace, 0);
+        Vector3f vMax = vMin.clone();
+        Vector3f vDim;
+        float fMin, fMax;
+        for (int i = 1; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            if (vMin.x > vP.x) {
+                vMin.x = vP.x;
+            } else if (vMax.x < vP.x) {
+                vMax.x = vP.x;
+            }
+            if (vMin.y > vP.y) {
+                vMin.y = vP.y;
+            } else if (vMax.y < vP.y) {
+                vMax.y = vP.y;
+            }
+            if (vMin.z > vP.z) {
+                vMin.z = vP.z;
+            } else if (vMax.z < vP.z) {
+                vMax.z = vP.z;
+            }
+        }
+
+        vDim = vMax.subtract(vMin);
+        int iChannel = 0;
+        fMin = vMin.x;
+        fMax = vMax.x;
+        if (vDim.y > vDim.x && vDim.y > vDim.z) {
+            iChannel = 1;
+            fMin = vMin.y;
+            fMax = vMax.y;
+        } else if (vDim.z > vDim.x) {
+            iChannel = 2;
+            fMin = vMin.z;
+            fMax = vMax.z;
+        }
+
+        //TODO Nehon: this is really fishy... seems like a hashtable implementation with nasty array manipulation...
+        int[] piHashTable = new int[iNrTrianglesIn * 3];
+        int[] piHashCount = new int[CELLS];
+        int[] piHashOffsets = new int[CELLS];
+        int[] piHashCount2 = new int[CELLS];
+
+        // count amount of elements in each cell unit
+        for (int i = 0; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z);
+            final int iCell = findGridCell(fMin, fMax, fVal);
+            ++piHashCount[iCell];
+        }
+
+        // evaluate start index of each cell.
+        piHashOffsets[0] = 0;
+        for (int k = 1; k < CELLS; k++) {
+            piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1];
+        }
+
+        // insert vertices
+        for (int i = 0; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z);
+            final int iCell = findGridCell(fMin, fMax, fVal);
+
+            assert (piHashCount2[iCell] < piHashCount[iCell]);
+
+            //    int * pTable = &piHashTable[piHashOffsets[iCell]];
+            //    pTable[piHashCount2[iCell]] = i;  // vertex i has been inserted.
+            piHashTable[piHashOffsets[iCell] + piHashCount2[iCell]] = i;// vertex i has been inserted.     
+            ++piHashCount2[iCell];
+        }
+        for (int k = 0; k < CELLS; k++) {
+            assert (piHashCount2[k] == piHashCount[k]);  // verify the count
+        }
+
+        // find maximum amount of entries in any hash entry
+        int iMaxCount = piHashCount[0];
+        for (int k = 1; k < CELLS; k++) {
+            if (iMaxCount < piHashCount[k]) {
+                iMaxCount = piHashCount[k];
+            }
+        }
+
+        pTmpVert = new TmpVert[iMaxCount];
+
+        // complete the merge
+        for (int k = 0; k < CELLS; k++) {
+            // extract table of cell k and amount of entries in it
+            // int * pTable = &piHashTable[piHashOffsets[k]];
+            final int iEntries = piHashCount[k];
+            if (iEntries < 2) {
+                continue;
+            }
+
+            if (pTmpVert != null) {
+                for (int e = 0; e < iEntries; e++) {
+                    int j = piHashTable[piHashOffsets[k] + e];
+                    final Vector3f vP = getPosition(mikkTSpace, piTriList_in_and_out[j]);
+                    pTmpVert[e] = new TmpVert();
+                    pTmpVert[e].vert[0] = vP.x;
+                    pTmpVert[e].vert[1] = vP.y;
+                    pTmpVert[e].vert[2] = vP.z;
+                    pTmpVert[e].index = j;
+                }
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, 0, iEntries - 1);
+            } else {
+                //TODO Nehon: pTempVert is very unlikely to be null...maybe remove this...
+                int[] pTable = Arrays.copyOfRange(piHashTable, piHashOffsets[k], piHashOffsets[k] + iEntries);
+                MergeVertsSlow(piTriList_in_and_out, mikkTSpace, pTable, iEntries);
+            }
+        }
+    }
+
+    static void MergeVertsFast(int piTriList_in_and_out[], TmpVert pTmpVert[], final MikkTSpaceContext mikkTSpace, final int iL_in, final int iR_in) {
+        // make bbox        
+        float[] fvMin = new float[3];
+        float[] fvMax = new float[3];
+        for (int c = 0; c < 3; c++) {
+            fvMin[c] = pTmpVert[iL_in].vert[c];
+            fvMax[c] = fvMin[c];
+        }
+        for (int l = (iL_in + 1); l <= iR_in; l++) {
+            for (int c = 0; c < 3; c++) {
+                if (fvMin[c] > pTmpVert[l].vert[c]) {
+                    fvMin[c] = pTmpVert[l].vert[c];
+                } else if (fvMax[c] < pTmpVert[l].vert[c]) {
+                    fvMax[c] = pTmpVert[l].vert[c];
+                }
+            }
+        }
+
+        float dx = fvMax[0] - fvMin[0];
+        float dy = fvMax[1] - fvMin[1];
+        float dz = fvMax[2] - fvMin[2];
+
+        int channel = 0;
+        if (dy > dx && dy > dz) {
+            channel = 1;
+        } else if (dz > dx) {
+            channel = 2;
+        }
+
+        float fSep = 0.5f * (fvMax[channel] + fvMin[channel]);
+
+        // terminate recursion when the separation/average value
+        // is no longer strictly between fMin and fMax values.
+        if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) {
+            // complete the weld
+            for (int l = iL_in; l <= iR_in; l++) {
+                int i = pTmpVert[l].index;
+                final int index = piTriList_in_and_out[i];
+                final Vector3f vP = getPosition(mikkTSpace, index);
+                final Vector3f vN = getNormal(mikkTSpace, index);
+                final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+                boolean bNotFound = true;
+                int l2 = iL_in, i2rec = -1;
+                while (l2 < l && bNotFound) {
+                    final int i2 = pTmpVert[l2].index;
+                    final int index2 = piTriList_in_and_out[i2];
+                    final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                    final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                    final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+                    i2rec = i2;
+
+                    //if (vP==vP2 && vN==vN2 && vT==vT2)
+                    if (vP.x == vP2.x && vP.y == vP2.y && vP.z == vP2.z
+                            && vN.x == vN2.x && vN.y == vN2.y && vN.z == vN2.z
+                            && vT.x == vT2.x && vT.y == vT2.y && vT.z == vT2.z) {
+                        bNotFound = false;
+                    } else {
+                        ++l2;
+                    }
+                }
+
+                // merge if previously found
+                if (!bNotFound) {
+                    piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
+                }
+            }
+        } else {
+            int iL = iL_in, iR = iR_in;
+            assert ((iR_in - iL_in) > 0);  // at least 2 entries
+
+            // separate (by fSep) all points between iL_in and iR_in in pTmpVert[]
+            while (iL < iR) {
+                boolean bReadyLeftSwap = false, bReadyRightSwap = false;
+                while ((!bReadyLeftSwap) && iL < iR) {
+                    assert (iL >= iL_in && iL <= iR_in);
+                    bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep);
+                    if (!bReadyLeftSwap) {
+                        ++iL;
+                    }
+                }
+                while ((!bReadyRightSwap) && iL < iR) {
+                    assert (iR >= iL_in && iR <= iR_in);
+                    bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
+                    if (!bReadyRightSwap) {
+                        --iR;
+                    }
+                }
+                assert ((iL < iR) || !(bReadyLeftSwap && bReadyRightSwap));
+
+                if (bReadyLeftSwap && bReadyRightSwap) {
+                    final TmpVert sTmp = pTmpVert[iL];
+                    assert (iL < iR);
+                    pTmpVert[iL] = pTmpVert[iR];
+                    pTmpVert[iR] = sTmp;
+                    ++iL;
+                    --iR;
+                }
+            }
+
+            assert (iL == (iR + 1) || (iL == iR));
+            if (iL == iR) {
+                final boolean bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
+                if (bReadyRightSwap) {
+                    ++iL;
+                } else {
+                    --iR;
+                }
+            }
+
+            // only need to weld when there is more than 1 instance of the (x,y,z)
+            if (iL_in < iR) {
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL_in, iR);  // weld all left of fSep
+            }
+            if (iL < iR_in) {
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL, iR_in);  // weld all right of (or equal to) fSep
+            }
+        }
+    }
+
+    //TODO Nehon: Used only if an array failed to be allocated... Can't happen in Java...
+    static void MergeVertsSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int pTable[], final int iEntries) {
+        // this can be optimized further using a tree structure or more hashing.
+        for (int e = 0; e < iEntries; e++) {
+            int i = pTable[e];
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final Vector3f vN = getNormal(mikkTSpace, index);
+            final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+            boolean bNotFound = true;
+            int e2 = 0, i2rec = -1;
+            while (e2 < e && bNotFound) {
+                final int i2 = pTable[e2];
+                final int index2 = piTriList_in_and_out[i2];
+                final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+                i2rec = i2;
+
+                if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) {
+                    bNotFound = false;
+                } else {
+                    ++e2;
+                }
+            }
+
+            // merge if previously found
+            if (!bNotFound) {
+                piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
+            }
+        }
+    }
+
+    //TODO Nehon : Not used...seemsit's used in the original version if the structure to store the data in the regular method failed...
+    static void generateSharedVerticesIndexListSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+        int iNumUniqueVerts = 0;
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            for (int i = 0; i < 3; i++) {
+                final int offs = t * 3 + i;
+                final int index = piTriList_in_and_out[offs];
+
+                final Vector3f vP = getPosition(mikkTSpace, index);
+                final Vector3f vN = getNormal(mikkTSpace, index);
+                final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+                boolean bFound = false;
+                int t2 = 0, index2rec = -1;
+                while (!bFound && t2 <= t) {
+                    int j = 0;
+                    while (!bFound && j < 3) {
+                        final int index2 = piTriList_in_and_out[t2 * 3 + j];
+                        final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                        final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                        final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+
+                        if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) {
+                            bFound = true;
+                        } else {
+                            ++j;
+                        }
+                    }
+                    if (!bFound) {
+                        ++t2;
+                    }
+                }
+
+                assert (bFound);
+                // if we found our own
+                if (index2rec == index) {
+                    ++iNumUniqueVerts;
+                }
+
+                piTriList_in_and_out[offs] = index2rec;
+            }
+        }
+    }
+
+    static int generateInitialVerticesIndexList(TriInfo pTriInfos[], int piTriList_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+        int iTSpacesOffs = 0;
+        int iDstTriIndex = 0;
+        for (int f = 0; f < mikkTSpace.getNumFaces(); f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts != 3 && verts != 4) {
+                continue;
+            }
+
+            //TODO nehon : clean this, have a local TrinInfo and assign it to pTriInfo[iDstTriIndex] at the end... and change those variables names...
+            pTriInfos[iDstTriIndex] = new TriInfo();
+            pTriInfos[iDstTriIndex].orgFaceNumber = f;
+            pTriInfos[iDstTriIndex].tSpacesOffs = iTSpacesOffs;
+
+            if (verts == 3) {
+                //TODO same here it should be easy once the local TriInfo is created.
+                byte[] pVerts = pTriInfos[iDstTriIndex].vertNum;
+                pVerts[0] = 0;
+                pVerts[1] = 1;
+                pVerts[2] = 2;
+                piTriList_out[iDstTriIndex * 3 + 0] = makeIndex(f, 0);
+                piTriList_out[iDstTriIndex * 3 + 1] = makeIndex(f, 1);
+                piTriList_out[iDstTriIndex * 3 + 2] = makeIndex(f, 2);
+                ++iDstTriIndex;  // next
+            } else {
+                //Note, Nehon: we should never get there with JME, because we don't support quads... 
+                //but I'm going to let it there incase someone needs it... Just know this code is not tested.
+                {//TODO remove those useless brackets...
+                    pTriInfos[iDstTriIndex + 1].orgFaceNumber = f;
+                    pTriInfos[iDstTriIndex + 1].tSpacesOffs = iTSpacesOffs;
+                }
+
+                {
+                    // need an order independent way to evaluate
+                    // tspace on quads. This is done by splitting
+                    // along the shortest diagonal.
+                    final int i0 = makeIndex(f, 0);
+                    final int i1 = makeIndex(f, 1);
+                    final int i2 = makeIndex(f, 2);
+                    final int i3 = makeIndex(f, 3);
+                    final Vector3f T0 = getTexCoord(mikkTSpace, i0);
+                    final Vector3f T1 = getTexCoord(mikkTSpace, i1);
+                    final Vector3f T2 = getTexCoord(mikkTSpace, i2);
+                    final Vector3f T3 = getTexCoord(mikkTSpace, i3);
+                    final float distSQ_02 = T2.subtract(T0).lengthSquared();
+                    final float distSQ_13 = T3.subtract(T1).lengthSquared();
+                    boolean bQuadDiagIs_02;
+                    if (distSQ_02 < distSQ_13) {
+                        bQuadDiagIs_02 = true;
+                    } else if (distSQ_13 < distSQ_02) {
+                        bQuadDiagIs_02 = false;
+                    } else {
+                        final Vector3f P0 = getPosition(mikkTSpace, i0);
+                        final Vector3f P1 = getPosition(mikkTSpace, i1);
+                        final Vector3f P2 = getPosition(mikkTSpace, i2);
+                        final Vector3f P3 = getPosition(mikkTSpace, i3);
+                        final float distSQ_022 = P2.subtract(P0).lengthSquared();
+                        final float distSQ_132 = P3.subtract(P1).lengthSquared();
+
+                        bQuadDiagIs_02 = distSQ_132 >= distSQ_022;
+                    }
+
+                    if (bQuadDiagIs_02) {
+                        {
+                            byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_A[0] = 0;
+                            pVerts_A[1] = 1;
+                            pVerts_A[2] = 2;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i2;
+                        ++iDstTriIndex;  // next
+                        {
+                            byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_B[0] = 0;
+                            pVerts_B[1] = 2;
+                            pVerts_B[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i2;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                    } else {
+                        {
+                            byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_A[0] = 0;
+                            pVerts_A[1] = 1;
+                            pVerts_A[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                        {
+                            byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_B[0] = 1;
+                            pVerts_B[1] = 2;
+                            pVerts_B[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i2;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                    }
+                }
+            }
+
+            iTSpacesOffs += verts;
+            assert (iDstTriIndex <= iNrTrianglesIn);
+        }
+
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            pTriInfos[t].flag = 0;
+        }
+
+        // return total amount of tspaces
+        return iTSpacesOffs;
+    }
+
+    static Vector3f getPosition(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] pos = new float[3];
+        indexToData(iF, iI, index);
+        mikkTSpace.getPosition(pos, iF[0], iI[0]);
+        return new Vector3f(pos[0], pos[1], pos[2]);
+    }
+
+    static Vector3f getNormal(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] norm = new float[3];
+        indexToData(iF, iI, index);
+        mikkTSpace.getNormal(norm, iF[0], iI[0]);
+        return new Vector3f(norm[0], norm[1], norm[2]);
+    }
+
+    static Vector3f getTexCoord(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] texc = new float[2];
+        indexToData(iF, iI, index);
+        mikkTSpace.getTexCoord(texc, iF[0], iI[0]);
+        return new Vector3f(texc[0], texc[1], 1.0f);
+    }
+
+    // returns the texture area times 2
+    static float calcTexArea(final MikkTSpaceContext mikkTSpace, final int indices[]) {
+        final Vector3f t1 = getTexCoord(mikkTSpace, indices[0]);
+        final Vector3f t2 = getTexCoord(mikkTSpace, indices[1]);
+        final Vector3f t3 = getTexCoord(mikkTSpace, indices[2]);
+
+        final float t21x = t2.x - t1.x;
+        final float t21y = t2.y - t1.y;
+        final float t31x = t3.x - t1.x;
+        final float t31y = t3.y - t1.y;
+
+        final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
+
+        return fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2;
+    }
+
+    private static boolean isNotZero(float v) {
+        return Math.abs(v) > 0;
+    }
+
+    static void initTriInfo(TriInfo pTriInfos[], final int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+
+        // pTriInfos[f].flag is cleared in GenerateInitialVerticesIndexList() which is called before this function.
+        // generate neighbor info list
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                pTriInfos[f].faceNeighbors[i] = -1;
+                pTriInfos[f].assignedGroup[i] = null;
+
+                pTriInfos[f].os.x = 0.0f;
+                pTriInfos[f].os.y = 0.0f;
+                pTriInfos[f].os.z = 0.0f;
+                pTriInfos[f].ot.x = 0.0f;
+                pTriInfos[f].ot.y = 0.0f;
+                pTriInfos[f].ot.z = 0.0f;
+                pTriInfos[f].magS = 0;
+                pTriInfos[f].magT = 0;
+
+                // assumed bad
+                pTriInfos[f].flag |= GROUP_WITH_ANY;
+            }
+        }
+
+        // evaluate first order derivatives
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            // initial values
+            final Vector3f v1 = getPosition(mikkTSpace, piTriListIn[f * 3 + 0]);
+            final Vector3f v2 = getPosition(mikkTSpace, piTriListIn[f * 3 + 1]);
+            final Vector3f v3 = getPosition(mikkTSpace, piTriListIn[f * 3 + 2]);
+            final Vector3f t1 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 0]);
+            final Vector3f t2 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 1]);
+            final Vector3f t3 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 2]);
+
+            final float t21x = t2.x - t1.x;
+            final float t21y = t2.y - t1.y;
+            final float t31x = t3.x - t1.x;
+            final float t31y = t3.y - t1.y;
+            final Vector3f d1 = v2.subtract(v1);
+            final Vector3f d2 = v3.subtract(v1);
+
+            final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
+            //assert(fSignedAreaSTx2!=0);
+            Vector3f vOs = d1.mult(t31y).subtract(d2.mult(t21y));  // eq 18
+            Vector3f vOt = d1.mult(-t31x).add(d2.mult(t21x));  // eq 19
+
+            pTriInfos[f].flag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0);
+
+            if (isNotZero(fSignedAreaSTx2)) {
+                final float fAbsArea = Math.abs(fSignedAreaSTx2);
+                final float fLenOs = vOs.length();
+                final float fLenOt = vOt.length();
+                final float fS = (pTriInfos[f].flag & ORIENT_PRESERVING) == 0 ? (-1.0f) : 1.0f;
+                if (isNotZero(fLenOs)) {
+                    pTriInfos[f].os = vOs.multLocal(fS / fLenOs);
+                }
+                if (isNotZero(fLenOt)) {
+                    pTriInfos[f].ot = vOt.multLocal(fS / fLenOt);
+                }
+
+                // evaluate magnitudes prior to normalization of vOs and vOt
+                pTriInfos[f].magS = fLenOs / fAbsArea;
+                pTriInfos[f].magT = fLenOt / fAbsArea;
+
+                // if this is a good triangle
+                if (isNotZero(pTriInfos[f].magS) && isNotZero(pTriInfos[f].magT)) {
+                    pTriInfos[f].flag &= (~GROUP_WITH_ANY);
+                }
+            }
+        }
+
+        // force otherwise healthy quads to a fixed orientation
+        int t = 0;
+        while (t < (iNrTrianglesIn - 1)) {
+            final int iFO_a = pTriInfos[t].orgFaceNumber;
+            final int iFO_b = pTriInfos[t + 1].orgFaceNumber;
+            if (iFO_a == iFO_b) {
+                // this is a quad
+                final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0;
+                final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0;
+
+                // bad triangles should already have been removed by
+                // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false
+                if ((bIsDeg_a || bIsDeg_b) == false) {
+                    final boolean bOrientA = (pTriInfos[t].flag & ORIENT_PRESERVING) != 0;
+                    final boolean bOrientB = (pTriInfos[t + 1].flag & ORIENT_PRESERVING) != 0;
+                    // if this happens the quad has extremely bad mapping!!
+                    if (bOrientA != bOrientB) {
+                        //printf("found quad with bad mapping\n");
+                        boolean bChooseOrientFirstTri = false;
+                        if ((pTriInfos[t + 1].flag & GROUP_WITH_ANY) != 0) {
+                            bChooseOrientFirstTri = true;
+                        } else if (calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, t * 3 + 0, t * 3 + 3)) >= calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, (t + 1) * 3 + 0, (t + 1) * 3 + 3))) {
+                            bChooseOrientFirstTri = true;
+                        }
+
+                        // force match
+                        {
+                            final int t0 = bChooseOrientFirstTri ? t : (t + 1);
+                            final int t1 = bChooseOrientFirstTri ? (t + 1) : t;
+                            pTriInfos[t1].flag &= (~ORIENT_PRESERVING);  // clear first
+                            pTriInfos[t1].flag |= (pTriInfos[t0].flag & ORIENT_PRESERVING);  // copy bit
+                        }
+                    }
+                }
+                t += 2;
+            } else {
+                ++t;
+            }
+        }
+
+        // match up edge pairs
+        {
+            //Edge * pEdges = (Edge *) malloc(sizeof(Edge)*iNrTrianglesIn*3);
+            Edge[] pEdges = new Edge[iNrTrianglesIn * 3];
+
+            //TODO nehon weird... original algorithm check if pEdges is null but it's just been allocated... weirder, it does soemthing different if the edges are null...
+            //    if (pEdges==null)
+            //      BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn);
+            //    else
+            //    {
+            buildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn);
+
+            //    }
+        }
+    }
+
+    static int build4RuleGroups(TriInfo pTriInfos[], Group pGroups[], int piGroupTrianglesBuffer[], final int piTriListIn[], final int iNrTrianglesIn) {
+        final int iNrMaxGroups = iNrTrianglesIn * 3;
+        int iNrActiveGroups = 0;
+        int iOffset = 0;
+        // (void)iNrMaxGroups;  /* quiet warnings in non debug mode */
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                // if not assigned to a group
+                if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0 && pTriInfos[f].assignedGroup[i] == null) {
+                    boolean bOrPre;                    
+                    final int vert_index = piTriListIn[f * 3 + i];
+                    assert (iNrActiveGroups < iNrMaxGroups);
+                    pTriInfos[f].assignedGroup[i] = new Group(); 
+                    pGroups[iNrActiveGroups] = pTriInfos[f].assignedGroup[i];
+                    pTriInfos[f].assignedGroup[i].vertexRepresentitive = vert_index;
+                    pTriInfos[f].assignedGroup[i].orientPreservering = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0;
+                    pTriInfos[f].assignedGroup[i].nrFaces = 0;
+                    
+                    ++iNrActiveGroups;
+
+                    addTriToGroup(pTriInfos[f].assignedGroup[i], f);
+                    bOrPre = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0;
+                    int neigh_indexL = pTriInfos[f].faceNeighbors[i];
+                    int neigh_indexR = pTriInfos[f].faceNeighbors[i > 0 ? (i - 1) : 2];
+                    if (neigh_indexL >= 0) {
+                        // neighbor
+                        final boolean bAnswer
+                                = assignRecur(piTriListIn, pTriInfos, neigh_indexL,
+                                        pTriInfos[f].assignedGroup[i]);
+
+                        final boolean bOrPre2 = (pTriInfos[neigh_indexL].flag & ORIENT_PRESERVING) != 0;
+                        final boolean bDiff = bOrPre != bOrPre2;
+                        assert (bAnswer || bDiff);
+                        //(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */
+                    }
+                    if (neigh_indexR >= 0) {
+                        // neighbor
+                        final boolean bAnswer
+                                = assignRecur(piTriListIn, pTriInfos, neigh_indexR,
+                                        pTriInfos[f].assignedGroup[i]);
+
+                        final boolean bOrPre2 = (pTriInfos[neigh_indexR].flag & ORIENT_PRESERVING) != 0;
+                        final boolean bDiff = bOrPre != bOrPre2;
+                        assert (bAnswer || bDiff);
+                        //(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */
+                    }
+
+                    int[] faceIndices = new int[pTriInfos[f].assignedGroup[i].nrFaces];
+                    //pTriInfos[f].assignedGroup[i].faceIndices.toArray(faceIndices);
+                    for (int j = 0; j < faceIndices.length; j++) {
+                        faceIndices[j] = pTriInfos[f].assignedGroup[i].faceIndices.get(j);
+                    }
+                    
+                    //Nehon: copy back the faceIndices data into the groupTriangleBuffer.
+                    System.arraycopy( faceIndices, 0, piGroupTrianglesBuffer, iOffset, pTriInfos[f].assignedGroup[i].nrFaces);
+                    // update offset
+                    iOffset += pTriInfos[f].assignedGroup[i].nrFaces;
+                    // since the groups are disjoint a triangle can never
+                    // belong to more than 3 groups. Subsequently something
+                    // is completely screwed if this assertion ever hits.
+                    assert (iOffset <= iNrMaxGroups);
+                }
+            }
+        }
+
+        return iNrActiveGroups;
+    }
+
+    static void addTriToGroup(Group group, final int triIndex) {
+        //group.faceIndices[group.nrFaces] = triIndex;
+        group.faceIndices.add(triIndex);
+        ++group.nrFaces;
+    }
+
+    static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final int iMyTriIndex, Group pGroup) {
+        TriInfo pMyTriInfo = psTriInfos[iMyTriIndex];
+
+        // track down vertex
+        final int iVertRep = pGroup.vertexRepresentitive;
+        int index = 3 * iMyTriIndex;
+        int i = -1;
+        if (piTriListIn[index] == iVertRep) {
+            i = 0;
+        } else if (piTriListIn[index + 1] == iVertRep) {
+            i = 1;
+        } else if (piTriListIn[index + 2] == iVertRep) {
+            i = 2;
+        }
+        assert (i >= 0 && i < 3);
+
+        // early out
+        if (pMyTriInfo.assignedGroup[i] == pGroup) {
+            return true;
+        } else if (pMyTriInfo.assignedGroup[i] != null) {
+            return false;
+        }
+        if ((pMyTriInfo.flag & GROUP_WITH_ANY) != 0) {
+            // first to group with a group-with-anything triangle
+            // determines it's orientation.
+            // This is the only existing order dependency in the code!!
+            if (pMyTriInfo.assignedGroup[0] == null
+                    && pMyTriInfo.assignedGroup[1] == null
+                    && pMyTriInfo.assignedGroup[2] == null) {
+                pMyTriInfo.flag &= (~ORIENT_PRESERVING);
+                pMyTriInfo.flag |= (pGroup.orientPreservering ? ORIENT_PRESERVING : 0);
+            }
+        }
+        {
+            final boolean bOrient = (pMyTriInfo.flag & ORIENT_PRESERVING) != 0;
+            if (bOrient != pGroup.orientPreservering) {
+                return false;
+            }
+        }
+
+        addTriToGroup(pGroup, iMyTriIndex);
+        pMyTriInfo.assignedGroup[i] = pGroup;
+
+        {
+            final int neigh_indexL = pMyTriInfo.faceNeighbors[i];
+            final int neigh_indexR = pMyTriInfo.faceNeighbors[i > 0 ? (i - 1) : 2];
+            if (neigh_indexL >= 0) {
+                assignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);
+            }
+            if (neigh_indexR >= 0) {
+                assignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup);
+            }
+        }
+
+        return true;
+    }
+
+    static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], final Group pGroups[],
+            final int iNrActiveGroups, final int piTriListIn[], final float fThresCos,
+            final MikkTSpaceContext mikkTSpace) {
+        TSpace[] pSubGroupTspace;
+        SubGroup[] pUniSubGroups;
+        int[] pTmpMembers;
+        int iMaxNrFaces = 0, iUniqueTspaces = 0, g = 0, i = 0;
+        for (g = 0; g < iNrActiveGroups; g++) {
+            if (iMaxNrFaces < pGroups[g].nrFaces) {
+                iMaxNrFaces = pGroups[g].nrFaces;
+            }
+        }
+
+        if (iMaxNrFaces == 0) {
+            return true;
+        }
+
+        // make initial allocations
+        pSubGroupTspace = new TSpace[iMaxNrFaces];
+        pUniSubGroups = new SubGroup[iMaxNrFaces];
+        pTmpMembers = new int[iMaxNrFaces];
+
+
+        iUniqueTspaces = 0;
+        for (g = 0; g < iNrActiveGroups; g++) {
+            final Group pGroup = pGroups[g];
+            int iUniqueSubGroups = 0, s = 0;
+
+            for (i = 0; i < pGroup.nrFaces; i++) // triangles
+            {
+                final int f = pGroup.faceIndices.get(i);  // triangle number
+                int index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0;
+                SubGroup tmp_group = new SubGroup();
+                boolean bFound;
+                Vector3f n, vOs, vOt;
+                if (pTriInfos[f].assignedGroup[0] == pGroup) {
+                    index = 0;
+                } else if (pTriInfos[f].assignedGroup[1] == pGroup) {
+                    index = 1;
+                } else if (pTriInfos[f].assignedGroup[2] == pGroup) {
+                    index = 2;
+                }
+                assert (index >= 0 && index < 3);
+
+                iVertIndex = piTriListIn[f * 3 + index];
+                assert (iVertIndex == pGroup.vertexRepresentitive);
+
+                // is normalized already
+                n = getNormal(mikkTSpace, iVertIndex);
+
+                // project
+                vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os)));
+                vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot)));
+                vOs.normalizeLocal();
+                vOt.normalizeLocal();
+
+                // original face number
+                iOF_1 = pTriInfos[f].orgFaceNumber;
+
+                iMembers = 0;
+                for (j = 0; j < pGroup.nrFaces; j++) {
+                    final int t = pGroup.faceIndices.get(j);  // triangle number
+                    final int iOF_2 = pTriInfos[t].orgFaceNumber;
+
+                    // project
+                    Vector3f vOs2 = pTriInfos[t].os.subtract(n.mult(n.dot(pTriInfos[t].os)));
+                    Vector3f vOt2 = pTriInfos[t].ot.subtract(n.mult(n.dot(pTriInfos[t].ot)));
+                    vOs2.normalizeLocal();
+                    vOt2.normalizeLocal();
+
+                    {
+                        final boolean bAny = ((pTriInfos[f].flag | pTriInfos[t].flag) & GROUP_WITH_ANY) != 0;
+                        // make sure triangles which belong to the same quad are joined.
+                        final boolean bSameOrgFace = iOF_1 == iOF_2;
+
+                        final float fCosS = vOs.dot(vOs2);
+                        final float fCosT = vOt.dot(vOt2);
+
+                        assert (f != t || bSameOrgFace);  // sanity check
+                        if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) {
+                            pTmpMembers[iMembers++] = t;
+                        }
+                    }
+                }
+
+                // sort pTmpMembers
+                tmp_group.nrFaces = iMembers;
+                tmp_group.triMembers = pTmpMembers;
+                if (iMembers > 1) {
+                    quickSort(pTmpMembers, 0, iMembers - 1, INTERNAL_RND_SORT_SEED);
+                }
+
+                // look for an existing match
+                bFound = false;
+                l = 0;
+                while (l < iUniqueSubGroups && !bFound) {
+                    bFound = compareSubGroups(tmp_group, pUniSubGroups[l]);
+                    if (!bFound) {
+                        ++l;
+                    }
+                }
+
+                // assign tangent space index
+                assert (bFound || l == iUniqueSubGroups);
+                //piTempTangIndices[f*3+index] = iUniqueTspaces+l;
+
+                // if no match was found we allocate a new subgroup
+                if (!bFound) {
+                    // insert new subgroup
+                    int[] pIndices = new int[iMembers];
+                    pUniSubGroups[iUniqueSubGroups] = new SubGroup();
+                    pUniSubGroups[iUniqueSubGroups].nrFaces = iMembers;
+                    pUniSubGroups[iUniqueSubGroups].triMembers = pIndices;
+                    System.arraycopy(tmp_group.triMembers, 0, pIndices, 0, iMembers);
+                    //memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int));
+                    pSubGroupTspace[iUniqueSubGroups]
+                            = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentitive);
+                    ++iUniqueSubGroups;
+                }
+
+                // output tspace
+                {
+                    final int iOffs = pTriInfos[f].tSpacesOffs;
+                    final int iVert = pTriInfos[f].vertNum[index];
+                    TSpace pTS_out = psTspace[iOffs + iVert];
+                    assert (pTS_out.counter < 2);
+                    assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientPreservering);
+                    if (pTS_out.counter == 1) {
+                        pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l]));
+                        pTS_out.counter = 2;  // update counter
+                        pTS_out.orient = pGroup.orientPreservering;
+                    } else {
+                        assert (pTS_out.counter == 0);
+                        pTS_out.set(pSubGroupTspace[l]);
+                        pTS_out.counter = 1;  // update counter
+                        pTS_out.orient = pGroup.orientPreservering;
+                    }
+                }
+            }
+
+            iUniqueTspaces += iUniqueSubGroups;
+        }
+
+        return true;
+    }
+
+    static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriListIn[], final TriInfo pTriInfos[],
+            final MikkTSpaceContext mikkTSpace, final int iVertexRepresentitive) {
+        TSpace res = new TSpace();
+        float fAngleSum = 0;        
+
+        for (int face = 0; face < iFaces; face++) {
+            final int f = face_indices[face];
+
+            // only valid triangles get to add their contribution
+            if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0) {
+                
+                int i = -1;
+                if (piTriListIn[3 * f + 0] == iVertexRepresentitive) {
+                    i = 0;
+                } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) {
+                    i = 1;
+                } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) {
+                    i = 2;
+                }
+                assert (i >= 0 && i < 3);
+
+                // project
+                int index = piTriListIn[3 * f + i];
+                Vector3f n = getNormal(mikkTSpace, index);
+                Vector3f vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os)));
+                Vector3f vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot)));
+                vOs.normalizeLocal();
+                vOt.normalizeLocal();
+
+                int i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)];
+                int i1 = piTriListIn[3 * f + i];
+                int i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)];
+
+                Vector3f p0 = getPosition(mikkTSpace, i0);
+                Vector3f p1 = getPosition(mikkTSpace, i1);
+                Vector3f  p2 = getPosition(mikkTSpace, i2);
+                Vector3f v1 = p0.subtract(p1);
+                Vector3f v2 = p2.subtract(p1);
+
+                // project
+                v1.subtractLocal(n.mult(n.dot(v1))).normalizeLocal();
+                v2.subtractLocal(n.mult(n.dot(v2))).normalizeLocal();
+
+                // weight contribution by the angle
+                // between the two edge vectors
+                float fCos = v1.dot(v2);
+                fCos = fCos > 1 ? 1 : (fCos < (-1) ? (-1) : fCos);
+                float fAngle = (float) Math.acos(fCos);
+                float fMagS = pTriInfos[f].magS;
+                float fMagT = pTriInfos[f].magT;
+
+                res.os.addLocal(vOs.multLocal(fAngle));
+                res.ot.addLocal(vOt.multLocal(fAngle));
+                res.magS += (fAngle * fMagS);
+                res.magT += (fAngle * fMagT);
+                fAngleSum += fAngle;
+            }
+        }
+
+        // normalize
+        res.os.normalizeLocal();
+        res.ot.normalizeLocal();
+
+        if (fAngleSum > 0) {
+            res.magS /= fAngleSum;
+            res.magT /= fAngleSum;
+        }
+
+        return res;
+    }
+
+    static boolean compareSubGroups(final SubGroup pg1, final SubGroup pg2) {
+        if(pg2 == null || (pg1.nrFaces != pg2.nrFaces)){
+            return false;
+        }
+        boolean stillSame = true;
+        int i = 0;        
+        while (i < pg1.nrFaces && stillSame) {
+            stillSame = pg1.triMembers[i] == pg2.triMembers[i];
+            if (stillSame) {
+                ++i;
+            }
+        }
+        return stillSame;
+    }
+
+    static void quickSort(int[] pSortBuffer, int iLeft, int iRight, long uSeed) {
+        int iL, iR, n, index, iMid, iTmp;
+
+        // Random
+        long t = uSeed & 31;
+        t = (uSeed << t) | (uSeed >> (32 - t));
+        uSeed = uSeed + t + 3;
+        // Random end
+        uSeed = uSeed & 0xffffffffL;
+
+        iL = iLeft;
+        iR = iRight;
+        n = (iR - iL) + 1;
+        assert (n >= 0);
+        index = (int) ((uSeed & 0xffffffffL) % n);
+
+        iMid = pSortBuffer[index + iL];
+
+        do {
+            while (pSortBuffer[iL] < iMid) {
+                ++iL;
+            }
+            while (pSortBuffer[iR] > iMid) {
+                --iR;
+            }
+
+            if (iL <= iR) {
+                iTmp = pSortBuffer[iL];
+                pSortBuffer[iL] = pSortBuffer[iR];
+                pSortBuffer[iR] = iTmp;
+                ++iL;
+                --iR;
+            }
+        } while (iL <= iR);
+
+        if (iLeft < iR) {
+            quickSort(pSortBuffer, iLeft, iR, uSeed);
+        }
+        if (iL < iRight) {
+            quickSort(pSortBuffer, iL, iRight, uSeed);
+        }
+    }
+
+    static void buildNeighborsFast(TriInfo pTriInfos[], Edge[] pEdges, final int piTriListIn[], final int iNrTrianglesIn) {
+        // build array of edges
+        long uSeed = INTERNAL_RND_SORT_SEED;        // could replace with a random seed?
+        
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                final int i0 = piTriListIn[f * 3 + i];
+                final int i1 = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
+                pEdges[f * 3 + i] = new Edge();
+                pEdges[f * 3 + i].setI0(i0 < i1 ? i0 : i1);      // put minimum index in i0
+                pEdges[f * 3 + i].setI1(!(i0 < i1) ? i0 : i1);    // put maximum index in i1
+                pEdges[f * 3 + i].setF(f);              // record face number
+            }
+        }
+
+        // sort over all edges by i0, this is the pricy one.
+        quickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed);  // sort channel 0 which is i0
+
+        // sub sort over i1, should be fast.
+        // could replace this with a 64 bit int sort over (i0,i1)
+        // with i0 as msb in the quicksort call above.
+        int iEntries = iNrTrianglesIn * 3;
+        int iCurStartIndex = 0;
+        for (int i = 1; i < iEntries; i++) {
+            if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0()) {
+                final int iL = iCurStartIndex;
+                final int iR = i - 1;
+                //final int iElems = i-iL;
+                iCurStartIndex = i;
+                quickSortEdges(pEdges, iL, iR, 1, uSeed);  // sort channel 1 which is i1
+            }
+        }
+
+        // sub sort over f, which should be fast.
+        // this step is to remain compliant with BuildNeighborsSlow() when
+        // more than 2 triangles use the same edge (such as a butterfly topology).
+        iCurStartIndex = 0;
+        for (int i = 1; i < iEntries; i++) {
+            if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0() || pEdges[iCurStartIndex].getI1() != pEdges[i].getI1()) {
+                final int iL = iCurStartIndex;
+                final int iR = i - 1;
+                //final int iElems = i-iL;
+                iCurStartIndex = i;
+                quickSortEdges(pEdges, iL, iR, 2, uSeed);  // sort channel 2 which is f
+            }
+        }
+
+        // pair up, adjacent triangles
+        for (int i = 0; i < iEntries; i++) {
+            final int i0 = pEdges[i].getI0();
+            final int i1 = pEdges[i].getI1();
+            final int g = pEdges[i].getF();
+            boolean bUnassigned_A;
+
+            int[] i0_A = new int[1];
+            int[] i1_A = new int[1];
+            int[] edgenum_A = new int[1];
+            int[] edgenum_B = new int[1];
+            //int edgenum_B=0;  // 0,1 or 2
+            int[] triList = new int[3];
+            System.arraycopy(piTriListIn, g * 3, triList, 0, 3);
+            getEdge(i0_A, i1_A, edgenum_A, triList, i0, i1);  // resolve index ordering and edge_num
+            bUnassigned_A = pTriInfos[g].faceNeighbors[edgenum_A[0]] == -1;
+
+            if (bUnassigned_A) {
+                // get true index ordering
+                int j = i + 1, t;
+                boolean bNotFound = true;
+                while (j < iEntries && i0 == pEdges[j].getI0() && i1 == pEdges[j].getI1() && bNotFound) {
+                    boolean bUnassigned_B;
+                    int[] i0_B = new int[1];
+                    int[] i1_B = new int[1];
+                    t = pEdges[j].getF();
+                    // flip i0_B and i1_B
+                    System.arraycopy(piTriListIn, t * 3, triList, 0, 3);
+                    getEdge(i1_B, i0_B, edgenum_B, triList, pEdges[j].getI0(), pEdges[j].getI1());  // resolve index ordering and edge_num
+                    //assert(!(i0_A==i1_B && i1_A==i0_B));
+                    bUnassigned_B = pTriInfos[t].faceNeighbors[edgenum_B[0]] == -1;
+                    if (i0_A[0] == i0_B[0] && i1_A[0] == i1_B[0] && bUnassigned_B) {
+                        bNotFound = false;
+                    } else {
+                        ++j;
+                    }
+                }
+
+                if (!bNotFound) {
+                    int t2 = pEdges[j].getF();
+                    pTriInfos[g].faceNeighbors[edgenum_A[0]] = t2;
+                    //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1);
+                    pTriInfos[t2].faceNeighbors[edgenum_B[0]] = g;
+                }
+            }
+        }
+    }
+
+    static void buildNeighborsSlow(TriInfo pTriInfos[], final int piTriListIn[], final int iNrTrianglesIn) {
+        
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                // if unassigned
+                if (pTriInfos[f].faceNeighbors[i] == -1) {
+                    final int i0_A = piTriListIn[f * 3 + i];
+                    final int i1_A = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
+
+                    // search for a neighbor
+                    boolean bFound = false;
+                    int t = 0, j = 0;
+                    while (!bFound && t < iNrTrianglesIn) {
+                        if (t != f) {
+                            j = 0;
+                            while (!bFound && j < 3) {
+                                // in rev order
+                                final int i1_B = piTriListIn[t * 3 + j];
+                                final int i0_B = piTriListIn[t * 3 + (j < 2 ? (j + 1) : 0)];
+                                //assert(!(i0_A==i1_B && i1_A==i0_B));
+                                if (i0_A == i0_B && i1_A == i1_B) {
+                                    bFound = true;
+                                } else {
+                                    ++j;
+                                }
+                            }
+                        }
+
+                        if (!bFound) {
+                            ++t;
+                        }
+                    }
+
+                    // assign neighbors
+                    if (bFound) {
+                        pTriInfos[f].faceNeighbors[i] = t;
+                        //assert(pTriInfos[t].FaceNeighbors[j]==-1);
+                        pTriInfos[t].faceNeighbors[j] = f;
+                    }
+                }
+            }
+        }
+    }
+
+    static void quickSortEdges(Edge[] pSortBuffer, int iLeft, int iRight, final int channel, long uSeed) {
+        // early out
+        Edge sTmp;
+        final int iElems = iRight - iLeft + 1;
+        if (iElems < 2) {
+            return;
+        } else if (iElems == 2) {
+            if (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel]) {
+                sTmp = pSortBuffer[iLeft];
+                pSortBuffer[iLeft] = pSortBuffer[iRight];
+                pSortBuffer[iRight] = sTmp;
+            }
+            return;
+        }
+
+        // Random
+        long t = uSeed & 31;
+        t = (uSeed << t) | (uSeed >> (32 - t));
+        uSeed = uSeed + t + 3;
+        // Random end
+        
+        uSeed = uSeed & 0xffffffffL;
+
+        int iL = iLeft;
+        int iR = iRight;
+        int n = (iR - iL) + 1;
+        assert (n >= 0);
+        int index = (int) (uSeed % n);
+
+        int iMid = pSortBuffer[index + iL].array[channel];
+
+        do {
+            while (pSortBuffer[iL].array[channel] < iMid) {
+                ++iL;
+            }
+            while (pSortBuffer[iR].array[channel] > iMid) {
+                --iR;
+            }
+
+            if (iL <= iR) {
+                sTmp = pSortBuffer[iL];
+                pSortBuffer[iL] = pSortBuffer[iR];
+                pSortBuffer[iR] = sTmp;
+                ++iL;
+                --iR;
+            }
+        } while (iL <= iR);
+
+        if (iLeft < iR) {
+            quickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed);
+        }
+        if (iL < iRight) {
+            quickSortEdges(pSortBuffer, iL, iRight, channel, uSeed);
+        }
+    }
+
+// resolve ordering and edge number
+    static void getEdge(int[] i0_out, int[] i1_out, int[] edgenum_out, final int[] indices, final int i0_in, final int i1_in) {
+        edgenum_out[0] = -1;
+
+        // test if first index is on the edge
+        if (indices[0] == i0_in || indices[0] == i1_in) {
+            // test if second index is on the edge
+            if (indices[1] == i0_in || indices[1] == i1_in) {
+                edgenum_out[0] = 0;  // first edge
+                i0_out[0] = indices[0];
+                i1_out[0] = indices[1];
+            } else {
+                edgenum_out[0] = 2;  // third edge
+                i0_out[0] = indices[2];
+                i1_out[0] = indices[0];
+            }
+        } else {
+            // only second and third index is on the edge
+            edgenum_out[0] = 1;  // second edge
+            i0_out[0] = indices[1];
+            i1_out[0] = indices[2];
+        }
+    }
+
+    static void degenPrologue(TriInfo pTriInfos[], int piTriList_out[], final int iNrTrianglesIn, final int iTotTris) {
+        
+        // locate quads with only one good triangle
+        int t = 0;
+        while (t < (iTotTris - 1)) {
+            final int iFO_a = pTriInfos[t].orgFaceNumber;
+            final int iFO_b = pTriInfos[t + 1].orgFaceNumber;
+            if (iFO_a == iFO_b) {
+                // this is a quad
+                final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0;
+                final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0;
+                //TODO nehon : Check this in detail as this operation is utterly strange
+                if ((bIsDeg_a ^ bIsDeg_b) != false) {
+                    pTriInfos[t].flag |= QUAD_ONE_DEGEN_TRI;
+                    pTriInfos[t + 1].flag |= QUAD_ONE_DEGEN_TRI;
+                }
+                t += 2;
+            } else {
+                ++t;
+            }
+        }
+
+        // reorder list so all degen triangles are moved to the back
+        // without reordering the good triangles
+        int iNextGoodTriangleSearchIndex = 1;
+        t = 0;
+        boolean bStillFindingGoodOnes = true;
+        while (t < iNrTrianglesIn && bStillFindingGoodOnes) {
+            final boolean bIsGood = (pTriInfos[t].flag & MARK_DEGENERATE) == 0;
+            if (bIsGood) {
+                if (iNextGoodTriangleSearchIndex < (t + 2)) {
+                    iNextGoodTriangleSearchIndex = t + 2;
+                }
+            } else {                
+                // search for the first good triangle.
+                boolean bJustADegenerate = true;
+                while (bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris) {
+                    final boolean bIsGood2 = (pTriInfos[iNextGoodTriangleSearchIndex].flag & MARK_DEGENERATE) == 0;
+                    if (bIsGood2) {
+                        bJustADegenerate = false;
+                    } else {
+                        ++iNextGoodTriangleSearchIndex;
+                    }
+                }
+
+                int t0 = t;
+                int t1 = iNextGoodTriangleSearchIndex;
+                ++iNextGoodTriangleSearchIndex;
+                assert (iNextGoodTriangleSearchIndex > (t + 1));
+
+                // swap triangle t0 and t1
+                if (!bJustADegenerate) {                    
+                    for (int i = 0; i < 3; i++) {
+                        final int index = piTriList_out[t0 * 3 + i];
+                        piTriList_out[t0 * 3 + i] = piTriList_out[t1 * 3 + i];
+                        piTriList_out[t1 * 3 + i] = index;
+                    }
+                    {
+                        final TriInfo tri_info = pTriInfos[t0];
+                        pTriInfos[t0] = pTriInfos[t1];
+                        pTriInfos[t1] = tri_info;
+                    }
+                } else {
+                    bStillFindingGoodOnes = false;  // this is not supposed to happen
+                }
+            }
+
+            if (bStillFindingGoodOnes) {
+                ++t;
+            }
+        }
+
+        assert (bStillFindingGoodOnes);  // code will still work.
+        assert (iNrTrianglesIn == t);
+    }
+
+    static void DegenEpilogue(TSpace psTspace[], TriInfo pTriInfos[], int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn, final int iTotTris) {
+        
+        // deal with degenerate triangles
+        // punishment for degenerate triangles is O(N^2)
+        for (int t = iNrTrianglesIn; t < iTotTris; t++) {
+            // degenerate triangles on a quad with one good triangle are skipped
+            // here but processed in the next loop
+            final boolean bSkip = (pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0;
+
+            if (!bSkip) {
+                for (int i = 0; i < 3; i++) {
+                    final int index1 = piTriListIn[t * 3 + i];
+                    // search through the good triangles
+                    boolean bNotFound = true;
+                    int j = 0;
+                    while (bNotFound && j < (3 * iNrTrianglesIn)) {
+                        final int index2 = piTriListIn[j];
+                        if (index1 == index2) {
+                            bNotFound = false;
+                        } else {
+                            ++j;
+                        }
+                    }
+
+                    if (!bNotFound) {
+                        final int iTri = j / 3;
+                        final int iVert = j % 3;
+                        final int iSrcVert = pTriInfos[iTri].vertNum[iVert];
+                        final int iSrcOffs = pTriInfos[iTri].tSpacesOffs;
+                        final int iDstVert = pTriInfos[t].vertNum[i];
+                        final int iDstOffs = pTriInfos[t].tSpacesOffs;
+
+                        // copy tspace
+                        psTspace[iDstOffs + iDstVert] = psTspace[iSrcOffs + iSrcVert];
+                    }
+                }
+            }
+        }
+
+        // deal with degenerate quads with one good triangle
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            // this triangle belongs to a quad where the
+            // other triangle is degenerate
+            if ((pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0) {
+               
+                byte[] pV = pTriInfos[t].vertNum;
+                int iFlag = (1 << pV[0]) | (1 << pV[1]) | (1 << pV[2]);
+                int iMissingIndex = 0;
+                if ((iFlag & 2) == 0) {
+                    iMissingIndex = 1;
+                } else if ((iFlag & 4) == 0) {
+                    iMissingIndex = 2;
+                } else if ((iFlag & 8) == 0) {
+                    iMissingIndex = 3;
+                }
+
+                int iOrgF = pTriInfos[t].orgFaceNumber;
+                Vector3f vDstP = getPosition(mikkTSpace, makeIndex(iOrgF, iMissingIndex));
+                boolean bNotFound = true;
+                int i = 0;
+                while (bNotFound && i < 3) {
+                    final int iVert = pV[i];
+                    final Vector3f vSrcP = getPosition(mikkTSpace, makeIndex(iOrgF, iVert));
+                    if (vSrcP.equals(vDstP)) {
+                        final int iOffs = pTriInfos[t].tSpacesOffs;
+                        psTspace[iOffs + iMissingIndex] = psTspace[iOffs + iVert];
+                        bNotFound = false;
+                    } else {
+                        ++i;
+                    }
+                }
+                assert (!bNotFound);
+            }
+        }
+
+    }    
+
+    /**
+     * SubGroup inner class
+     */
+    private static class SubGroup {
+        int nrFaces;
+        int[] triMembers;
+    }
+
+    private static class Group {
+        int nrFaces;
+        List<Integer> faceIndices = new ArrayList<Integer>();
+        int vertexRepresentitive;
+        boolean orientPreservering;
+    }
+
+    private static class TriInfo {
+
+        int[] faceNeighbors = new int[3];
+        Group[] assignedGroup = new Group[3];
+
+        // normalized first order face derivatives
+        Vector3f os = new Vector3f();
+        Vector3f ot = new Vector3f();
+        float magS, magT;  // original magnitudes
+
+        // determines if the current and the next triangle are a quad.
+        int orgFaceNumber;
+        int flag, tSpacesOffs;
+        byte[] vertNum = new byte[4];
+    }
+
+    private static class TSpace {
+
+        Vector3f os = new Vector3f();
+        float magS;
+        Vector3f ot = new Vector3f();
+        float magT;
+        int counter;  // this is to average back into quads.
+        boolean orient;
+        
+        void set(TSpace ts){
+            os.set(ts.os);
+            magS = ts.magS;
+            ot.set(ts.ot);
+            magT = ts.magT;
+            counter = ts.counter;
+            orient = ts.orient;
+        }
+    }
+
+    private static class TmpVert {
+
+        float vert[] = new float[3];
+        int index;
+    }
+
+    private static class Edge {
+
+        void setI0(int i){            
+            array[0] = i;
+        }
+        
+        void setI1(int i){            
+            array[1] = i;
+        }
+        
+        void setF(int i){            
+            array[2] = i;
+        }
+        
+        int getI0(){            
+            return array[0];
+        }
+        
+        int getI1(){            
+            return array[1];
+        }
+        
+        int getF(){            
+            return array[2];
+        }
+        
+        int[] array = new int[3];
+    }
+
+}

+ 4 - 3
jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert

@@ -32,11 +32,12 @@ void main(){
     #ifdef POINT_SPRITE
         vec4 worldPos = g_WorldMatrix * pos;
         float d = distance(g_CameraPosition.xyz, worldPos.xyz);
-        gl_PointSize = max(1.0, (inSize * SIZE_MULTIPLIER * m_Quadratic) / d);
+        float size = (inSize * SIZE_MULTIPLIER * m_Quadratic) / d;
+        gl_PointSize = max(1.0, size);
 
         //vec4 worldViewPos = g_WorldViewMatrix * pos;
         //gl_PointSize = (inSize * SIZE_MULTIPLIER * m_Quadratic)*100.0 / worldViewPos.z;
 
-        color.a *= min(gl_PointSize, 1.0);
+        color.a *= min(size, 1.0);
     #endif
-}
+}

+ 19 - 0
jme3-core/src/main/resources/joystick-mapping.properties

@@ -95,6 +95,25 @@ Gamepad\ F310\ (Controller).ry=rz
 # keeps it from confusing the .rx mapping.
 Gamepad\ F310\ (Controller).z=trigger
 
+# Logitech F310 gamepad with dip switch XInput for Windows 10
+Controller\ (Gamepad\ F310).0=2
+Controller\ (Gamepad\ F310).1=1
+Controller\ (Gamepad\ F310).2=3
+Controller\ (Gamepad\ F310).3=0
+
+Controller\ (Gamepad\ F310).6=8
+Controller\ (Gamepad\ F310).7=9
+
+Controller\ (Gamepad\ F310).8=10
+Controller\ (Gamepad\ F310).9=11
+
+Controller\ (Gamepad\ F310).rx=z
+Controller\ (Gamepad\ F310).ry=rz
+
+# requires custom code to support trigger buttons but this
+# keeps it from confusing the .rx mapping.
+Controller\ (Gamepad\ F310).z=trigger
+
 # Alternate version of the XBOX 360 controller
 XBOX\ 360\ For\ Windows\ (Controller).0=2
 XBOX\ 360\ For\ Windows\ (Controller).1=1

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

@@ -155,7 +155,7 @@ public class TextureAtlas {
                 return false;
             } else {
                 if (normal != null && normal.getKey() != null) {
-                    addTexture(diffuse, "NormalMap", keyName);
+                    addTexture(normal, "NormalMap", keyName);
                 }
                 if (specular != null && specular.getKey() != null) {
                     addTexture(specular, "SpecularMap", keyName);

+ 1 - 1
jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java

@@ -360,7 +360,7 @@ public final class SettingsDialog extends JFrame {
         vsyncBox.setSelected(source.isVSync());
         
         gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma"));
-        gammaBox.setSelected(source.getGammaCorrection());
+        gammaBox.setSelected(source.isGammaCorrection());
         
         gbc = new GridBagConstraints();
         gbc.weightx = 0.5;

+ 1 - 1
jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert

@@ -19,6 +19,6 @@ void main(void)
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos,modelSpaceNormals);
    #endif
-   normal = normalize(g_NormalMatrix * modelSpaceNormals);
+   normal = normalize(TransformNormal(modelSpaceNormals));
    gl_Position = TransformWorldViewProjection(modelSpacePos);
 }

+ 9 - 5
jme3-examples/build.gradle

@@ -5,11 +5,15 @@ if (!hasProperty('mainClass')) {
 }
 
 task run(dependsOn: 'build', type:JavaExec) {
-   main = mainClass
-   classpath = sourceSets.main.runtimeClasspath   
-   if( assertions  == "true" ){
-       enableAssertions = true;
-   }
+    main = mainClass
+    classpath = sourceSets.main.runtimeClasspath
+    if (System.properties['os.name'].toLowerCase().contains('mac')) {
+        jvmArgs "-XstartOnFirstThread"
+        jvmArgs "-Djava.awt.headless=true"
+    }
+    if( assertions  == "true" ){
+        enableAssertions = true;
+    }
 }
 
 dependencies {

+ 72 - 0
jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java

@@ -0,0 +1,72 @@
+package jme3test.app;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+/**
+ * @author john01dav
+ */
+public class TestEnqueueRunnable extends SimpleApplication{
+    private ExampleAsyncTask exampleAsyncTask;
+    
+    public static void main(String[] args){
+        new TestEnqueueRunnable().start();
+    }
+    
+    @Override
+    public void simpleInitApp(){
+        Geometry geom = new Geometry("Box", new Box(1, 1, 1));
+        Material material = new Material(getAssetManager(), "/Common/MatDefs/Misc/Unshaded.j3md");
+        material.setColor("Color", ColorRGBA.Blue); //a color is needed to start with
+        geom.setMaterial(material);
+        getRootNode().attachChild(geom);
+        
+        exampleAsyncTask = new ExampleAsyncTask(material);
+        exampleAsyncTask.getThread().start();
+    }
+
+    @Override
+    public void destroy(){
+        exampleAsyncTask.endTask();
+        super.destroy();
+    }
+    
+    private class ExampleAsyncTask implements Runnable{
+        private final Thread thread;
+        private final Material material;
+        private volatile boolean running = true;
+
+        public ExampleAsyncTask(Material material){
+            this.thread = new Thread(this);
+            this.material = material;
+        }
+
+        public Thread getThread(){
+            return thread;
+        }
+        
+        public void run(){
+            while(running){
+                enqueue(new Runnable(){ //primary usage of this in real applications would use lambda expressions which are unavailable at java 6
+                    public void run(){
+                        material.setColor("Color", ColorRGBA.randomColor());
+                    }
+                });
+                
+                try{
+                    Thread.sleep(1000);
+                }catch(InterruptedException e){}
+            }
+        }
+        
+        public void endTask(){
+            running = false;
+            thread.interrupt();
+        }
+        
+    }
+    
+}

+ 1 - 1
jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java

@@ -456,7 +456,7 @@ public class PhysicsVehicle extends PhysicsRigidBody {
     /**
      * Get the current forward vector of the vehicle in world coordinates
      * @param vector The object to write the forward vector values to.
-     * Passing null will cause a new {@link Vector3f) to be created.
+     * Passing null will cause a new {@link Vector3f} to be created.
      * @return The forward vector
      */
     public Vector3f getForwardVector(Vector3f vector) {

+ 19 - 18
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java

@@ -54,6 +54,7 @@ import com.jme3.system.AppSettings;
 import com.jme3.system.JmeContext;
 import com.jme3.system.NanoTimer;
 import com.jme3.system.NativeLibraryLoader;
+import com.jme3.system.NullRenderer;
 import com.jme3.system.SystemListener;
 import com.jme3.system.Timer;
 
@@ -69,9 +70,9 @@ import com.jogamp.opengl.GLContext;
 public abstract class JoglContext implements JmeContext {
 
     private static final Logger logger = Logger.getLogger(JoglContext.class.getName());
-    
+
     protected static final String THREAD_NAME = "jME3 Main";
-    
+
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean renderable = new AtomicBoolean(false);
     protected final Object createdLock = new Object();
@@ -91,7 +92,7 @@ public abstract class JoglContext implements JmeContext {
             NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
         }
     }
-    
+
     @Override
 	public void setSystemListener(SystemListener listener){
         this.listener = listener;
@@ -101,7 +102,7 @@ public abstract class JoglContext implements JmeContext {
 	public void setSettings(AppSettings settings) {
         this.settings.copyFrom(settings);
     }
-    
+
     @Override
 	public boolean isRenderable(){
         return renderable.get();
@@ -160,50 +161,50 @@ public abstract class JoglContext implements JmeContext {
             }
         }
     }
-    
+
     protected void initContextFirstTime(){
         if (GLContext.getCurrent().getGLVersionNumber().getMajor() < 2) {
-            throw new RendererException("OpenGL 2.0 or higher is " + 
+            throw new RendererException("OpenGL 2.0 or higher is " +
                                         "required for jMonkeyEngine");
         }
-        
+
         if (settings.getRenderer().startsWith("JOGL")) {
         	com.jme3.renderer.opengl.GL gl = new JoglGL();
         	GLExt glext = new JoglGLExt();
         	GLFbo glfbo = new JoglGLFbo();
-            
+
             if (settings.getBoolean("GraphicsDebug")) {
                 gl    = new GLDebugDesktop(gl, glext, glfbo);
                 glext = (GLExt) gl;
                 glfbo = (GLFbo) gl;
             }
-            
+
             if (settings.getBoolean("GraphicsTiming")) {
                 GLTimingState timingState = new GLTimingState();
                 gl    = (com.jme3.renderer.opengl.GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
                 glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
             }
-                  
+
             if (settings.getBoolean("GraphicsTrace")) {
                 gl    = (com.jme3.renderer.opengl.GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
                 glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
             }
-            
+
             renderer = new GLRenderer(gl, glext, glfbo);
             renderer.initialize();
         } else {
             throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
         }
-        
+
         if (GLContext.getCurrentGL().isExtensionAvailable("GL_ARB_debug_output") && settings.getBoolean("GraphicsDebug")) {
         	GLContext.getCurrent().enableGLDebugMessage(true);
         	GLContext.getCurrent().addGLDebugListener(new JoglGLDebugOutputHandler());
         }
-        
-        renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
-        renderer.setLinearizeSrgbImages(settings.getGammaCorrection());
+
+        renderer.setMainFrameBufferSrgb(settings.isGammaCorrection());
+        renderer.setLinearizeSrgbImages(settings.isGammaCorrection());
 
         // Init input
         if (keyInput != null) {
@@ -241,7 +242,7 @@ public abstract class JoglContext implements JmeContext {
             createdLock.notifyAll();
         }
     }
-    
+
     protected int determineMaxSamples(int requestedSamples) {
         GL gl = GLContext.getCurrentGL();
         if (gl.hasFullFBOSupport()) {
@@ -257,7 +258,7 @@ public abstract class JoglContext implements JmeContext {
             }
         }
     }
-    
+
     protected int getNumSamplesToUse() {
         int samples = 0;
         if (settings.getSamples() > 1){
@@ -268,7 +269,7 @@ public abstract class JoglContext implements JmeContext {
                         "Couldn''t satisfy antialiasing samples requirement: x{0}. "
                         + "Video hardware only supports: x{1}",
                         new Object[]{samples, supportedSamples});
-                
+
                 samples = supportedSamples;
             }
         }

+ 53 - 52
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -29,7 +29,6 @@
  * 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.lwjgl;
 
 import com.jme3.input.lwjgl.JInputJoyInput;
@@ -53,6 +52,7 @@ import com.jme3.renderer.opengl.GLTiming;
 import com.jme3.renderer.opengl.GLTimingState;
 import com.jme3.renderer.opengl.GLTracer;
 import com.jme3.system.*;
+import java.io.File;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
@@ -69,7 +69,7 @@ public abstract class LwjglContext implements JmeContext {
     private static final Logger logger = Logger.getLogger(LwjglContext.class.getName());
 
     protected static final String THREAD_NAME = "jME3 Main";
-    
+
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean renderable = new AtomicBoolean(false);
     protected final Object createdLock = new Object();
@@ -82,18 +82,18 @@ public abstract class LwjglContext implements JmeContext {
     protected Timer timer;
     protected SystemListener listener;
 
-    public void setSystemListener(SystemListener listener){
+    public void setSystemListener(SystemListener listener) {
         this.listener = listener;
     }
 
     protected void printContextInitInfo() {
-        logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n" +
-                               " * Graphics Adapter: {2}\n" +
-                               " * Driver Version: {3}\n" +
-                               " * Scaling Factor: {4}",
-                               new Object[]{ Sys.getVersion(), Thread.currentThread().getName(), 
-                                             Display.getAdapter(), Display.getVersion(),
-                                             Display.getPixelScaleFactor() });
+        logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n"
+                + " * Graphics Adapter: {2}\n"
+                + " * Driver Version: {3}\n"
+                + " * Scaling Factor: {4}",
+                new Object[]{Sys.getVersion(), Thread.currentThread().getName(),
+                    Display.getAdapter(), Display.getVersion(),
+                    Display.getPixelScaleFactor()});
     }
 
     protected ContextAttribs createContextAttribs() {
@@ -113,7 +113,7 @@ public abstract class LwjglContext implements JmeContext {
             return null;
         }
     }
-    
+
     protected int determineMaxSamples(int requestedSamples) {
         try {
             // If we already have a valid context, determine samples using current
@@ -131,13 +131,13 @@ public abstract class LwjglContext implements JmeContext {
         } catch (LWJGLException ex) {
             listener.handleError("Failed to check if display is current", ex);
         }
-        
+
         if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) {
             // No pbuffer, assume everything is supported.
             return Integer.MAX_VALUE;
         } else {
             Pbuffer pb = null;
-            
+
             // OpenGL2 method: Create pbuffer and query samples
             // from GL_ARB_framebuffer_object or GL_EXT_framebuffer_multisample.
             try {
@@ -155,13 +155,14 @@ public abstract class LwjglContext implements JmeContext {
             } catch (LWJGLException ex) {
                 // Something else failed.
                 return Integer.MAX_VALUE;
-            } finally { 
+            } finally {
                 if (pb != null) {
                     pb.destroy();
                 }
             }
         }
     }
+
     protected void loadNatives() {
         if (JmeSystem.isLowPermissions()) {
             return;
@@ -178,10 +179,10 @@ public abstract class LwjglContext implements JmeContext {
         }
         NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
     }
-    
+
     protected int getNumSamplesToUse() {
         int samples = 0;
-        if (settings.getSamples() > 1){
+        if (settings.getSamples() > 1) {
             samples = settings.getSamples();
             int supportedSamples = determineMaxSamples(samples);
             if (supportedSamples < samples) {
@@ -189,62 +190,62 @@ public abstract class LwjglContext implements JmeContext {
                         "Couldn''t satisfy antialiasing samples requirement: x{0}. "
                         + "Video hardware only supports: x{1}",
                         new Object[]{samples, supportedSamples});
-                
+
                 samples = supportedSamples;
             }
         }
         return samples;
     }
 
-    protected void initContextFirstTime(){
+    protected void initContextFirstTime() {
         if (!GLContext.getCapabilities().OpenGL20) {
-            throw new RendererException("OpenGL 2.0 or higher is " + 
-                                        "required for jMonkeyEngine");
+            throw new RendererException("OpenGL 2.0 or higher is "
+                    + "required for jMonkeyEngine");
         }
-        
+
         if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
-         || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
+                || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
             GL gl = new LwjglGL();
             GLExt glext = new LwjglGLExt();
             GLFbo glfbo;
-            
+
             if (GLContext.getCapabilities().OpenGL30) {
                 glfbo = new LwjglGLFboGL3();
             } else {
                 glfbo = new LwjglGLFboEXT();
             }
-            
+
             if (settings.getBoolean("GraphicsDebug")) {
-                gl    = new GLDebugDesktop(gl, glext, glfbo);
+                gl = new GLDebugDesktop(gl, glext, glfbo);
                 glext = (GLExt) gl;
                 glfbo = (GLFbo) gl;
             }
-            
+
             if (settings.getBoolean("GraphicsTiming")) {
                 GLTimingState timingState = new GLTimingState();
-                gl    = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
+                gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
                 glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
             }
-                  
+
             if (settings.getBoolean("GraphicsTrace")) {
-                gl    = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
+                gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
                 glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
             }
-            
+
             renderer = new GLRenderer(gl, glext, glfbo);
             renderer.initialize();
         } else {
             throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
         }
-        
+
         if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
             ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler()));
         }
-        
-        renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
-        renderer.setLinearizeSrgbImages(settings.getGammaCorrection());
+
+        renderer.setMainFrameBufferSrgb(settings.isGammaCorrection());
+        renderer.setLinearizeSrgbImages(settings.isGammaCorrection());
 
         // Init input
         if (keyInput != null) {
@@ -260,42 +261,42 @@ public abstract class LwjglContext implements JmeContext {
         }
     }
 
-    public void internalDestroy(){
+    public void internalDestroy() {
         renderer = null;
         timer = null;
         renderable.set(false);
-        synchronized (createdLock){
+        synchronized (createdLock) {
             created.set(false);
             createdLock.notifyAll();
         }
     }
-    
-    public void internalCreate(){
+
+    public void internalCreate() {
         timer = new LwjglTimer();
-        
-        synchronized (createdLock){
+
+        synchronized (createdLock) {
             created.set(true);
             createdLock.notifyAll();
         }
-        
-        if (renderable.get()){
+
+        if (renderable.get()) {
             initContextFirstTime();
-        }else{
+        } else {
             assert getType() == Type.Canvas;
         }
     }
 
-    public void create(){
+    public void create() {
         create(false);
     }
 
-    public void destroy(){
+    public void destroy() {
         destroy(false);
     }
 
-    protected void waitFor(boolean createdVal){
-        synchronized (createdLock){
-            while (created.get() != createdVal){
+    protected void waitFor(boolean createdVal) {
+        synchronized (createdLock) {
+            while (created.get() != createdVal) {
                 try {
                     createdLock.wait();
                 } catch (InterruptedException ex) {
@@ -304,11 +305,11 @@ public abstract class LwjglContext implements JmeContext {
         }
     }
 
-    public boolean isCreated(){
+    public boolean isCreated() {
         return created.get();
     }
-    
-    public boolean isRenderable(){
+
+    public boolean isRenderable() {
         return renderable.get();
     }
 
@@ -316,7 +317,7 @@ public abstract class LwjglContext implements JmeContext {
         this.settings.copyFrom(settings);
     }
 
-    public AppSettings getSettings(){
+    public AppSettings getSettings() {
         return settings;
     }
 

+ 7 - 7
jme3-lwjgl3/build.gradle

@@ -2,14 +2,14 @@ if (!hasProperty('mainClass')) {
     ext.mainClass = ''
 }
 
-repositories {
-    maven {
-        url "https://oss.sonatype.org/content/repositories/snapshots"
-    }
-}
+def lwjglVersion = '3.0.0b'
 
 dependencies {
     compile project(':jme3-core')
     compile project(':jme3-desktop')
-    compile 'org.lwjgl:lwjgl:3.0.0b'
-}
+
+    compile "org.lwjgl:lwjgl:${lwjglVersion}"
+    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows"
+    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-linux"
+    compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-osx"
+}

BIN
jme3-lwjgl3/lib/lwjgl-3.0.0b-35-natives.jar


BIN
jme3-lwjgl3/lib/lwjgl-3.0.0b-35.jar


+ 22 - 18
jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java

@@ -32,32 +32,38 @@
 package com.jme3.audio.lwjgl;
 
 import com.jme3.audio.openal.ALC;
+import java.nio.IntBuffer;
 import org.lwjgl.openal.ALC10;
 import org.lwjgl.openal.ALContext;
 import org.lwjgl.openal.ALDevice;
-
-import java.nio.IntBuffer;
-
-import static org.lwjgl.openal.ALC10.alcGetContextsDevice;
-import static org.lwjgl.openal.ALC10.alcGetCurrentContext;
+import org.lwjgl.openal.SOFTPauseDevice;
 
 public class LwjglALC implements ALC {
 
     private ALDevice device;
     private ALContext context;
 
+    private long contextId;
+    private long deviceId;
+
     public void createALC() {
         device = ALDevice.create();
         context = ALContext.create(device);
+        context.makeCurrent();
+
+        contextId = ALC10.alcGetCurrentContext();
+        deviceId = ALC10.alcGetContextsDevice(contextId);
     }
 
     public void destroyALC() {
         if (context != null) {
             context.destroy();
+            context = null;
         }
 
         if (device != null) {
             device.destroy();
+            device = null;
         }
     }
 
@@ -66,31 +72,29 @@ public class LwjglALC implements ALC {
     }
 
     public String alcGetString(final int parameter) {
-        final long context = alcGetCurrentContext();
-        final long device = alcGetContextsDevice(context);
-        return ALC10.alcGetString(device, parameter);
+        return ALC10.alcGetString(deviceId, parameter);
     }
 
     public boolean alcIsExtensionPresent(final String extension) {
-        final long context = alcGetCurrentContext();
-        final long device = alcGetContextsDevice(context);
-        return ALC10.alcIsExtensionPresent(device, extension);
+        return ALC10.alcIsExtensionPresent(deviceId, extension);
     }
 
     public void alcGetInteger(final int param, final IntBuffer buffer, final int size) {
-        if (buffer.position() != 0) throw new AssertionError();
-        if (buffer.limit() != size) throw new AssertionError();
-
-        final long context = alcGetCurrentContext();
-        final long device = alcGetContextsDevice(context);
-        final int value = ALC10.alcGetInteger(device, param);
-        //buffer.put(value);
+        if (buffer.position() != 0) {
+            throw new AssertionError();
+        }
+        if (buffer.limit() != size) {
+            throw new AssertionError();
+        }
+        ALC10.alcGetIntegerv(deviceId, param, buffer);
     }
 
     public void alcDevicePauseSOFT() {
+        SOFTPauseDevice.alcDevicePauseSOFT(deviceId);
     }
 
     public void alcDeviceResumeSOFT() {
+        SOFTPauseDevice.alcDeviceResumeSOFT(deviceId);
     }
 
 }

+ 2 - 1
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java

@@ -66,7 +66,8 @@ public class GlfwKeyInput implements KeyInput {
         glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() {
             @Override
             public void invoke(long window, int key, int scancode, int action, int mods) {
-                final KeyInputEvent evt = new KeyInputEvent(scancode, (char) key, GLFW_PRESS == action, GLFW_REPEAT == action);
+                int jmeKey = GlfwKeyMap.toJmeKeyCode(key);
+                final KeyInputEvent evt = new KeyInputEvent(jmeKey, (char) key, GLFW_PRESS == action, GLFW_REPEAT == action);
                 evt.setTime(getInputTimeNanos());
                 keyInputEvents.add(evt);
             }

+ 171 - 0
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java

@@ -0,0 +1,171 @@
+/*
+ * 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.input.lwjgl;
+
+import static org.lwjgl.glfw.GLFW.*;
+import static com.jme3.input.KeyInput.*;
+
+public class GlfwKeyMap {
+
+    private static final int[] glfwToJmeKeyMap = new int[GLFW_KEY_LAST + 1];
+
+    private static void reg(int jmeKey, int glfwKey) {
+        glfwToJmeKeyMap[glfwKey] = jmeKey;
+    }
+
+    static {
+        reg(KEY_ESCAPE, GLFW_KEY_ESCAPE);
+        reg(KEY_1, GLFW_KEY_1);
+        reg(KEY_2, GLFW_KEY_2);
+        reg(KEY_3, GLFW_KEY_3);
+        reg(KEY_4, GLFW_KEY_4);
+        reg(KEY_5, GLFW_KEY_5);
+        reg(KEY_6, GLFW_KEY_6);
+        reg(KEY_7, GLFW_KEY_7);
+        reg(KEY_8, GLFW_KEY_8);
+        reg(KEY_9, GLFW_KEY_9);
+        reg(KEY_0, GLFW_KEY_0);
+        reg(KEY_MINUS, GLFW_KEY_MINUS);
+        reg(KEY_EQUALS, GLFW_KEY_EQUAL);
+        reg(KEY_BACK, GLFW_KEY_BACKSPACE);
+        reg(KEY_TAB, GLFW_KEY_TAB);
+        reg(KEY_Q, GLFW_KEY_Q);
+        reg(KEY_W, GLFW_KEY_W);
+        reg(KEY_E, GLFW_KEY_E);
+        reg(KEY_R, GLFW_KEY_R);
+        reg(KEY_T, GLFW_KEY_T);
+        reg(KEY_Y, GLFW_KEY_Y);
+        reg(KEY_U, GLFW_KEY_U);
+        reg(KEY_I, GLFW_KEY_I);
+        reg(KEY_O, GLFW_KEY_O);
+        reg(KEY_P, GLFW_KEY_P);
+        reg(KEY_LBRACKET, GLFW_KEY_LEFT_BRACKET);
+        reg(KEY_RBRACKET, GLFW_KEY_RIGHT_BRACKET);
+        reg(KEY_RETURN, GLFW_KEY_ENTER);
+        reg(KEY_LCONTROL, GLFW_KEY_LEFT_CONTROL);
+        reg(KEY_A, GLFW_KEY_A);
+        reg(KEY_S, GLFW_KEY_S);
+        reg(KEY_D, GLFW_KEY_D);
+        reg(KEY_F, GLFW_KEY_F);
+        reg(KEY_G, GLFW_KEY_G);
+        reg(KEY_H, GLFW_KEY_H);
+        reg(KEY_J, GLFW_KEY_J);
+        reg(KEY_K, GLFW_KEY_K);
+        reg(KEY_L, GLFW_KEY_L);
+        reg(KEY_SEMICOLON, GLFW_KEY_SEMICOLON);
+        reg(KEY_APOSTROPHE, GLFW_KEY_APOSTROPHE);
+        reg(KEY_GRAVE, GLFW_KEY_GRAVE_ACCENT);
+        reg(KEY_LSHIFT, GLFW_KEY_LEFT_SHIFT);
+        reg(KEY_BACKSLASH, GLFW_KEY_BACKSLASH);
+        reg(KEY_Z, GLFW_KEY_Z);
+        reg(KEY_X, GLFW_KEY_X);
+        reg(KEY_C, GLFW_KEY_C);
+        reg(KEY_V, GLFW_KEY_V);
+        reg(KEY_B, GLFW_KEY_B);
+        reg(KEY_N, GLFW_KEY_N);
+        reg(KEY_M, GLFW_KEY_M);
+        reg(KEY_COMMA, GLFW_KEY_COMMA);
+        reg(KEY_PERIOD, GLFW_KEY_PERIOD);
+        reg(KEY_SLASH, GLFW_KEY_SLASH);
+        reg(KEY_RSHIFT, GLFW_KEY_RIGHT_SHIFT);
+        reg(KEY_MULTIPLY, GLFW_KEY_KP_MULTIPLY);
+        reg(KEY_LMENU, GLFW_KEY_LEFT_ALT);
+        reg(KEY_SPACE, GLFW_KEY_SPACE);
+        reg(KEY_CAPITAL, GLFW_KEY_CAPS_LOCK);
+        reg(KEY_F1, GLFW_KEY_F1);
+        reg(KEY_F2, GLFW_KEY_F2);
+        reg(KEY_F3, GLFW_KEY_F3);
+        reg(KEY_F4, GLFW_KEY_F4);
+        reg(KEY_F5, GLFW_KEY_F5);
+        reg(KEY_F6, GLFW_KEY_F6);
+        reg(KEY_F7, GLFW_KEY_F7);
+        reg(KEY_F8, GLFW_KEY_F8);
+        reg(KEY_F9, GLFW_KEY_F9);
+        reg(KEY_F10, GLFW_KEY_F10);
+        reg(KEY_NUMLOCK, GLFW_KEY_NUM_LOCK);
+        reg(KEY_SCROLL, GLFW_KEY_SCROLL_LOCK);
+        reg(KEY_NUMPAD7, GLFW_KEY_KP_7);
+        reg(KEY_NUMPAD8, GLFW_KEY_KP_8);
+        reg(KEY_NUMPAD9, GLFW_KEY_KP_9);
+        reg(KEY_SUBTRACT, GLFW_KEY_KP_SUBTRACT);
+        reg(KEY_NUMPAD4, GLFW_KEY_KP_4);
+        reg(KEY_NUMPAD5, GLFW_KEY_KP_5);
+        reg(KEY_NUMPAD6, GLFW_KEY_KP_6);
+        reg(KEY_ADD, GLFW_KEY_KP_ADD);
+        reg(KEY_NUMPAD1, GLFW_KEY_KP_1);
+        reg(KEY_NUMPAD2, GLFW_KEY_KP_2);
+        reg(KEY_NUMPAD3, GLFW_KEY_KP_3);
+        reg(KEY_NUMPAD0, GLFW_KEY_KP_0);
+        reg(KEY_DECIMAL, GLFW_KEY_KP_DECIMAL);
+        reg(KEY_F11, GLFW_KEY_F11);
+        reg(KEY_F12, GLFW_KEY_F12);
+        reg(KEY_F13, GLFW_KEY_F13);
+        reg(KEY_F14, GLFW_KEY_F14);
+        reg(KEY_F15, GLFW_KEY_F15);
+        //reg(KEY_KANA, GLFW_KEY_);
+        //reg(KEY_CONVERT, GLFW_KEY_);
+        //reg(KEY_NOCONVERT, GLFW_KEY_);
+        //reg(KEY_YEN, GLFW_KEY_);
+        //reg(KEY_NUMPADEQUALS, GLFW_KEY_);
+        //reg(KEY_CIRCUMFLEX, GLFW_KEY_);
+        //reg(KEY_AT, GLFW_KEY_);
+        //reg(KEY_COLON, GLFW_KEY_);
+        //reg(KEY_UNDERLINE, GLFW_KEY_);
+        //reg(KEY_KANJI, GLFW_KEY_);
+        //reg(KEY_STOP, GLFW_KEY_);
+        //reg(KEY_AX, GLFW_KEY_);
+        //reg(KEY_UNLABELED, GLFW_KEY_);
+        reg(KEY_NUMPADENTER, GLFW_KEY_KP_ENTER);
+        reg(KEY_RCONTROL, GLFW_KEY_RIGHT_CONTROL);
+        //reg(KEY_NUMPADCOMMA, GLFW_KEY_);
+        reg(KEY_DIVIDE, GLFW_KEY_KP_DIVIDE);
+        reg(KEY_SYSRQ, GLFW_KEY_PRINT_SCREEN);
+        reg(KEY_RMENU, GLFW_KEY_RIGHT_ALT);
+        reg(KEY_PAUSE, GLFW_KEY_PAUSE);
+        reg(KEY_HOME, GLFW_KEY_HOME);
+        reg(KEY_UP, GLFW_KEY_UP);
+        reg(KEY_PRIOR, GLFW_KEY_PAGE_UP);
+        reg(KEY_LEFT, GLFW_KEY_LEFT);
+        reg(KEY_RIGHT, GLFW_KEY_RIGHT);
+        reg(KEY_END, GLFW_KEY_END);
+        reg(KEY_DOWN, GLFW_KEY_DOWN);
+        reg(KEY_NEXT, GLFW_KEY_PAGE_DOWN);
+        reg(KEY_INSERT, GLFW_KEY_INSERT);
+        reg(KEY_DELETE, GLFW_KEY_DELETE);
+        reg(KEY_LMETA, GLFW_KEY_LEFT_SUPER);
+        reg(KEY_RMETA, GLFW_KEY_RIGHT_SUPER);
+    }
+
+    public static int toJmeKeyCode(int glfwKey) {
+        return glfwToJmeKeyMap[glfwKey];
+    }
+}

+ 91 - 47
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java

@@ -38,16 +38,22 @@ import com.jme3.input.RawInputListener;
 import com.jme3.input.event.MouseButtonEvent;
 import com.jme3.input.event.MouseMotionEvent;
 import com.jme3.system.lwjgl.LwjglWindow;
+import com.jme3.util.BufferUtils;
 import org.lwjgl.glfw.GLFWCursorPosCallback;
 import org.lwjgl.glfw.GLFWMouseButtonCallback;
 import org.lwjgl.glfw.GLFWScrollCallback;
 
 import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Queue;
 import java.util.logging.Logger;
 
 import static org.lwjgl.glfw.GLFW.*;
+import org.lwjgl.glfw.GLFWImage;
+import org.lwjgl.system.MemoryUtil;
 
 /**
  * Captures mouse input using GLFW callbacks. It then temporarily stores these in event queues which are processed in the
@@ -74,57 +80,70 @@ public class GlfwMouseInput implements MouseInput {
     private Queue<MouseMotionEvent> mouseMotionEvents = new LinkedList<MouseMotionEvent>();
     private Queue<MouseButtonEvent> mouseButtonEvents = new LinkedList<MouseButtonEvent>();
 
-    public GlfwMouseInput(final LwjglWindow context) {
+    private Map<JmeCursor, Long> jmeToGlfwCursorMap = new HashMap<JmeCursor, Long>();
+
+    public GlfwMouseInput(LwjglWindow context) {
         this.context = context;
     }
 
+    private void onCursorPos(long window, double xpos, double ypos) {
+        int xDelta;
+        int yDelta;
+        int x = (int) Math.round(xpos);
+        int y = context.getSettings().getHeight() - (int) Math.round(ypos);
+
+        if (mouseX == 0) {
+            mouseX = x;
+        }
+
+        if (mouseY == 0) {
+            mouseY = y;
+        }
+
+        xDelta = x - mouseX;
+        yDelta = y - mouseY;
+        mouseX = x;
+        mouseY = y;
+
+        if (xDelta != 0 || yDelta != 0) {
+            final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y, xDelta, yDelta, mouseWheel, 0);
+            mouseMotionEvent.setTime(getInputTimeNanos());
+            mouseMotionEvents.add(mouseMotionEvent);
+        }
+    }
+
+    private void onWheelScroll(long window, double xOffset, double yOffset) {
+        mouseWheel += yOffset;
+        final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset));
+        mouseMotionEvent.setTime(getInputTimeNanos());
+        mouseMotionEvents.add(mouseMotionEvent);
+    }
+
+    private void onMouseButton(final long window, final int button, final int action, final int mods) {
+        final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(convertButton(button), action == GLFW_PRESS, mouseX, mouseY);
+        mouseButtonEvent.setTime(getInputTimeNanos());
+        mouseButtonEvents.add(mouseButtonEvent);
+    }
+
     public void initialize() {
         glfwSetCursorPosCallback(context.getWindowHandle(), cursorPosCallback = new GLFWCursorPosCallback() {
             @Override
             public void invoke(long window, double xpos, double ypos) {
-                int xDelta;
-                int yDelta;
-                int x = (int) Math.round(xpos);
-                int y = context.getSettings().getHeight() - (int) Math.round(ypos);
-
-                if (mouseX == 0) {
-                    mouseX = x;
-                }
-
-                if (mouseY == 0) {
-                    mouseY = y;
-                }
-
-                xDelta = x - mouseX;
-                yDelta = y - mouseY;
-                mouseX = x;
-                mouseY = y;
-
-                if (xDelta != 0 || yDelta != 0) {
-                    final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y, xDelta, yDelta, mouseWheel, 0);
-                    mouseMotionEvent.setTime(getInputTimeNanos());
-                    mouseMotionEvents.add(mouseMotionEvent);
-                }
+                onCursorPos(window, xpos, ypos);
             }
         });
 
         glfwSetScrollCallback(context.getWindowHandle(), scrollCallback = new GLFWScrollCallback() {
             @Override
             public void invoke(final long window, final double xOffset, final double yOffset) {
-                mouseWheel += yOffset;
-
-                final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset));
-                mouseMotionEvent.setTime(getInputTimeNanos());
-                mouseMotionEvents.add(mouseMotionEvent);
+                onWheelScroll(window, xOffset, yOffset);
             }
         });
 
         glfwSetMouseButtonCallback(context.getWindowHandle(), mouseButtonCallback = new GLFWMouseButtonCallback() {
             @Override
             public void invoke(final long window, final int button, final int action, final int mods) {
-                final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(convertButton(button), action == GLFW_PRESS, mouseX, mouseY);
-                mouseButtonEvent.setTime(getInputTimeNanos());
-                mouseButtonEvents.add(mouseButtonEvent);
+                onMouseButton(window, button, action, mods);
             }
         });
 
@@ -160,6 +179,10 @@ public class GlfwMouseInput implements MouseInput {
         scrollCallback.release();
         mouseButtonCallback.release();
 
+        for (long glfwCursor : jmeToGlfwCursorMap.values()) {
+            glfwDestroyCursor(glfwCursor);
+        }
+
         logger.fine("Mouse destroyed.");
     }
 
@@ -185,31 +208,52 @@ public class GlfwMouseInput implements MouseInput {
         return (long) (glfwGetTime() * 1000000000);
     }
 
-    public void setNativeCursor(final JmeCursor jmeCursor) {
+    private long createGlfwCursor(JmeCursor jmeCursor) {
+        GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF));
+
+        // TODO: currently animated cursors are not supported
+        IntBuffer imageData = jmeCursor.getImagesData();
+        ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity());
+        buf.asIntBuffer().put(imageData);
+
+        glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf);
+
+        return glfwCreateCursor(glfwImage, jmeCursor.getXHotSpot(), jmeCursor.getYHotSpot());
+    }
+
+    public void setNativeCursor(JmeCursor jmeCursor) {
         if (jmeCursor != null) {
-            final ByteBuffer byteBuffer = org.lwjgl.BufferUtils.createByteBuffer(jmeCursor.getImagesData().capacity());
-            byteBuffer.asIntBuffer().put(jmeCursor.getImagesData().array());
-            final long cursor = glfwCreateCursor(byteBuffer, jmeCursor.getXHotSpot(), jmeCursor.getYHotSpot());
-            glfwSetCursor(context.getWindowHandle(), cursor);
+            Long glfwCursor = jmeToGlfwCursorMap.get(jmeCursor);
+
+            if (glfwCursor == null) {
+                glfwCursor = createGlfwCursor(jmeCursor);
+                jmeToGlfwCursorMap.put(jmeCursor, glfwCursor);
+            }
+
+            glfwSetCursor(context.getWindowHandle(), glfwCursor);
+        } else {
+            glfwSetCursor(context.getWindowHandle(), MemoryUtil.NULL);
         }
     }
 
     /**
-     * Simply converts the GLFW button code to a JME button code. If there is no match it just returns the GLFW button
-     * code. Bare in mind GLFW supports 8 different mouse buttons.
+     * Simply converts the GLFW button code to a JME button code. If there is no
+     * match it just returns the GLFW button code. Bear in mind GLFW supports 8
+     * different mouse buttons.
      *
      * @param glfwButton the raw GLFW button index.
      * @return the mapped {@link MouseInput} button id.
      */
     private int convertButton(final int glfwButton) {
-        if (glfwButton == GLFW_MOUSE_BUTTON_LEFT) {
-            return MouseInput.BUTTON_LEFT;
-        } else if(glfwButton == GLFW_MOUSE_BUTTON_MIDDLE) {
-            return MouseInput.BUTTON_MIDDLE;
-        } else if(glfwButton == GLFW_MOUSE_BUTTON_RIGHT) {
-            return MouseInput.BUTTON_RIGHT;
+        switch (glfwButton) {
+            case GLFW_MOUSE_BUTTON_LEFT:
+                return MouseInput.BUTTON_LEFT;
+            case GLFW_MOUSE_BUTTON_MIDDLE:
+                return MouseInput.BUTTON_MIDDLE;
+            case GLFW_MOUSE_BUTTON_RIGHT:
+                return MouseInput.BUTTON_RIGHT;
+            default:
+                return glfwButton;
         }
-
-        return glfwButton;
     }
 }

+ 27 - 30
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -43,9 +43,7 @@ import com.jme3.renderer.lwjgl.LwjglGLFboEXT;
 import com.jme3.renderer.lwjgl.LwjglGLFboGL3;
 import com.jme3.renderer.opengl.*;
 import com.jme3.system.*;
-import org.lwjgl.Sys;
 import org.lwjgl.glfw.GLFW;
-import org.lwjgl.opengl.ARBDebugOutput;
 import org.lwjgl.opengl.ARBFramebufferObject;
 import org.lwjgl.opengl.EXTFramebufferMultisample;
 import org.lwjgl.opengl.GLCapabilities;
@@ -53,9 +51,10 @@ import org.lwjgl.opengl.GLCapabilities;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import static org.lwjgl.glfw.GLFW.GLFW_TRUE;
+import org.lwjgl.opengl.ARBDebugOutput;
 
 import static org.lwjgl.opengl.GL.createCapabilities;
-import static org.lwjgl.opengl.GL11.GL_TRUE;
 import static org.lwjgl.opengl.GL11.glGetInteger;
 
 /**
@@ -84,40 +83,22 @@ public abstract class LwjglContext implements JmeContext {
     }
 
     protected void printContextInitInfo() {
-        logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n" +
-                        " * Graphics Adapter: GLFW {2}",
-                new Object[]{Sys.getVersion(), Thread.currentThread().getName(), GLFW.glfwGetVersionString()});
+        logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n"
+                + " * Graphics Adapter: GLFW {2}",
+                new Object[]{org.lwjgl.Version.getVersion(), Thread.currentThread().getName(), GLFW.glfwGetVersionString()});
     }
 
     protected int determineMaxSamples() {
         // If we already have a valid context, determine samples using current context.
-        if (GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object") == GL_TRUE) {
+        if (GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object") == GLFW_TRUE) {
             return glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES);
-        } else if (GLFW.glfwExtensionSupported("GL_EXT_framebuffer_multisample") == GL_TRUE) {
+        } else if (GLFW.glfwExtensionSupported("GL_EXT_framebuffer_multisample") == GLFW_TRUE) {
             return glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT);
         }
 
         return Integer.MAX_VALUE;
     }
 
-    protected void loadNatives() {
-        if (JmeSystem.isLowPermissions()) {
-            return;
-        }
-
-        if ("LWJGL".equals(settings.getAudioRenderer())) {
-            NativeLibraryLoader.loadNativeLibrary("openal-lwjgl3", true);
-        }
-
-        if (NativeLibraryLoader.isUsingNativeBullet()) {
-            NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
-        }
-
-        NativeLibraryLoader.loadNativeLibrary("glfw-lwjgl3", true);
-        NativeLibraryLoader.loadNativeLibrary("jemalloc-lwjgl3", true);
-        NativeLibraryLoader.loadNativeLibrary("lwjgl3", true);
-    }
-
     protected int getNumSamplesToUse() {
         int samples = 0;
         if (settings.getSamples() > 1) {
@@ -135,6 +116,17 @@ public abstract class LwjglContext implements JmeContext {
         return samples;
     }
 
+    protected void loadNatives() {
+        if (JmeSystem.isLowPermissions()) {
+            return;
+        }
+
+        if (NativeLibraryLoader.isUsingNativeBullet()) {
+            NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
+        }
+    }
+
+
     protected void initContextFirstTime() {
         final GLCapabilities capabilities = createCapabilities(settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3));
 
@@ -180,11 +172,11 @@ public abstract class LwjglContext implements JmeContext {
         }
 
         if (capabilities.GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
-            ARBDebugOutput.glDebugMessageCallbackARB(new LwjglGLDebugOutputHandler(), 0); // User param is zero. Not sure what we could use that for.
+            ARBDebugOutput.glDebugMessageCallbackARB(new LwjglGLDebugOutputHandler(), 0);
         }
 
-        renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
-        renderer.setLinearizeSrgbImages(settings.getGammaCorrection());
+        renderer.setMainFrameBufferSrgb(settings.isGammaCorrection());
+        renderer.setLinearizeSrgbImages(settings.isGammaCorrection());
 
         // Init input
         if (keyInput != null) {
@@ -198,7 +190,6 @@ public abstract class LwjglContext implements JmeContext {
         if (joyInput != null) {
             joyInput.initialize();
         }
-
         renderable.set(true);
     }
 
@@ -240,26 +231,32 @@ public abstract class LwjglContext implements JmeContext {
         }
     }
 
+    @Override
     public boolean isCreated() {
         return created.get();
     }
 
+    @Override
     public boolean isRenderable() {
         return renderable.get();
     }
 
+    @Override
     public void setSettings(AppSettings settings) {
         this.settings.copyFrom(settings);
     }
 
+    @Override
     public AppSettings getSettings() {
         return settings;
     }
 
+    @Override
     public Renderer getRenderer() {
         return renderer;
     }
 
+    @Override
     public Timer getTimer() {
         return timer;
     }

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

@@ -43,7 +43,6 @@ import com.jme3.system.AppSettings;
 import com.jme3.system.JmeContext;
 import com.jme3.system.JmeSystem;
 import com.jme3.system.NanoTimer;
-import org.lwjgl.Sys;
 import org.lwjgl.glfw.*;
 
 import java.awt.*;
@@ -52,6 +51,7 @@ import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.lwjgl.Version;
 
 import static org.lwjgl.glfw.GLFW.*;
 import static org.lwjgl.opengl.GL11.GL_FALSE;
@@ -72,7 +72,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
     protected boolean wasActive = false;
     protected boolean autoFlush = true;
     protected boolean allowSwapBuffers = false;
-    private long window = -1;
+    private long window = NULL;
     private final JmeContext.Type type;
     private int frameRateLimit = -1;
     private double frameSleepTime;
@@ -81,6 +81,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
     private GLFWWindowSizeCallback windowSizeCallback;
     private GLFWWindowFocusCallback windowFocusCallback;
 
+    private Thread mainThread;
+
     public LwjglWindow(final JmeContext.Type type) {
         if (!JmeContext.Type.Display.equals(type) && !JmeContext.Type.OffscreenSurface.equals(type) && !JmeContext.Type.Canvas.equals(type)) {
             throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided");
@@ -102,7 +104,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
      * @param title the title to set
      */
     public void setTitle(final String title) {
-        if (created.get() && window != -1) {
+        if (created.get() && window != NULL) {
             glfwSetWindowTitle(window, title);
         }
     }
@@ -127,45 +129,45 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         glfwSetErrorCallback(errorCallback = new GLFWErrorCallback() {
             @Override
             public void invoke(int error, long description) {
-                final String message = Callbacks.errorCallbackDescriptionString(description);
+                final String message = GLFWErrorCallback.getDescription(description);
                 listener.handleError(message, new Exception(message));
             }
         });
 
-        if (glfwInit() != GL_TRUE) {
+        if (glfwInit() != GLFW_TRUE) {
             throw new IllegalStateException("Unable to initialize GLFW");
         }
 
         glfwDefaultWindowHints();
-        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
-
-        // TODO: Add support for monitor selection
-        long monitor = NULL;
 
-        if (settings.isFullscreen()) {
-            monitor = glfwGetPrimaryMonitor();
+        if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
+            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+        } else {
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
         }
 
-        final ByteBuffer videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
-
-        if (settings.getWidth() <= 0 || settings.getHeight() <= 0) {
-            settings.setResolution(GLFWvidmode.width(videoMode), GLFWvidmode.height(videoMode));
+        if (settings.getBoolean("RendererDebug")) {
+            glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
         }
 
-        window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL);
-
-        if (window == NULL) {
-            throw new RuntimeException("Failed to create the GLFW window");
+        if (settings.isGammaCorrection()) {
+            glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
         }
 
-        glfwWindowHint(GLFW_RESIZABLE, settings.isResizable() ? GL_TRUE : GL_FALSE);
+        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
+        glfwWindowHint(GLFW_RESIZABLE, settings.isResizable() ? GLFW_TRUE : GLFW_FALSE);
+
+        glfwWindowHint(GLFW_DOUBLE_BUFFER, GLFW_TRUE);
         glfwWindowHint(GLFW_DEPTH_BITS, settings.getDepthBits());
         glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits());
         glfwWindowHint(GLFW_SAMPLES, settings.getSamples());
-        glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GL_TRUE : GL_FALSE);
+        glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GLFW_TRUE : GLFW_FALSE);
         glfwWindowHint(GLFW_REFRESH_RATE, settings.getFrequency());
 
-        // Not sure how else to support bits per pixel
         if (settings.getBitsPerPixel() == 24) {
             glfwWindowHint(GLFW_RED_BITS, 8);
             glfwWindowHint(GLFW_GREEN_BITS, 8);
@@ -178,11 +180,38 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
 
         glfwWindowHint(GLFW_ALPHA_BITS, settings.getAlphaBits());
 
+        // TODO: Add support for monitor selection
+        long monitor = NULL;
+
+        if (settings.isFullscreen()) {
+            monitor = glfwGetPrimaryMonitor();
+        }
+
+        final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
+
+        if (settings.getWidth() <= 0 || settings.getHeight() <= 0) {
+            settings.setResolution(videoMode.width(), videoMode.height());
+        }
+
+        window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL);
+
+        if (window == NULL) {
+            throw new RuntimeException("Failed to create the GLFW window");
+        }
+
+        // Add a resize callback which delegates to the listener
+        glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() {
+            @Override
+            public void invoke(final long window, final int width, final int height) {
+                settings.setResolution(width, height);
+                listener.reshape(width, height);
+            }
+        });
+
         glfwSetWindowFocusCallback(window, windowFocusCallback = new GLFWWindowFocusCallback() {
             @Override
             public void invoke(final long window, final int focused) {
                 final boolean focus = (focused == GL_TRUE);
-
                 if (wasActive != focus) {
                     if (!wasActive) {
                         listener.gainFocus();
@@ -197,8 +226,10 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         });
 
         // Center the window
-        if (!settings.isFullscreen() && Type.Display.equals(type)) {
-            glfwSetWindowPos(window, (GLFWvidmode.width(videoMode) - settings.getWidth()) / 2, (GLFWvidmode.height(videoMode) - settings.getHeight()) / 2);
+        if (!settings.isFullscreen()) {
+            glfwSetWindowPos(window,
+                    (videoMode.width() - settings.getWidth()) / 2,
+                    (videoMode.height() - settings.getHeight()) / 2);
         }
 
         // Make the OpenGL context current
@@ -211,19 +242,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             glfwSwapInterval(0);
         }
 
-        // Make the window visible
-        if (Type.Display.equals(type)) {
-            glfwShowWindow(window);
-        }
 
-        // Add a resize callback which delegates to the listener
-        glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() {
-            @Override
-            public void invoke(final long window, final int width, final int height) {
-                settings.setResolution(width, height);
-                listener.reshape(width, height);
-            }
-        });
+        glfwShowWindow(window);
 
         allowSwapBuffers = settings.isSwapBuffers();
 
@@ -239,29 +259,40 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
                 renderer.cleanup();
             }
 
-            errorCallback.release();
-            windowSizeCallback.release();
-            windowFocusCallback.release();
+            if (errorCallback != null) {
+                errorCallback.release();
+                errorCallback = null;
+            }
+
+            if (windowSizeCallback != null) {
+                windowSizeCallback.release();
+                windowSizeCallback = null;
+            }
+
+            if (windowFocusCallback != null) {
+                windowFocusCallback.release();
+                windowFocusCallback = null;
+            }
 
-            if (window != 0) {
+            if (window != NULL) {
                 glfwDestroyWindow(window);
+                window = NULL;
             }
         } catch (Exception ex) {
             listener.handleError("Failed to destroy context", ex);
         }
     }
 
+    @Override
     public void create(boolean waitFor) {
         if (created.get()) {
             LOGGER.warning("create() called when display is already created!");
             return;
         }
 
-        new Thread(this, THREAD_NAME).start();
-
-        if (waitFor) {
-            waitFor(true);
-        }
+        // NOTE: this is required for Mac OS X!
+        mainThread = Thread.currentThread();
+        run();
     }
 
     /**
@@ -272,6 +303,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             if (!JmeSystem.isLowPermissions()) {
                 // Enable uncaught exception handler only for current thread
                 Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                    @Override
                     public void uncaughtException(Thread thread, Throwable thrown) {
                         listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown);
                         if (needClose.get()) {
@@ -283,6 +315,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
                 });
             }
 
+            loadNatives();
+
             timer = new NanoTimer();
 
             // For canvas, this will create a pbuffer,
@@ -296,8 +330,9 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             super.internalCreate();
         } catch (Exception ex) {
             try {
-                if (window != -1) {
+                if (window != NULL) {
                     glfwDestroyWindow(window);
+                    window = NULL;
                 }
             } catch (Exception ex2) {
                 LOGGER.log(Level.WARNING, null, ex2);
@@ -318,6 +353,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         // If a restart is required, lets recreate the context.
         if (needRestart.getAndSet(false)) {
             try {
+                destroyContext();
                 createContext(settings);
             } catch (Exception ex) {
                 LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex);
@@ -346,8 +382,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
             }
         }
 
-        glfwPollEvents();
-
         // Subclasses just call GLObjectManager clean up objects here
         // it is safe .. for now.
         if (renderer != null) {
@@ -377,6 +411,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
                 }
             }
         }
+
+        glfwPollEvents();
     }
 
     private void setFrameRateLimit(int frameRateLimit) {
@@ -389,21 +425,22 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
      */
 
     protected void deinitInThread() {
+        listener.destroy();
+
         destroyContext();
+        super.internalDestroy();
 
-        listener.destroy();
         LOGGER.fine("Display destroyed.");
-        super.internalDestroy();
     }
 
+    @Override
     public void run() {
         if (listener == null) {
             throw new IllegalStateException("SystemListener is not set on context!"
                     + "Must set with JmeContext.setSystemListener().");
         }
 
-        loadNatives();
-        LOGGER.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion());
+        LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion());
 
         if (!initInThread()) {
             LOGGER.log(Level.SEVERE, "Display initialization failed. Cannot continue.");
@@ -411,15 +448,16 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         }
 
         while (true) {
-            if (glfwWindowShouldClose(window) == GL_TRUE) {
-                listener.requestClose(false);
-            }
 
             runLoop();
 
             if (needClose.get()) {
                 break;
             }
+
+            if (glfwWindowShouldClose(window) == GL_TRUE) {
+                listener.requestClose(false);
+            }
         }
 
         deinitInThread();
@@ -458,6 +496,11 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
     public void destroy(boolean waitFor) {
         needClose.set(true);
 
+        if (mainThread == Thread.currentThread()) {
+            // Ignore waitFor.
+            return;
+        }
+
         if (waitFor) {
             waitFor(false);
         }

+ 31 - 22
jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java

@@ -301,35 +301,38 @@ public class DefaultClient implements Client
 
     protected void closeConnections( DisconnectInfo info )
     {
-        if( !isRunning )
-            return;
+        synchronized(this) {
+            if( !isRunning )
+                return;
 
-        if( services.isStarted() ) {
-            // Let the services get a chance to stop before we
-            // kill the connection.
-            services.stop();
-        }
+            if( services.isStarted() ) {
+                // Let the services get a chance to stop before we
+                // kill the connection.
+                services.stop();
+            }
         
-        // Send a close message
+            // Send a close message
     
-        // Tell the thread it's ok to die
-        for( ConnectorAdapter ca : channels ) {
-            if( ca == null )
-                continue;
-            ca.close();
-        }
+            // Tell the thread it's ok to die
+            for( ConnectorAdapter ca : channels ) {
+                if( ca == null )
+                    continue;
+                ca.close();
+            }
         
-        // Wait for the threads?
+            // Wait for the threads?
 
-        // Just in case we never fully connected
-        connecting.countDown();
+            // Just in case we never fully connected
+            connecting.countDown();
         
-        fireDisconnected(info);
+            isRunning = false;
         
-        isRunning = false;
+            // Terminate the services
+            services.terminate();            
+        }
         
-        // Terminate the services
-        services.terminate();            
+        // Make sure we aren't synched while firing events
+        fireDisconnected(info);        
     }         
 
     @Override
@@ -462,11 +465,17 @@ public class DefaultClient implements Client
                 this.id = (int)crm.getId();
                 log.log( Level.FINE, "Connection established, id:{0}.", this.id );
                 connecting.countDown();
-                fireConnected();
+                //fireConnected();
             } else {
                 // Else it's a message letting us know that the 
                 // hosted services have been started
                 startServices();
+ 
+                // Delay firing 'connected' until the services have all
+                // been started to avoid odd race conditions.  If there is some
+                // need to get some kind of event before the services have been
+                // started then we should create a new event step.               
+                fireConnected();
             }
             return;
         } else if( m instanceof ChannelInfoMessage ) {

+ 1 - 1
jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java

@@ -608,7 +608,7 @@ public class DefaultServer implements Server
             // should always already be closed through all paths that I
             // can conceive... but it doesn't hurt to be sure. 
             for( Endpoint p : channels ) {
-                if( p == null ) 
+                if( p == null || !p.isConnected() ) 
                     continue;
                 p.close();
             }

+ 7 - 3
jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java

@@ -112,6 +112,8 @@ public class KernelAdapter extends Thread
         
         // Kill the kernel
         kernel.terminate();
+        
+        join();
     }
 
     protected void reportError( Endpoint p, Object context, Exception e )
@@ -119,9 +121,11 @@ public class KernelAdapter extends Thread
         // Should really be queued up so the outer thread can
         // retrieve them.  For now we'll just log it.  FIXME
         log.log( Level.SEVERE, "Unhandled error, endpoint:" + p + ", context:" + context, e );
-        
-        // In lieu of other options, at least close the endpoint
-        p.close();
+
+        if( p.isConnected() ) {
+            // In lieu of other options, at least close the endpoint
+            p.close();
+        }
     }                                                      
 
     protected HostedConnection getConnection( Endpoint p )

+ 1 - 1
jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java

@@ -181,7 +181,7 @@ public class MessageProtocol
             Message m = (Message)obj;
             messages.add(m);
         } catch( IOException e ) {
-            throw new RuntimeException( "Error deserializing object, clas ID:" + buffer.getShort(0), e );   
+            throw new RuntimeException( "Error deserializing object, class ID:" + buffer.getShort(0), e );   
         }         
     }
 }

+ 12 - 0
jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java

@@ -76,6 +76,18 @@ public abstract class AbstractKernel implements Kernel
         log.log( Level.SEVERE, "Unhanddled kernel error", e );
     }
 
+    protected void wakeupReader() {
+        // If there are no pending messages then add one so that the
+        // kernel-user knows to wake up if it is only listening for
+        // envelopes.
+        if( !hasEnvelopes() ) {
+            // Note: this is not really a race condition.  At worst, our
+            // event has already been handled by now and it does no harm
+            // to check again.
+            addEnvelope( EVENTS_PENDING );
+        }
+    }
+
     protected long nextEndpointId()
     {
         return nextId.getAndIncrement();

+ 4 - 9
jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java

@@ -106,6 +106,9 @@ public class SelectorKernel extends AbstractKernel
         try {
             thread.close();
             thread = null;
+            
+            // Need to let any caller waiting for a read() wakeup 
+            wakeupReader();       
         } catch( IOException e ) {
             throw new KernelException( "Error closing host connection:" + address, e );
         }
@@ -164,15 +167,7 @@ public class SelectorKernel extends AbstractKernel
         // Enqueue an endpoint event for the listeners
         addEvent( EndpointEvent.createRemove( this, p ) );
 
-        // If there are no pending messages then add one so that the
-        // kernel-user knows to wake up if it is only listening for
-        // envelopes.
-        if( !hasEnvelopes() ) {
-            // Note: this is not really a race condition.  At worst, our
-            // event has already been handled by now and it does no harm
-            // to check again.
-            addEnvelope( EVENTS_PENDING );
-        }
+        wakeupReader();
     }
 
     /**

+ 5 - 10
jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java

@@ -110,6 +110,9 @@ public class UdpKernel extends AbstractKernel
             thread.close();
             writer.shutdown();
             thread = null;
+            
+            // Need to let any caller waiting for a read() wakeup 
+            wakeupReader();       
         } catch( IOException e ) {
             throw new KernelException( "Error closing host connection:" + address, e );
         }
@@ -169,16 +172,8 @@ public class UdpKernel extends AbstractKernel
         log.log( Level.FINE, "Socket endpoints size:{0}", socketEndpoints.size() );
 
         addEvent( EndpointEvent.createRemove( this, p ) );
-        
-        // If there are no pending messages then add one so that the
-        // kernel-user knows to wake up if it is only listening for
-        // envelopes.
-        if( !hasEnvelopes() ) {
-            // Note: this is not really a race condition.  At worst, our
-            // event has already been handled by now and it does no harm
-            // to check again.
-            addEnvelope( EVENTS_PENDING );
-        }
+ 
+        wakeupReader();       
     }
 
     protected void newData( DatagramPacket packet )

+ 5 - 3
jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java

@@ -146,15 +146,17 @@ public class SerializerRegistrationsMessage extends AbstractMessage {
             // that also run their own servers but realistically they would have
             // to disable the ServerSerializerRegistrationsServer anyway.
             if( compiled != null ) {
-                log.log( Level.INFO, "Skipping registration as registry is locked, presumably by a local server process.");
+                log.log(Level.INFO, "Skipping registration as registry is locked, presumably by a local server process.");
                 return;
             }
         }
         
+        log.log(Level.FINE, "Registering {0} classes...", registrations.length);
         for( Registration reg : registrations ) {
-            log.log( Level.INFO, "Registering:{0}", reg);
+            log.log(Level.INFO, "Registering:{0}", reg);
             reg.register();
         }
+        log.log(Level.FINE, "Done registering serializable classes.");
     }
     
     @Serializable
@@ -187,7 +189,7 @@ public class SerializerRegistrationsMessage extends AbstractMessage {
                     serializer = (Serializer)serializerType.newInstance();                    
                 }
                 SerializerRegistration result = Serializer.registerClassForId(id, type, serializer);
-                log.log( Level.FINE, "   result:{0}", result);                
+                log.log(Level.FINE, "   result:{0}", result);                
             } catch( ClassNotFoundException e ) {
                 throw new RuntimeException( "Class not found attempting to register:" + this, e );
             } catch( InstantiationException e ) {

+ 18 - 2
jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java

@@ -130,7 +130,7 @@ public abstract class Serializer {
         registerClass(IdentityHashMap.class,            new MapSerializer());
         registerClass(TreeMap.class,                    new MapSerializer());
         registerClass(WeakHashMap.class,                new MapSerializer());
-        
+ 
         registerClass(Enum.class,      new EnumSerializer());
         registerClass(GZIPCompressedMessage.class, new GZIPSerializer());
         registerClass(ZIPCompressedMessage.class, new ZIPSerializer());
@@ -331,7 +331,7 @@ public abstract class Serializer {
     @SuppressWarnings("unchecked")
     public static SerializerRegistration getSerializerRegistration(Class cls, boolean failOnMiss) {
         SerializerRegistration reg = classRegistrations.get(cls);
-
+        
         if (reg != null) return reg;
 
         for (Map.Entry<Class, SerializerRegistration> entry : classRegistrations.entrySet()) {
@@ -425,6 +425,22 @@ public abstract class Serializer {
             return;
         }
         SerializerRegistration reg = writeClass(buffer, object.getClass());
+        
+        // If the caller (or us) has registered a generic base class (like Enum)
+        // that is meant to steer automatic resolution for things like FieldSerializer
+        // that have final classes in fields... then there are cases where the exact 
+        // type isn't known by the outer class.  (Think of a message object
+        // that has an Object field but tries to send an Enum subclass in it.)
+        // In that case, the SerializerRegistration object we get back isn't
+        // really going to be capable of recreating the object on the other
+        // end because it won't know what class to use.  This only comes up
+        // in writeclassAndObejct() because we just wrote an ID to a more generic
+        // class than will be readable on the other end.  The check is simple, though.
+        if( reg.getType() != object.getClass() ) {
+            throw new IllegalArgumentException("Class has not been registered:" 
+                    + object.getClass() + " but resolved to generic serializer for:" + reg.getType());
+        } 
+
         reg.getSerializer().writeObject(buffer, object);
     }
 

+ 3 - 2
jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java

@@ -48,8 +48,9 @@ public class EnumSerializer extends Serializer {
 
             if (ordinal == -1) return null;
             T[] enumConstants = c.getEnumConstants();
-            if (enumConstants == null)
-                throw new SerializerException( "Class has no enum constants:" + c );
+            if (enumConstants == null) {
+                throw new SerializerException("Class has no enum constants:" + c + "  Ordinal:" + ordinal);
+            }
             return enumConstants[ordinal];
         } catch (IndexOutOfBoundsException ex) {
             return null;

+ 34 - 5
jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java

@@ -34,11 +34,14 @@ package com.jme3.network.serializing.serializers;
 import com.jme3.network.serializing.Serializer;
 import com.jme3.network.serializing.SerializerException;
 import java.io.IOException;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * The field serializer is the default serializer used for custom class.
@@ -46,16 +49,35 @@ import java.util.*;
  * @author Lars Wesselius, Nathan Sweet
  */
 public class FieldSerializer extends Serializer {
+    
+    static final Logger log = Logger.getLogger(FieldSerializer.class.getName());
+
     private static Map<Class, SavedField[]> savedFields = new HashMap<Class, SavedField[]>();
+    private static Map<Class, Constructor> savedCtors = new HashMap<Class, Constructor>();
 
     protected void checkClass(Class clazz) {
     
         // See if the class has a public no-arg constructor
         try {
-            clazz.getConstructor();
+            savedCtors.put(clazz, clazz.getConstructor());
+            return;
+        } catch( NoSuchMethodException e ) {
+            //throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz ); 
+        }
+        
+        // See if it has a non-public no-arg constructor
+        try {
+            Constructor ctor = clazz.getDeclaredConstructor();
+            
+            // Make sure we can call it later.
+            ctor.setAccessible(true);
+             
+            savedCtors.put(clazz, ctor);
+            return;
         } catch( NoSuchMethodException e ) {
-            throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz ); 
-        } 
+        }
+        
+        throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz );  
     }        
     
     public void initialize(Class clazz) {
@@ -121,7 +143,8 @@ public class FieldSerializer extends Serializer {
 
         T object;
         try {
-            object = c.newInstance();
+            Constructor<T> ctor = (Constructor<T>)savedCtors.get(c);
+            object = ctor.newInstance();
         } catch (Exception e) {
             throw new SerializerException( "Error creating object of type:" + c, e );
         }
@@ -129,6 +152,9 @@ public class FieldSerializer extends Serializer {
         for (SavedField savedField : fields) {
             Field field = savedField.field;
             Serializer serializer = savedField.serializer;
+            if( log.isLoggable(Level.FINER) ) {
+                log.log(Level.FINER, "Reading field:{0} using serializer:{1}", new Object[]{field, serializer});
+            }
             Object value;
 
             if (serializer != null) {
@@ -164,9 +190,12 @@ public class FieldSerializer extends Serializer {
             try {
                 val = savedField.field.get(object);
             } catch (IllegalAccessException e) {
-                e.printStackTrace();
+                throw new SerializerException("Unable to access field:" + savedField.field + " on:" + object, e);
             }
             Serializer serializer = savedField.serializer;
+            if( log.isLoggable(Level.FINER) ) {
+                log.log(Level.FINER, "Writing field:{0} using serializer:{1}", new Object[]{savedField.field, serializer});
+            }
 
             try {
                 if (serializer != null) {

+ 5 - 0
jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java

@@ -351,6 +351,11 @@ public class RmiRegistry {
         }
         
         public Object invoke( short procId, Object[] args ) {
+            if( log.isLoggable(Level.FINEST) ) {
+                log.finest("SharedObject->invoking:" + classInfo.getMethod(procId) 
+                           + " on:" + object 
+                           + " with:" + (args == null ? "null" : Arrays.asList(args))); 
+            }
             return classInfo.getMethod(procId).invoke(object, args);
         }
     }

+ 1 - 1
jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java

@@ -184,7 +184,7 @@ public class RpcConnection {
     
         if( log.isLoggable(Level.FINEST) ) {
             log.log(Level.FINEST, "handleMessage({0})", msg);
-        }    
+        }
         RpcHandler handler = handlers.get(msg.getObjectId());
         try {
             if( handler == null ) {

+ 1 - 1
jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java

@@ -65,7 +65,7 @@ public class ServerSerializerRegistrationsService extends AbstractHostedService
     public void connectionAdded(Server server, HostedConnection hc) {
         // Just in case
         super.connectionAdded(server, hc);
-        
+ 
         // Send the client the registration information
         hc.send(SerializerRegistrationsMessage.INSTANCE);
     }

+ 0 - 1
sdk/BasicGameTemplate/MANIFEST.MF

@@ -1 +0,0 @@
-X-Comment: Created with jMonkeyPlatform

+ 0 - 0
sdk/BasicGameTemplate/assets/Interface/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/MatDefs/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Materials/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Models/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Scenes/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Shaders/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Sounds/.keep


+ 0 - 0
sdk/BasicGameTemplate/assets/Textures/.keep


+ 0 - 76
sdk/BasicGameTemplate/build.xml

@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- You may freely edit this file. See commented blocks below for -->
-<!-- some examples of how to customize the build. -->
-<!-- (If you delete it and reopen the project it will be recreated.) -->
-<!-- By default, only the Clean and Build commands use this build script. -->
-<!-- Commands such as Run, Debug, and Test only use this build script if -->
-<!-- the Compile on Save feature is turned off for the project. -->
-<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
-<!-- in the project's Project Properties dialog box.-->
-<project name="BasicGameTemplate" default="default" basedir=".">
-    <description>Builds, tests, and runs the project BasicGameTemplate.</description>
-    <import file="nbproject/build-impl.xml"/>
-
-    <!--
-
-    There exist several targets which are by default empty and which can be 
-    used for execution of your tasks. These targets are usually executed 
-    before and after some main targets. They are: 
-
-      -pre-init:                 called before initialization of project properties
-      -post-init:                called after initialization of project properties
-      -pre-compile:              called before javac compilation
-      -post-compile:             called after javac compilation
-      -pre-compile-single:       called before javac compilation of single file
-      -post-compile-single:      called after javac compilation of single file
-      -pre-compile-test:         called before javac compilation of JUnit tests
-      -post-compile-test:        called after javac compilation of JUnit tests
-      -pre-compile-test-single:  called before javac compilation of single JUnit test
-      -post-compile-test-single: called after javac compilation of single JUunit test
-      -pre-jar:                  called before JAR building
-      -post-jar:                 called after JAR building
-      -post-clean:               called after cleaning build products
-
-    (Targets beginning with '-' are not intended to be called on their own.)
-
-    Example of inserting an obfuscator after compilation could look like this:
-
-        <target name="-post-compile">
-            <obfuscate>
-                <fileset dir="${build.classes.dir}"/>
-            </obfuscate>
-        </target>
-
-    For list of available properties check the imported 
-    nbproject/build-impl.xml file. 
-
-
-    Another way to customize the build is by overriding existing main targets.
-    The targets of interest are: 
-
-      -init-macrodef-javac:     defines macro for javac compilation
-      -init-macrodef-junit:     defines macro for junit execution
-      -init-macrodef-debug:     defines macro for class debugging
-      -init-macrodef-java:      defines macro for class execution
-      -do-jar-with-manifest:    JAR building (if you are using a manifest)
-      -do-jar-without-manifest: JAR building (if you are not using a manifest)
-      run:                      execution of project 
-      -javadoc-build:           Javadoc generation
-      test-report:              JUnit report generation
-
-    An example of overriding the target for project execution could look like this:
-
-        <target name="run" depends="BasicGameTemplate-impl.jar">
-            <exec dir="bin" executable="launcher.exe">
-                <arg file="${dist.jar}"/>
-            </exec>
-        </target>
-
-    Notice that the overridden target depends on the jar target and not only on 
-    the compile target as the regular run target does. Again, for a list of available 
-    properties which you can use, check the target you are overriding in the
-    nbproject/build-impl.xml file. 
-
-    -->
-    
-</project>

+ 0 - 22
sdk/BasicGameTemplate/master-application.jnlp

@@ -1,22 +0,0 @@
-<jnlp spec="1.0+" codebase="${jnlp.codebase}" href="launch.jnlp">
-    <information>
-        <title>${APPLICATION.TITLE}</title>
-        <vendor>${APPLICATION.VENDOR}</vendor>
-        <homepage href="${APPLICATION.HOMEPAGE}"/>
-        <description>${APPLICATION.DESC}</description>
-        <description kind="short">${APPLICATION.DESC.SHORT}</description>
-<!--${JNLP.ICONS}-->
-<!--${JNLP.OFFLINE.ALLOWED}-->
-    </information>
-<!--${JNLP.SECURITY}-->
-    <resources>
-<!--${JNLP.RESOURCES.RUNTIME}-->
-<!--${JNLP.RESOURCES.MAIN.JAR}-->
-<!--${JNLP.RESOURCES.JARS}-->
-<jar href='lib/assets.jar'/>
-<!--${JNLP.RESOURCES.EXTENSIONS}-->
-    </resources>
-    <application-desc main-class="${jnlp.main.class}">
-<!--${JNLP.APPLICATION.ARGS}-->
-    </application-desc>
-</jnlp>

+ 0 - 8
sdk/BasicGameTemplate/nbproject/genfiles.properties

@@ -1,8 +0,0 @@
-build.xml.data.CRC32=94bf7c61
-build.xml.script.CRC32=79a29eb7
[email protected]
-# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
-# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=1ac6e2f9
-nbproject/build-impl.xml.script.CRC32=28b1a2c2
-nbproject/[email protected]

+ 0 - 92
sdk/BasicGameTemplate/nbproject/project.properties

@@ -1,92 +0,0 @@
-annotation.processing.enabled=true
-annotation.processing.enabled.in.editor=false
-annotation.processing.processors.list=
-annotation.processing.run.all.processors=true
-application.title=MyGame
-application.vendor=MyCompany
-assets.jar.name=assets.jar
-assets.excludes=**/*.j3odata,**/*.mesh,**/*.skeleton,**/*.mesh\.xml,**/*.skeleton\.xml,**/*.scene,**/*.material,**/*.obj,**/*.mtl,**/*.3ds,**/*.dae,**/*.blend,**/*.blend*[0-9]
-assets.folder.name=assets
-assets.compress=true
-build.classes.dir=${build.dir}/classes
-build.classes.excludes=**/*.java,**/*.form
-# This directory is removed when the project is cleaned:
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-# Only compile against the classpath explicitly listed here:
-build.sysclasspath=ignore
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-compile.on.save=true
-# Uncomment to specify the preferred debugger connection transport:
-#debug.transport=dt_socket
-debug.classpath=\
-    ${run.classpath}
-debug.test.classpath=\
-    ${run.test.classpath}
-# This directory is removed when the project is cleaned:
-dist.dir=dist
-dist.jar=${dist.dir}/${application.title}.jar
-dist.javadoc.dir=${dist.dir}/javadoc
-endorsed.classpath=
-excludes=
-includes=**
-jar.compress=false
-javac.classpath=\
-    ${libs.jme3-jogg.classpath}:\
-    ${libs.jme3-blender.classpath}:\
-    ${libs.jme3-networking.classpath}:\
-    ${libs.jme3-plugins.classpath}:\
-    ${libs.jme3-core.classpath}:\
-    ${libs.jme3-desktop.classpath}:\
-    ${libs.jme3-lwjgl.classpath}:\
-    ${libs.jme3-niftygui.classpath}:\
-    ${libs.jme3-effects.classpath}:\
-    ${libs.jme3-terrain.classpath}:\
-    ${libs.jme3-jbullet.classpath}
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.deprecation=false
-javac.processorpath=\
-    ${javac.classpath}
-javac.source=1.6
-javac.target=1.6
-javac.test.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=
-jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
-jnlp.codebase.type=local
-jnlp.descriptor=application
-jnlp.enabled=false
-jnlp.offline-allowed=false
-jnlp.signed=false
-main.class=mygame.Main
-meta.inf.dir=${src.dir}/META-INF
-manifest.file=MANIFEST.MF
-mkdist.disabled=false
-platform.active=default_platform
-run.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}:\
-    ${assets.folder.name}
-# Space-separated list of JVM arguments used when running the project
-# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
-# or test-sys-prop.name=value to set system properties for unit tests):
-run.jvmargs=
-run.test.classpath=\
-    ${javac.test.classpath}:\
-    ${build.test.classes.dir}
-source.encoding=UTF-8
-src.dir=src

+ 0 - 18
sdk/BasicGameTemplate/nbproject/project.xml

@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.java.j2seproject</type>
-    <configuration>
-        <buildExtensions xmlns="http://www.netbeans.org/ns/ant-build-extender/1">
-            <extension file="assets-impl.xml" id="assets">
-                <dependency dependsOn="-init-assets" target="-do-init"/>
-            </extension>
-        </buildExtensions>
-        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
-            <name>BasicGameTemplate</name>
-            <source-roots>
-                <root id="src.dir"/>
-            </source-roots>
-            <test-roots/>
-        </data>
-    </configuration>
-</project>

+ 0 - 43
sdk/BasicGameTemplate/src/mygame/Main.java

@@ -1,43 +0,0 @@
-package mygame;
-
-import com.jme3.app.SimpleApplication;
-import com.jme3.material.Material;
-import com.jme3.math.ColorRGBA;
-import com.jme3.math.Vector3f;
-import com.jme3.renderer.RenderManager;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.shape.Box;
-
-/**
- * test
- * @author normenhansen
- */
-public class Main extends SimpleApplication {
-
-    public static void main(String[] args) {
-        Main app = new Main();
-        app.start();
-    }
-
-    @Override
-    public void simpleInitApp() {
-        Box b = new Box(1, 1, 1);
-        Geometry geom = new Geometry("Box", b);
-
-        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mat.setColor("Color", ColorRGBA.Blue);
-        geom.setMaterial(mat);
-
-        rootNode.attachChild(geom);
-    }
-
-    @Override
-    public void simpleUpdate(float tpf) {
-        //TODO: add update code
-    }
-
-    @Override
-    public void simpleRender(RenderManager rm) {
-        //TODO: add render code
-    }
-}

+ 0 - 73
sdk/JME3TestsTemplate/build.xml

@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- You may freely edit this file. See commented blocks below for -->
-<!-- some examples of how to customize the build. -->
-<!-- (If you delete it and reopen the project it will be recreated.) -->
-<!-- By default, only the Clean and Build commands use this build script. -->
-<!-- Commands such as Run, Debug, and Test only use this build script if -->
-<!-- the Compile on Save feature is turned off for the project. -->
-<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
-<!-- in the project's Project Properties dialog box.-->
-<project name="JME3TestsTemplate" default="default" basedir=".">
-    <description>Builds, tests, and runs the project JME3TestsTemplate.</description>
-    <import file="nbproject/build-impl.xml"/>
-    <!--
-
-    There exist several targets which are by default empty and which can be 
-    used for execution of your tasks. These targets are usually executed 
-    before and after some main targets. They are: 
-
-      -pre-init:                 called before initialization of project properties
-      -post-init:                called after initialization of project properties
-      -pre-compile:              called before javac compilation
-      -post-compile:             called after javac compilation
-      -pre-compile-single:       called before javac compilation of single file
-      -post-compile-single:      called after javac compilation of single file
-      -pre-compile-test:         called before javac compilation of JUnit tests
-      -post-compile-test:        called after javac compilation of JUnit tests
-      -pre-compile-test-single:  called before javac compilation of single JUnit test
-      -post-compile-test-single: called after javac compilation of single JUunit test
-      -pre-jar:                  called before JAR building
-      -post-jar:                 called after JAR building
-      -post-clean:               called after cleaning build products
-
-    (Targets beginning with '-' are not intended to be called on their own.)
-
-    Example of inserting an obfuscator after compilation could look like this:
-
-        <target name="-post-compile">
-            <obfuscate>
-                <fileset dir="${build.classes.dir}"/>
-            </obfuscate>
-        </target>
-
-    For list of available properties check the imported 
-    nbproject/build-impl.xml file. 
-
-
-    Another way to customize the build is by overriding existing main targets.
-    The targets of interest are: 
-
-      -init-macrodef-javac:     defines macro for javac compilation
-      -init-macrodef-junit:     defines macro for junit execution
-      -init-macrodef-debug:     defines macro for class debugging
-      -init-macrodef-java:      defines macro for class execution
-      -do-jar:                  JAR building
-      run:                      execution of project 
-      -javadoc-build:           Javadoc generation
-      test-report:              JUnit report generation
-
-    An example of overriding the target for project execution could look like this:
-
-        <target name="run" depends="JME3TestsTemplate-impl.jar">
-            <exec dir="bin" executable="launcher.exe">
-                <arg file="${dist.jar}"/>
-            </exec>
-        </target>
-
-    Notice that the overridden target depends on the jar target and not only on 
-    the compile target as the regular run target does. Again, for a list of available 
-    properties which you can use, check the target you are overriding in the
-    nbproject/build-impl.xml file. 
-
-    -->
-</project>

+ 0 - 880
sdk/JME3TestsTemplate/nbproject/build-impl.xml

@@ -1,880 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-*** GENERATED FROM project.xml - DO NOT EDIT  ***
-***         EDIT ../build.xml INSTEAD         ***
-
-For the purpose of easier reading the script
-is divided into following sections:
-
-  - initialization
-  - compilation
-  - jar
-  - execution
-  - debugging
-  - javadoc
-  - junit compilation
-  - junit execution
-  - junit debugging
-  - applet
-  - cleanup
-
-        -->
-<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="JME3TestsTemplate-impl">
-    <fail message="Please build using Ant 1.7.1 or higher.">
-        <condition>
-            <not>
-                <antversion atleast="1.7.1"/>
-            </not>
-        </condition>
-    </fail>
-    <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
-    <!-- 
-                ======================
-                INITIALIZATION SECTION 
-                ======================
-            -->
-    <target name="-pre-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="-pre-init" name="-init-private">
-        <property file="nbproject/private/config.properties"/>
-        <property file="nbproject/private/configs/${config}.properties"/>
-        <property file="nbproject/private/private.properties"/>
-    </target>
-    <target depends="-pre-init,-init-private" name="-init-user">
-        <property file="${user.properties.file}"/>
-        <!-- The two properties below are usually overridden -->
-        <!-- by the active platform. Just a fallback. -->
-        <property name="default.javac.source" value="1.4"/>
-        <property name="default.javac.target" value="1.4"/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user" name="-init-project">
-        <property file="nbproject/configs/${config}.properties"/>
-        <property file="nbproject/project.properties"/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
-        <available file="${manifest.file}" property="manifest.available"/>
-        <available file="${application.splash}" property="splashscreen.available"/>
-        <condition property="main.class.available">
-            <and>
-                <isset property="main.class"/>
-                <not>
-                    <equals arg1="${main.class}" arg2="" trim="true"/>
-                </not>
-            </and>
-        </condition>
-        <condition property="manifest.available+main.class">
-            <and>
-                <isset property="manifest.available"/>
-                <isset property="main.class.available"/>
-            </and>
-        </condition>
-        <condition property="do.mkdist">
-            <and>
-                <isset property="libs.CopyLibs.classpath"/>
-                <not>
-                    <istrue value="${mkdist.disabled}"/>
-                </not>
-            </and>
-        </condition>
-        <condition property="manifest.available+main.class+mkdist.available">
-            <and>
-                <istrue value="${manifest.available+main.class}"/>
-                <isset property="do.mkdist"/>
-            </and>
-        </condition>
-        <condition property="manifest.available+main.class+mkdist.available+splashscreen.available">
-            <and>
-                <istrue value="${manifest.available+main.class+mkdist.available}"/>
-                <istrue value="${splashscreen.available}"/>
-            </and>
-        </condition>
-        <condition property="do.archive">
-            <not>
-                <istrue value="${jar.archive.disabled}"/>
-            </not>
-        </condition>
-        <condition property="do.archive+manifest.available">
-            <and>
-                <isset property="manifest.available"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+manifest.available+main.class">
-            <and>
-                <istrue value="${manifest.available+main.class}"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+manifest.available+main.class+mkdist.available">
-            <and>
-                <istrue value="${manifest.available+main.class+mkdist.available}"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+manifest.available+main.class+mkdist.available+splashscreen.available">
-            <and>
-                <istrue value="${manifest.available+main.class+mkdist.available+splashscreen.available}"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="have.tests">
-            <or/>
-        </condition>
-        <condition property="have.sources">
-            <or>
-                <available file="${src.dir}"/>
-            </or>
-        </condition>
-        <condition property="netbeans.home+have.tests">
-            <and>
-                <isset property="netbeans.home"/>
-                <isset property="have.tests"/>
-            </and>
-        </condition>
-        <condition property="no.javadoc.preview">
-            <and>
-                <isset property="javadoc.preview"/>
-                <isfalse value="${javadoc.preview}"/>
-            </and>
-        </condition>
-        <property name="run.jvmargs" value=""/>
-        <property name="javac.compilerargs" value=""/>
-        <property name="work.dir" value="${basedir}"/>
-        <condition property="no.deps">
-            <and>
-                <istrue value="${no.dependencies}"/>
-            </and>
-        </condition>
-        <property name="javac.debug" value="true"/>
-        <property name="javadoc.preview" value="true"/>
-        <property name="application.args" value=""/>
-        <property name="source.encoding" value="${file.encoding}"/>
-        <property name="runtime.encoding" value="${source.encoding}"/>
-        <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
-            <and>
-                <isset property="javadoc.encoding"/>
-                <not>
-                    <equals arg1="${javadoc.encoding}" arg2=""/>
-                </not>
-            </and>
-        </condition>
-        <property name="javadoc.encoding.used" value="${source.encoding}"/>
-        <property name="includes" value="**"/>
-        <property name="excludes" value=""/>
-        <property name="do.depend" value="false"/>
-        <condition property="do.depend.true">
-            <istrue value="${do.depend}"/>
-        </condition>
-        <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
-        <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
-            <length length="0" string="${endorsed.classpath}" when="greater"/>
-        </condition>
-        <property name="javac.fork" value="false"/>
-        <property name="jar.index" value="false"/>
-        <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
-    </target>
-    <target name="-post-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
-        <fail unless="src.dir">Must set src.dir</fail>
-        <fail unless="build.dir">Must set build.dir</fail>
-        <fail unless="dist.dir">Must set dist.dir</fail>
-        <fail unless="build.classes.dir">Must set build.classes.dir</fail>
-        <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
-        <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
-        <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
-        <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
-        <fail unless="dist.jar">Must set dist.jar</fail>
-    </target>
-    <target name="-init-macrodef-property">
-        <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute name="name"/>
-            <attribute name="value"/>
-            <sequential>
-                <property name="@{name}" value="${@{value}}"/>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
-        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <attribute default="${javac.processorpath}" name="processorpath"/>
-            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="${javac.debug}" name="debug"/>
-            <attribute default="${empty.dir}" name="sourcepath"/>
-            <attribute default="${empty.dir}" name="gensrcdir"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.dir}/empty" name="empty.dir"/>
-                <mkdir dir="${empty.dir}"/>
-                <mkdir dir="@{apgeneratedsrcdir}"/>
-                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
-                    <src>
-                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
-                            <include name="*"/>
-                        </dirset>
-                    </src>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <compilerarg line="${javac.compilerargs}"/>
-                    <compilerarg value="-processorpath"/>
-                    <compilerarg path="@{processorpath}:${empty.dir}"/>
-                    <compilerarg line="${ap.processors.internal}"/>
-                    <compilerarg line="${annotation.processing.processor.options}"/>
-                    <compilerarg value="-s"/>
-                    <compilerarg path="@{apgeneratedsrcdir}"/>
-                    <compilerarg line="${ap.proc.none.internal}"/>
-                    <customize/>
-                </javac>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
-        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <attribute default="${javac.processorpath}" name="processorpath"/>
-            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="${javac.debug}" name="debug"/>
-            <attribute default="${empty.dir}" name="sourcepath"/>
-            <attribute default="${empty.dir}" name="gensrcdir"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.dir}/empty" name="empty.dir"/>
-                <mkdir dir="${empty.dir}"/>
-                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
-                    <src>
-                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
-                            <include name="*"/>
-                        </dirset>
-                    </src>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <compilerarg line="${javac.compilerargs}"/>
-                    <customize/>
-                </javac>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
-        <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <sequential>
-                <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                </depend>
-            </sequential>
-        </macrodef>
-        <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <sequential>
-                <fail unless="javac.includes">Must set javac.includes</fail>
-                <pathconvert pathsep="," property="javac.includes.binary">
-                    <path>
-                        <filelist dir="@{destdir}" files="${javac.includes}"/>
-                    </path>
-                    <globmapper from="*.java" to="*.class"/>
-                </pathconvert>
-                <delete>
-                    <files includes="${javac.includes.binary}"/>
-                </delete>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-junit">
-        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <sequential>
-                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" showoutput="true" tempdir="${build.dir}">
-                    <batchtest todir="${build.test.results.dir}"/>
-                    <classpath>
-                        <path path="${run.test.classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <formatter type="brief" usefile="false"/>
-                    <formatter type="xml"/>
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg line="${run.jvmargs}"/>
-                </junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
-        <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${main.class}" name="name"/>
-            <attribute default="${debug.classpath}" name="classpath"/>
-            <attribute default="" name="stopclassname"/>
-            <sequential>
-                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                </nbjpdastart>
-            </sequential>
-        </macrodef>
-        <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${build.classes.dir}" name="dir"/>
-            <sequential>
-                <nbjpdareload>
-                    <fileset dir="@{dir}" includes="${fix.classes}">
-                        <include name="${fix.includes}*.class"/>
-                    </fileset>
-                </nbjpdareload>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-debug-args">
-        <property name="version-output" value="java version &quot;${ant.java.version}"/>
-        <condition property="have-jdk-older-than-1.4">
-            <or>
-                <contains string="${version-output}" substring="java version &quot;1.0"/>
-                <contains string="${version-output}" substring="java version &quot;1.1"/>
-                <contains string="${version-output}" substring="java version &quot;1.2"/>
-                <contains string="${version-output}" substring="java version &quot;1.3"/>
-            </or>
-        </condition>
-        <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
-            <istrue value="${have-jdk-older-than-1.4}"/>
-        </condition>
-        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
-            <os family="windows"/>
-        </condition>
-        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
-            <isset property="debug.transport"/>
-        </condition>
-    </target>
-    <target depends="-init-debug-args" name="-init-macrodef-debug">
-        <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${main.class}" name="classname"/>
-            <attribute default="${debug.classpath}" name="classpath"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <java classname="@{classname}" dir="${work.dir}" fork="true">
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg line="${debug-args-line}"/>
-                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
-                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
-                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
-                    <jvmarg line="${run.jvmargs}"/>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="run-sys-prop."/>
-                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <customize/>
-                </java>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-java">
-        <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${main.class}" name="classname"/>
-            <attribute default="${run.classpath}" name="classpath"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <java classname="@{classname}" dir="${work.dir}" fork="true">
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
-                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
-                    <jvmarg line="${run.jvmargs}"/>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="run-sys-prop."/>
-                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <customize/>
-                </java>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-copylibs">
-        <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
-                <pathconvert property="run.classpath.without.build.classes.dir">
-                    <path path="${run.classpath}"/>
-                    <map from="${build.classes.dir.resolved}" to=""/>
-                </pathconvert>
-                <pathconvert pathsep=" " property="jar.classpath">
-                    <path path="${run.classpath.without.build.classes.dir}"/>
-                    <chainedmapper>
-                        <flattenmapper/>
-                        <globmapper from="*" to="lib/*"/>
-                    </chainedmapper>
-                </pathconvert>
-                <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
-                <copylibs compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifest="${manifest.file}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
-                    <fileset dir="${build.classes.dir}"/>
-                    <manifest>
-                        <attribute name="Class-Path" value="${jar.classpath}"/>
-                        <customize/>
-                    </manifest>
-                </copylibs>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-presetdef-jar">
-        <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}">
-                <j2seproject1:fileset dir="${build.classes.dir}"/>
-            </jar>
-        </presetdef>
-    </target>
-    <target name="-init-ap-cmdline-properties">
-        <property name="annotation.processing.enabled" value="true"/>
-        <property name="annotation.processing.processors.list" value=""/>
-        <property name="annotation.processing.processor.options" value=""/>
-        <property name="annotation.processing.run.all.processors" value="true"/>
-        <property name="javac.processorpath" value="${javac.classpath}"/>
-        <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
-        <condition property="ap.supported.internal" value="true">
-            <not>
-                <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
-            </not>
-        </condition>
-    </target>
-    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
-        <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
-            <isfalse value="${annotation.processing.run.all.processors}"/>
-        </condition>
-        <condition else="" property="ap.proc.none.internal" value="-proc:none">
-            <isfalse value="${annotation.processing.enabled}"/>
-        </condition>
-    </target>
-    <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
-        <property name="ap.cmd.line.internal" value=""/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-junit,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
-    <!--
-                ===================
-                COMPILATION SECTION
-                ===================
-            -->
-    <target name="-deps-jar-init" unless="built-jar.properties">
-        <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
-        <delete file="${built-jar.properties}" quiet="true"/>
-    </target>
-    <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
-        <echo level="warn" message="Cycle detected: JME3TestsTemplate was already built"/>
-    </target>
-    <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
-        <mkdir dir="${build.dir}"/>
-        <touch file="${built-jar.properties}" verbose="false"/>
-        <property file="${built-jar.properties}" prefix="already.built.jar."/>
-        <antcall target="-warn-already-built-jar"/>
-        <propertyfile file="${built-jar.properties}">
-            <entry key="${basedir}" value=""/>
-        </propertyfile>
-    </target>
-    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
-    <target depends="init" name="-check-automatic-build">
-        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
-    </target>
-    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
-        <antcall target="clean"/>
-    </target>
-    <target depends="init,deps-jar" name="-pre-pre-compile">
-        <mkdir dir="${build.classes.dir}"/>
-    </target>
-    <target name="-pre-compile">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="do.depend.true" name="-compile-depend">
-        <pathconvert property="build.generated.subdirs">
-            <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="*"/>
-            </dirset>
-        </pathconvert>
-        <j2seproject3:depend srcdir="${src.dir}:${build.generated.subdirs}"/>
-    </target>
-    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
-        <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
-        <copy todir="${build.classes.dir}">
-            <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-        </copy>
-    </target>
-    <target if="has.persistence.xml" name="-copy-persistence-xml">
-        <mkdir dir="${build.classes.dir}/META-INF"/>
-        <copy todir="${build.classes.dir}/META-INF">
-            <fileset dir="${meta.inf.dir}" includes="persistence.xml"/>
-        </copy>
-    </target>
-    <target name="-post-compile">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
-    <target name="-pre-compile-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
-        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
-        <j2seproject3:force-recompile/>
-        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}"/>
-    </target>
-    <target name="-post-compile-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
-    <!--
-                ====================
-                JAR BUILDING SECTION
-                ====================
-            -->
-    <target depends="init" name="-pre-pre-jar">
-        <dirname file="${dist.jar}" property="dist.jar.dir"/>
-        <mkdir dir="${dist.jar.dir}"/>
-    </target>
-    <target name="-pre-jar">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive" name="-do-jar-without-manifest" unless="manifest.available">
-        <j2seproject1:jar/>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available" name="-do-jar-with-manifest" unless="manifest.available+main.class">
-        <j2seproject1:jar manifest="${manifest.file}"/>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available+main.class" name="-do-jar-with-mainclass" unless="manifest.available+main.class+mkdist.available">
-        <j2seproject1:jar manifest="${manifest.file}">
-            <j2seproject1:manifest>
-                <j2seproject1:attribute name="Main-Class" value="${main.class}"/>
-            </j2seproject1:manifest>
-        </j2seproject1:jar>
-        <echo>To run this application from the command line without Ant, try:</echo>
-        <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
-        <property location="${dist.jar}" name="dist.jar.resolved"/>
-        <pathconvert property="run.classpath.with.dist.jar">
-            <path path="${run.classpath}"/>
-            <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
-        </pathconvert>
-        <echo>java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar,-init-macrodef-copylibs" if="do.archive+manifest.available+main.class+mkdist.available+splashscreen.available" name="-do-jar-with-libraries-and-splashscreen">
-        <basename file="${application.splash}" property="splashscreen.basename"/>
-        <mkdir dir="${build.classes.dir}/META-INF"/>
-        <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
-        <j2seproject3:copylibs>
-            <customize>
-                <attribute name="Main-Class" value="${main.class}"/>
-                <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
-            </customize>
-        </j2seproject3:copylibs>
-        <echo>To run this application from the command line without Ant, try:</echo>
-        <property location="${dist.jar}" name="dist.jar.resolved"/>
-        <echo>java -jar "${dist.jar.resolved}"</echo>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar,-init-macrodef-copylibs" if="do.archive+manifest.available+main.class+mkdist.available" name="-do-jar-with-libraries" unless="splashscreen.available">
-        <j2seproject3:copylibs>
-            <customize>
-                <attribute name="Main-Class" value="${main.class}"/>
-            </customize>
-        </j2seproject3:copylibs>
-        <echo>To run this application from the command line without Ant, try:</echo>
-        <property location="${dist.jar}" name="dist.jar.resolved"/>
-        <echo>java -jar "${dist.jar.resolved}"</echo>
-    </target>
-    <target name="-post-jar">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries-and-splashscreen,-do-jar-with-libraries,-post-jar" description="Build JAR." name="jar"/>
-    <!--
-                =================
-                EXECUTION SECTION
-                =================
-            -->
-    <target depends="init,compile" description="Run a main class." name="run">
-        <j2seproject1:java>
-            <customize>
-                <arg line="${application.args}"/>
-            </customize>
-        </j2seproject1:java>
-    </target>
-    <target name="-do-not-recompile">
-        <property name="javac.includes.binary" value=""/>
-    </target>
-    <target depends="init,compile-single" name="run-single">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <j2seproject1:java classname="${run.class}"/>
-    </target>
-    <target depends="init,compile-test-single" name="run-test-with-main">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
-    </target>
-    <!--
-                =================
-                DEBUGGING SECTION
-                =================
-            -->
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger">
-        <j2seproject1:nbjpdastart name="${debug.class}"/>
-    </target>
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
-        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
-    </target>
-    <target depends="init,compile" name="-debug-start-debuggee">
-        <j2seproject3:debug>
-            <customize>
-                <arg line="${application.args}"/>
-            </customize>
-        </j2seproject3:debug>
-    </target>
-    <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
-        <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
-    </target>
-    <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
-    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
-        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
-        <j2seproject3:debug classname="${debug.class}"/>
-    </target>
-    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
-    <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
-        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
-        <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
-    </target>
-    <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
-    <target depends="init" name="-pre-debug-fix">
-        <fail unless="fix.includes">Must set fix.includes</fail>
-        <property name="javac.includes" value="${fix.includes}.java"/>
-    </target>
-    <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
-        <j2seproject1:nbjpdareload/>
-    </target>
-    <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
-    <!--
-                ===============
-                JAVADOC SECTION
-                ===============
-            -->
-    <target depends="init" if="have.sources" name="-javadoc-build">
-        <mkdir dir="${dist.javadoc.dir}"/>
-        <javadoc additionalparam="${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
-            <classpath>
-                <path path="${javac.classpath}"/>
-            </classpath>
-            <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
-                <filename name="**/*.java"/>
-            </fileset>
-            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="**/*.java"/>
-            </fileset>
-        </javadoc>
-        <copy todir="${dist.javadoc.dir}">
-            <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
-                <filename name="**/doc-files/**"/>
-            </fileset>
-            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="**/doc-files/**"/>
-            </fileset>
-        </copy>
-    </target>
-    <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
-        <nbbrowse file="${dist.javadoc.dir}/index.html"/>
-    </target>
-    <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
-    <!--
-                =========================
-                JUNIT COMPILATION SECTION
-                =========================
-            -->
-    <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
-        <mkdir dir="${build.test.classes.dir}"/>
-    </target>
-    <target name="-pre-compile-test">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="do.depend.true" name="-compile-test-depend">
-        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir=""/>
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir=""/>
-        <copy todir="${build.test.classes.dir}"/>
-    </target>
-    <target name="-post-compile-test">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
-    <target name="-pre-compile-test-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
-        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
-        <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="" srcdir=""/>
-        <copy todir="${build.test.classes.dir}"/>
-    </target>
-    <target name="-post-compile-test-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
-    <!--
-                =======================
-                JUNIT EXECUTION SECTION
-                =======================
-            -->
-    <target depends="init" if="have.tests" name="-pre-test-run">
-        <mkdir dir="${build.test.results.dir}"/>
-    </target>
-    <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
-        <j2seproject3:junit testincludes="**/*Test.java"/>
-    </target>
-    <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
-        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
-    </target>
-    <target depends="init" if="have.tests" name="test-report"/>
-    <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
-    <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
-    <target depends="init" if="have.tests" name="-pre-test-run-single">
-        <mkdir dir="${build.test.results.dir}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
-        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
-        <j2seproject3:junit excludes="" includes="${test.includes}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
-        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
-    <!--
-                =======================
-                JUNIT DEBUGGING SECTION
-                =======================
-            -->
-    <target depends="init,compile-test" if="have.tests" name="-debug-start-debuggee-test">
-        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
-        <property location="${build.test.results.dir}/TEST-${test.class}.xml" name="test.report.file"/>
-        <delete file="${test.report.file}"/>
-        <mkdir dir="${build.test.results.dir}"/>
-        <j2seproject3:debug classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" classpath="${ant.home}/lib/ant.jar:${ant.home}/lib/ant-junit.jar:${debug.test.classpath}">
-            <customize>
-                <syspropertyset>
-                    <propertyref prefix="test-sys-prop."/>
-                    <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                </syspropertyset>
-                <arg value="${test.class}"/>
-                <arg value="showoutput=true"/>
-                <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter"/>
-                <arg value="formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,${test.report.file}"/>
-            </customize>
-        </j2seproject3:debug>
-    </target>
-    <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
-        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
-    </target>
-    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
-    <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
-        <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
-    </target>
-    <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
-    <!--
-                =========================
-                APPLET EXECUTION SECTION
-                =========================
-            -->
-    <target depends="init,compile-single" name="run-applet">
-        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
-        <j2seproject1:java classname="sun.applet.AppletViewer">
-            <customize>
-                <arg value="${applet.url}"/>
-            </customize>
-        </j2seproject1:java>
-    </target>
-    <!--
-                =========================
-                APPLET DEBUGGING  SECTION
-                =========================
-            -->
-    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
-        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
-        <j2seproject3:debug classname="sun.applet.AppletViewer">
-            <customize>
-                <arg value="${applet.url}"/>
-            </customize>
-        </j2seproject3:debug>
-    </target>
-    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
-    <!--
-                ===============
-                CLEANUP SECTION
-                ===============
-            -->
-    <target name="-deps-clean-init" unless="built-clean.properties">
-        <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
-        <delete file="${built-clean.properties}" quiet="true"/>
-    </target>
-    <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
-        <echo level="warn" message="Cycle detected: JME3TestsTemplate was already built"/>
-    </target>
-    <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
-        <mkdir dir="${build.dir}"/>
-        <touch file="${built-clean.properties}" verbose="false"/>
-        <property file="${built-clean.properties}" prefix="already.built.clean."/>
-        <antcall target="-warn-already-built-clean"/>
-        <propertyfile file="${built-clean.properties}">
-            <entry key="${basedir}" value=""/>
-        </propertyfile>
-    </target>
-    <target depends="init" name="-do-clean">
-        <delete dir="${build.dir}"/>
-        <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
-    </target>
-    <target name="-post-clean">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
-    <target name="-check-call-dep">
-        <property file="${call.built.properties}" prefix="already.built."/>
-        <condition property="should.call.dep">
-            <not>
-                <isset property="already.built.${call.subproject}"/>
-            </not>
-        </condition>
-    </target>
-    <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
-        <ant antfile="${call.script}" inheritall="false" target="${call.target}">
-            <propertyset>
-                <propertyref prefix="transfer."/>
-                <mapper from="transfer.*" to="*" type="glob"/>
-            </propertyset>
-        </ant>
-    </target>
-</project>

+ 0 - 8
sdk/JME3TestsTemplate/nbproject/genfiles.properties

@@ -1,8 +0,0 @@
-build.xml.data.CRC32=0f706f4a
-build.xml.script.CRC32=82b8b23d
[email protected]
-# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
-# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=0f706f4a
-nbproject/build-impl.xml.script.CRC32=46d1a69a
-nbproject/[email protected]

+ 0 - 83
sdk/JME3TestsTemplate/nbproject/project.properties

@@ -1,83 +0,0 @@
-application.title=JME3TestsTemplate
-application.vendor=jMonkeyEngine
-build.classes.dir=${build.dir}/classes
-build.classes.excludes=**/*.java,**/*.form
-# This directory is removed when the project is cleaned:
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-# Only compile against the classpath explicitly listed here:
-build.sysclasspath=ignore
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-# Uncomment to specify the preferred debugger connection transport:
-#debug.transport=dt_socket
-debug.classpath=\
-    ${run.classpath}
-debug.test.classpath=\
-    ${run.test.classpath}
-# This directory is removed when the project is cleaned:
-dist.dir=dist
-dist.jar=${dist.dir}/JME3TestsTemplate.jar
-dist.javadoc.dir=${dist.dir}/javadoc
-endorsed.classpath=
-excludes=
-includes=**
-jar.compress=false
-javac.classpath=\
-    ${libs.jme3-jogg.classpath}:\
-    ${libs.jme3-blender.classpath}:\
-    ${libs.jme3-networking.classpath}:\
-    ${libs.jme3-plugins.classpath}:\
-    ${libs.jme3-core.classpath}:\
-    ${libs.jme3-desktop.classpath}:\
-    ${libs.jme3-lwjgl.classpath}:\
-    ${libs.jme3-niftygui.classpath}:\
-    ${libs.jme3-effects.classpath}:\
-    ${libs.jme3-terrain.classpath}:\
-    ${libs.jme3-jbullet.classpath}:\
-    ${libs.jme3-test-data.classpath}
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.deprecation=false
-javac.source=1.6
-javac.target=1.6
-javac.test.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=
-jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
-jnlp.applet.class=jme3test.awt.TestApplet
-jnlp.applet.height=300
-jnlp.applet.width=300
-jnlp.codebase.type=local
-jnlp.descriptor=application
-jnlp.enabled=false
-jnlp.offline-allowed=false
-jnlp.signed=false
-main.class=jme3test.TestChooser
-manifest.file=manifest.mf
-meta.inf.dir=${src.dir}/META-INF
-platform.active=default_platform
-run.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-# Space-separated list of JVM arguments used when running the project
-# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
-# or test-sys-prop.name=value to set system properties for unit tests):
-run.jvmargs=
-run.test.classpath=\
-    ${javac.test.classpath}:\
-    ${build.test.classes.dir}
-source.encoding=UTF-8
-src.dir=src

+ 0 - 13
sdk/JME3TestsTemplate/nbproject/project.xml

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.java.j2seproject</type>
-    <configuration>
-        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
-            <name>JME3TestsTemplate</name>
-            <source-roots>
-                <root id="src.dir" name="JME3 Examples"/>
-            </source-roots>
-            <test-roots/>
-        </data>
-    </configuration>
-</project>

+ 0 - 1
sdk/JME3TestsTemplateAndroid/MANIFEST.MF

@@ -1 +0,0 @@
-X-Comment: Created with jMonkeyPlatform

+ 0 - 76
sdk/JME3TestsTemplateAndroid/build.xml

@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- You may freely edit this file. See commented blocks below for -->
-<!-- some examples of how to customize the build. -->
-<!-- (If you delete it and reopen the project it will be recreated.) -->
-<!-- By default, only the Clean and Build commands use this build script. -->
-<!-- Commands such as Run, Debug, and Test only use this build script if -->
-<!-- the Compile on Save feature is turned off for the project. -->
-<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
-<!-- in the project's Project Properties dialog box.-->
-<project name="BasicGameTemplate" default="default" basedir=".">
-    <description>Builds, tests, and runs the project BasicGameTemplate.</description>
-    <import file="nbproject/build-impl.xml"/>
-
-    <!--
-
-    There exist several targets which are by default empty and which can be
-    used for execution of your tasks. These targets are usually executed
-    before and after some main targets. They are:
-
-      -pre-init:                 called before initialization of project properties
-      -post-init:                called after initialization of project properties
-      -pre-compile:              called before javac compilation
-      -post-compile:             called after javac compilation
-      -pre-compile-single:       called before javac compilation of single file
-      -post-compile-single:      called after javac compilation of single file
-      -pre-compile-test:         called before javac compilation of JUnit tests
-      -post-compile-test:        called after javac compilation of JUnit tests
-      -pre-compile-test-single:  called before javac compilation of single JUnit test
-      -post-compile-test-single: called after javac compilation of single JUunit test
-      -pre-jar:                  called before JAR building
-      -post-jar:                 called after JAR building
-      -post-clean:               called after cleaning build products
-
-    (Targets beginning with '-' are not intended to be called on their own.)
-
-    Example of inserting an obfuscator after compilation could look like this:
-
-        <target name="-post-compile">
-            <obfuscate>
-                <fileset dir="${build.classes.dir}"/>
-            </obfuscate>
-        </target>
-
-    For list of available properties check the imported
-    nbproject/build-impl.xml file.
-
-
-    Another way to customize the build is by overriding existing main targets.
-    The targets of interest are:
-
-      -init-macrodef-javac:     defines macro for javac compilation
-      -init-macrodef-junit:     defines macro for junit execution
-      -init-macrodef-debug:     defines macro for class debugging
-      -init-macrodef-java:      defines macro for class execution
-      -do-jar-with-manifest:    JAR building (if you are using a manifest)
-      -do-jar-without-manifest: JAR building (if you are not using a manifest)
-      run:                      execution of project
-      -javadoc-build:           Javadoc generation
-      test-report:              JUnit report generation
-
-    An example of overriding the target for project execution could look like this:
-
-        <target name="run" depends="BasicGameTemplate-impl.jar">
-            <exec dir="bin" executable="launcher.exe">
-                <arg file="${dist.jar}"/>
-            </exec>
-        </target>
-
-    Notice that the overridden target depends on the jar target and not only on
-    the compile target as the regular run target does. Again, for a list of available
-    properties which you can use, check the target you are overriding in the
-    nbproject/build-impl.xml file.
-
-    -->
-
-</project>

+ 0 - 22
sdk/JME3TestsTemplateAndroid/master-application.jnlp

@@ -1,22 +0,0 @@
-<jnlp spec="1.0+" codebase="${jnlp.codebase}" href="launch.jnlp">
-    <information>
-        <title>${APPLICATION.TITLE}</title>
-        <vendor>${APPLICATION.VENDOR}</vendor>
-        <homepage href="${APPLICATION.HOMEPAGE}"/>
-        <description>${APPLICATION.DESC}</description>
-        <description kind="short">${APPLICATION.DESC.SHORT}</description>
-<!--${JNLP.ICONS}-->
-<!--${JNLP.OFFLINE.ALLOWED}-->
-    </information>
-<!--${JNLP.SECURITY}-->
-    <resources>
-<!--${JNLP.RESOURCES.RUNTIME}-->
-<!--${JNLP.RESOURCES.MAIN.JAR}-->
-<!--${JNLP.RESOURCES.JARS}-->
-<jar href='lib/assets.jar'/>
-<!--${JNLP.RESOURCES.EXTENSIONS}-->
-    </resources>
-    <application-desc main-class="${jnlp.main.class}">
-<!--${JNLP.APPLICATION.ARGS}-->
-    </application-desc>
-</jnlp>

+ 0 - 15
sdk/JME3TestsTemplateAndroid/mobile/AndroidManifest.xml

@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.jmonkeyengine.tests">
-    <application android:label="@string/app_name">
-        <activity android:label="@string/app_name" android:name="MainActivity" android:launchMode="singleTask">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity android:label="@string/app_name" android:name="TestsHarness" android:launchMode="singleTask" android:screenOrientation="landscape">
-        </activity>
-    </application>
-    <uses-sdk android:minSdkVersion="8"/>
-    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
-</manifest>

+ 0 - 17
sdk/JME3TestsTemplateAndroid/mobile/ant.properties

@@ -1,17 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked into Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-#  'source.dir' for the location of your java source folder and
-#  'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-#  'key.store' for the location of your keystore and
-#  'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-

+ 0 - 92
sdk/JME3TestsTemplateAndroid/mobile/build.xml

@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="jMonkeyEngine Test Applications" default="help">
-
-    <!-- The local.properties file is created and updated by the 'android' tool.
-         It contains the path to the SDK. It should *NOT* be checked into
-         Version Control Systems. -->
-    <property file="local.properties" />
-
-    <!-- The ant.properties file can be created by you. It is only edited by the
-         'android' tool to add properties to it.
-         This is the place to change some Ant specific build properties.
-         Here are some properties you may want to change/update:
-
-         source.dir
-             The name of the source directory. Default is 'src'.
-         out.dir
-             The name of the output directory. Default is 'bin'.
-
-         For other overridable properties, look at the beginning of the rules
-         files in the SDK, at tools/ant/build.xml
-
-         Properties related to the SDK location or the project target should
-         be updated using the 'android' tool with the 'update' action.
-
-         This file is an integral part of the build system for your
-         application and should be checked into Version Control Systems.
-
-         -->
-    <property file="ant.properties" />
-
-    <!-- if sdk.dir was not set from one of the property file, then
-         get it from the ANDROID_HOME env var.
-         This must be done before we load project.properties since
-         the proguard config can use sdk.dir -->
-    <property environment="env" />
-    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
-        <isset property="env.ANDROID_HOME" />
-    </condition>
-
-    <!-- The project.properties file is created and updated by the 'android'
-         tool, as well as ADT.
-
-         This contains project specific properties such as project target, and library
-         dependencies. Lower level build properties are stored in ant.properties
-         (or in .classpath for Eclipse projects).
-
-         This file is an integral part of the build system for your
-         application and should be checked into Version Control Systems. -->
-    <loadproperties srcFile="project.properties" />
-
-    <!-- quick check on sdk.dir -->
-    <fail
-            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
-            unless="sdk.dir"
-    />
-
-    <!--
-        Import per project custom build rules if present at the root of the project.
-        This is the place to put custom intermediary targets such as:
-            -pre-build
-            -pre-compile
-            -post-compile (This is typically used for code obfuscation.
-                           Compiled code location: ${out.classes.absolute.dir}
-                           If this is not done in place, override ${out.dex.input.absolute.dir})
-            -post-package
-            -post-build
-            -pre-clean
-    -->
-    <import file="custom_rules.xml" optional="true" />
-
-    <!-- Import the actual build file.
-
-         To customize existing targets, there are two options:
-         - Customize only one target:
-             - copy/paste the target into this file, *before* the
-               <import> task.
-             - customize it to your needs.
-         - Customize the whole content of build.xml
-             - copy/paste the content of the rules files (minus the top node)
-               into this file, replacing the <import> task.
-             - customize to your needs.
-
-         ***********************
-         ****** IMPORTANT ******
-         ***********************
-         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
-         in order to avoid having your file be overridden by tools such as "android update project"
-    -->
-    <!-- version-tag: 1 -->
-    <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>

+ 0 - 20
sdk/JME3TestsTemplateAndroid/mobile/proguard-project.txt

@@ -1,20 +0,0 @@
-# To enable ProGuard in your project, edit project.properties
-# to define the proguard.config property as described in that file.
-#
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in ${sdk.dir}/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}

+ 0 - 14
sdk/JME3TestsTemplateAndroid/mobile/project.properties

@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-8

BIN
sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256.png


BIN
sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256_9.9.png


BIN
sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512.png


BIN
sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512_9.9.png


BIN
sdk/JME3TestsTemplateAndroid/mobile/res/drawable/nonselected.png


Неке датотеке нису приказане због велике количине промена