diff --git a/.travis.yml b/.travis.yml index 48e75d0b4..fffa1ec68 100644 --- a/.travis.yml +++ b/.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 || :' diff --git a/bintray.gradle b/bintray.gradle new file mode 100644 index 000000000..0deafc8cf --- /dev/null +++ b/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/) +} diff --git a/build.gradle b/build.gradle index 192bc32d3..a4a4259a6 100644 --- a/build.gradle +++ b/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 // } -//} \ No newline at end of file +//} diff --git a/common.gradle b/common.gradle index 237ee5e7f..d275c19f5 100644 --- a/common.gradle +++ b/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 } } diff --git a/gradle.properties b/gradle.properties index 4380c31a0..f4c74445a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,3 +37,7 @@ POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com: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= diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index 58114cf28..c96807ed0 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/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 features = (List) linkedFeatures.get("meshes"); - if(features != null) { - for(Node feature : features) { - if(featureName.equals(feature.getName())) { - return feature; + List temporalMeshes = (List) linkedFeatures.get("meshes"); + if(temporalMeshes != null) { + for(TemporalMesh temporalMesh : temporalMeshes) { + if(featureName.equals(temporalMesh.getName())) { + return temporalMesh; } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index 69e087a28..69c0a6f50 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -174,7 +174,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { private static class BonesChain extends ArrayList { private static final long serialVersionUID = -1850524345643600718L; - private List bonesMatrices = new ArrayList(); + private List localBonesMatrices = new ArrayList(); public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection alteredOmas, BlenderContext blenderContext) { if (bone != null) { @@ -187,12 +187,21 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { this.add(boneContext); alteredOmas.add(boneContext.getBoneOma()); - Space space = this.size() < bonesAffected ? Space.CONSTRAINT_SPACE_LOCAL : Space.CONSTRAINT_SPACE_WORLD; - Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), space); - bonesMatrices.add(new DTransform(transform).toMatrix()); + Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); + localBonesMatrices.add(new DTransform(transform).toMatrix()); bone = bone.getParent(); } + + if(localBonesMatrices.size() > 0) { + // making the matrices describe the local transformation + Matrix parentWorldMatrix = localBonesMatrices.get(localBonesMatrices.size() - 1); + for(int i=localBonesMatrices.size() - 2;i>=0;--i) { + SimpleMatrix m = parentWorldMatrix.invert().mult(localBonesMatrices.get(i)); + parentWorldMatrix = localBonesMatrices.get(i); + localBonesMatrices.set(i, new Matrix(m)); + } + } } } @@ -211,16 +220,16 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix); boneMatrix = new Matrix(m); } - bonesMatrices.set(index, boneMatrix); + localBonesMatrices.set(index, boneMatrix); } public Matrix getWorldMatrix(int index) { if (index == this.size() - 1) { - return new Matrix(bonesMatrices.get(this.size() - 1)); + return new Matrix(localBonesMatrices.get(this.size() - 1)); } SimpleMatrix result = this.getWorldMatrix(index + 1); - result = result.mult(bonesMatrices.get(index)); + result = result.mult(localBonesMatrices.get(index)); return new Matrix(result); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java index 41117bf85..51a5aeb63 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java @@ -180,8 +180,8 @@ public class CurvesTemporalMesh extends TemporalMesh { if (bevelObject != null && beziers.size() > 0) { this.append(this.applyBevelAndTaper(this, bevelObject, taperObject, blenderContext)); } else { - int originalVerticesAmount = vertices.size(); for (BezierLine bezierLine : beziers) { + int originalVerticesAmount = vertices.size(); vertices.add(bezierLine.vertices[0]); Vector3f v = bezierLine.vertices[1].subtract(bezierLine.vertices[0]).normalizeLocal(); float temp = v.x; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java index 2291b5e56..1d76fc02f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ b/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 true 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 true to find a crossing point along the whole + * straight that contains the current edge + * @param extendSecondEdge + * set to true 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() { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java index 9dc851a58..a41d58ff0 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java @@ -276,30 +276,45 @@ public class Face implements Comparator { List facesToTriangulate = new ArrayList(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 { } } } - + /** * @return true if the face is smooth and false otherwise */ @@ -335,17 +350,23 @@ public class Face implements Comparator { return "Face " + indexes; } - /** - * The method finds the closest vertex to the one specified by index. - * 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 index. + * 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 vertices = temporalMesh.getVertices(); @@ -355,7 +376,7 @@ public class Face implements Comparator { 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 { 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 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 { } } - // 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 crossingVectors = new ArrayList(); + 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 distinctCrossingVectors = new ArrayList(); + 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 FastMath.ZERO_TOLERANCE) rigidBody.setLinearVelocity(velocity); if (jump) { //TODO: precalculate jump force Vector3f rotatedJumpForce = vars.vect1; diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java index e5215506f..4ba6152c9 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java +++ b/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); } diff --git a/jme3-core/src/main/java/com/jme3/app/Application.java b/jme3-core/src/main/java/com/jme3/app/Application.java index 81fdb6d3b..9688a5d1e 100644 --- a/jme3-core/src/main/java/com/jme3/app/Application.java +++ b/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 Future enqueue(Callable callable) { AppTask task = new AppTask(callable); taskQueue.add(task); return task; } + + /** + * Enqueues a runnable object to execute in the jME3 + * rendering thread. + *

+ * 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; + } + + } + } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 8664af6c7..2c5cc5b98 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/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 AudioNode 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 {@link com.jme3.audio.AudioData.DataType}.Stream, the audio will be streamed gradually from disk, + * otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer) + */ + public AudioNode(AssetManager assetManager, String name, DataType type) { + this(assetManager, name, type == DataType.Stream, true); + } /** * Creates a new AudioNode 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() + * Warning: 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. diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java index 5db94b409..2d7a4dc24 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java +++ b/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); } } diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 28bfd595d..ca3467781 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/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(); diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java index 8f32c2d6b..7d7901911 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -135,6 +135,14 @@ public class DefaultJoystickAxis implements JoystickAxis { return deadZone; } + /** + * Sets/overrides the dead zone for this axis. This indicates that values + * within +/- deadZone should be ignored. + */ + public void setDeadZone( float f ) { + this.deadZone = f; + } + @Override public String toString(){ return "JoystickAxis[name=" + name + ", parent=" + parent.getName() + ", id=" + axisIndex diff --git a/jme3-core/src/main/java/com/jme3/input/InputManager.java b/jme3-core/src/main/java/com/jme3/input/InputManager.java index 660731a08..b21d225bb 100644 --- a/jme3-core/src/main/java/com/jme3/input/InputManager.java +++ b/jme3-core/src/main/java/com/jme3/input/InputManager.java @@ -39,6 +39,7 @@ import com.jme3.math.FastMath; import com.jme3.math.Vector2f; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; +import com.jme3.util.SafeArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; @@ -96,16 +97,15 @@ public class InputManager implements RawInputListener { private boolean eventsPermitted = false; private boolean mouseVisible = true; private boolean safeMode = false; - private float axisDeadZone = 0.05f; - private Vector2f cursorPos = new Vector2f(); + private float globalAxisDeadZone = 0.05f; + private final Vector2f cursorPos = new Vector2f(); private Joystick[] joysticks; private final IntMap> bindings = new IntMap>(); private final HashMap mappings = new HashMap(); private final IntMap pressedButtons = new IntMap(); private final IntMap axisValues = new IntMap(); - private ArrayList rawListeners = new ArrayList(); - private RawInputListener[] rawListenerArray = null; - private ArrayList inputQueue = new ArrayList(); + private final SafeArrayList rawListeners = new SafeArrayList(RawInputListener.class); + private final ArrayList inputQueue = new ArrayList(); private static class Mapping { @@ -248,8 +248,8 @@ public class InputManager implements RawInputListener { } } - private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) { - if (value < axisDeadZone) { + private void invokeAnalogsAndActions(int hash, float value, float effectiveDeadZone, boolean applyTpf) { + if (value < effectiveDeadZone) { invokeAnalogs(hash, value, !applyTpf); return; } @@ -287,12 +287,14 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void beginInput() { } /** * Callback from RawInputListener. Do not use. */ + @Override public void endInput() { } @@ -304,17 +306,18 @@ public class InputManager implements RawInputListener { int joyId = evt.getJoyIndex(); int axis = evt.getAxisIndex(); float value = evt.getValue(); - if (value < axisDeadZone && value > -axisDeadZone) { + float effectiveDeadZone = Math.max(globalAxisDeadZone, evt.getAxis().getDeadZone()); + if (value < effectiveDeadZone && value > -effectiveDeadZone) { int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true); int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false); Float val1 = axisValues.get(hash1); Float val2 = axisValues.get(hash2); - if (val1 != null && val1.floatValue() > axisDeadZone) { + if (val1 != null && val1 > effectiveDeadZone) { invokeActions(hash1, false); } - if (val2 != null && val2.floatValue() > axisDeadZone) { + if (val2 != null && val2 > effectiveDeadZone) { invokeActions(hash2, false); } @@ -328,11 +331,11 @@ public class InputManager implements RawInputListener { // Clear the reverse direction's actions in case we // crossed center too quickly Float otherVal = axisValues.get(otherHash); - if (otherVal != null && otherVal.floatValue() > axisDeadZone) { + if (otherVal != null && otherVal > effectiveDeadZone) { invokeActions(otherHash, false); } - invokeAnalogsAndActions(hash, -value, true); + invokeAnalogsAndActions(hash, -value, effectiveDeadZone, true); axisValues.put(hash, -value); axisValues.remove(otherHash); } else { @@ -342,11 +345,11 @@ public class InputManager implements RawInputListener { // Clear the reverse direction's actions in case we // crossed center too quickly Float otherVal = axisValues.get(otherHash); - if (otherVal != null && otherVal.floatValue() > axisDeadZone) { + if (otherVal != null && otherVal > effectiveDeadZone) { invokeActions(otherHash, false); } - invokeAnalogsAndActions(hash, value, true); + invokeAnalogsAndActions(hash, value, effectiveDeadZone, true); axisValues.put(hash, value); axisValues.remove(otherHash); } @@ -355,6 +358,7 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void onJoyAxisEvent(JoyAxisEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); @@ -376,6 +380,7 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void onJoyButtonEvent(JoyButtonEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); @@ -391,15 +396,15 @@ public class InputManager implements RawInputListener { if (evt.getDX() != 0) { float val = Math.abs(evt.getDX()) / 1024f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false); + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, globalAxisDeadZone, false); } if (evt.getDY() != 0) { float val = Math.abs(evt.getDY()) / 1024f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false); + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, globalAxisDeadZone, false); } if (evt.getDeltaWheel() != 0) { float val = Math.abs(evt.getDeltaWheel()) / 100f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false); + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, globalAxisDeadZone, false); } } @@ -419,6 +424,7 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void onMouseMotionEvent(MouseMotionEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); @@ -437,6 +443,7 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void onMouseButtonEvent(MouseButtonEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); @@ -459,6 +466,7 @@ public class InputManager implements RawInputListener { /** * Callback from RawInputListener. Do not use. */ + @Override public void onKeyEvent(KeyInputEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time."); @@ -477,7 +485,7 @@ public class InputManager implements RawInputListener { * @param deadZone the deadzone for joystick axes. */ public void setAxisDeadZone(float deadZone) { - this.axisDeadZone = deadZone; + this.globalAxisDeadZone = deadZone; } /** @@ -486,7 +494,7 @@ public class InputManager implements RawInputListener { * @return the deadzone for joystick axes. */ public float getAxisDeadZone() { - return axisDeadZone; + return globalAxisDeadZone; } /** @@ -721,7 +729,6 @@ public class InputManager implements RawInputListener { */ public void addRawInputListener(RawInputListener listener) { rawListeners.add(listener); - rawListenerArray = null; } /** @@ -734,7 +741,6 @@ public class InputManager implements RawInputListener { */ public void removeRawInputListener(RawInputListener listener) { rawListeners.remove(listener); - rawListenerArray = null; } /** @@ -744,13 +750,6 @@ public class InputManager implements RawInputListener { */ public void clearRawInputListeners() { rawListeners.clear(); - rawListenerArray = null; - } - - private RawInputListener[] getRawListenerArray() { - if (rawListenerArray == null) - rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]); - return rawListenerArray; } /** @@ -813,7 +812,7 @@ public class InputManager implements RawInputListener { private void processQueue() { int queueSize = inputQueue.size(); - RawInputListener[] array = getRawListenerArray(); + RawInputListener[] array = rawListeners.getArray(); for (RawInputListener listener : array) { listener.beginInput(); diff --git a/jme3-core/src/main/java/com/jme3/input/KeyInput.java b/jme3-core/src/main/java/com/jme3/input/KeyInput.java index f9191c6a7..9ad78ed7b 100644 --- a/jme3-core/src/main/java/com/jme3/input/KeyInput.java +++ b/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; } diff --git a/jme3-core/src/main/java/com/jme3/input/KeyNames.java b/jme3-core/src/main/java/com/jme3/input/KeyNames.java index a1e1a41a8..563c3a5f8 100644 --- a/jme3-core/src/main/java/com/jme3/input/KeyNames.java +++ b/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]; } } diff --git a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java index 855f2e49a..84cdc17fc 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -70,6 +70,11 @@ public final class DefaultLightFilter implements LightFilter { for (int i = 0; i < worldLights.size(); i++) { Light light = worldLights.get(i); + // If this light is not enabled it will be ignored. + if (!light.isEnabled()) { + continue; + } + if (light.frustumCheckNeeded) { processedLights.add(light); light.frustumCheckNeeded = false; diff --git a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java index b1abb65cf..6613b8428 100644 --- a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java +++ b/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; + } } diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 24d8ccee3..759eea14e 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/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 @@ -110,9 +110,6 @@ public abstract class Light implements Savable, Cloneable { */ protected transient float lastDistance = -1; - /** - * If light is disabled, it will not have any - */ protected boolean enabled = true; /** @@ -176,20 +173,24 @@ public abstract class Light implements Savable, Cloneable { this.color.set(color); } - - /* - * Returns true if the light is enabled - * - * @return true if the light is enabled - * - * @see Light#setEnabled(boolean) + + /** + * Returns true if this light is enabled. + * @return true if enabled, otherwise false. */ - /* public boolean isEnabled() { return enabled; } - */ - + + /** + * Set to false in order to disable a light and have it filtered out from being included in rendering. + * + * @param enabled true to enable and false to disable the light. + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + /** * Determines if the light intersects with the given bounding box. *

@@ -234,7 +235,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(); } diff --git a/jme3-core/src/main/java/com/jme3/light/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java index 4b5224c30..2e8a57882 100644 --- a/jme3-core/src/main/java/com/jme3/light/PointLight.java +++ b/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; + } } diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index bc1335b5b..982488687 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/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; + } } diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 7a86031b5..5f4a4010b 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -1295,12 +1295,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); diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index f34d1d70f..e1361b26c 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -34,12 +34,8 @@ package com.jme3.renderer; import com.jme3.light.DefaultLightFilter; import com.jme3.light.LightFilter; import com.jme3.light.LightList; -import com.jme3.material.Material; -import com.jme3.material.MaterialDef; -import com.jme3.material.RenderState; -import com.jme3.material.Technique; -import com.jme3.material.TechniqueDef; -import com.jme3.math.*; +import com.jme3.material.*; +import com.jme3.math.Matrix4f; import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; @@ -55,6 +51,7 @@ import com.jme3.shader.UniformBindingManager; import com.jme3.system.NullRenderer; import com.jme3.system.Timer; import com.jme3.util.SafeArrayList; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -533,7 +530,6 @@ public class RenderManager { lightFilter.filterLights(g, filteredLightList); lightList = filteredLightList; } - //if forcedTechnique we try to force it for render, //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null @@ -556,7 +552,7 @@ public class RenderManager { forcedRenderState = tmpRs; //Reverted this part from revision 6197 - //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered + //If forcedTechnique does not exists, and forcedMaterial is not set, the geom MUST NOT be rendered } else if (forcedMaterial != null) { // use forced material forcedMaterial.render(g, lightList, this); @@ -641,10 +637,8 @@ public class RenderManager { *

* In addition to enqueuing the visible geometries, this method * also scenes which cast or receive shadows, by putting them into the - * RenderQueue's - * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) - * shadow queue}. Each Spatial which has its - * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} + * RenderQueue's {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}. + * Each Spatial which has its {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} * set to not off, will be put into the appropriate shadow queue, note that * this process does not check for frustum culling on any * {@link ShadowMode#Cast shadow casters}, as they don't have to be @@ -793,7 +787,8 @@ public class RenderManager { * @param singlePassLightBatchSize the number of lights. */ public void setSinglePassLightBatchSize(int singlePassLightBatchSize) { - this.singlePassLightBatchSize = singlePassLightBatchSize; + // Ensure the batch size is no less than 1 + this.singlePassLightBatchSize = singlePassLightBatchSize < 1 ? 1 : singlePassLightBatchSize; } @@ -999,13 +994,12 @@ public class RenderManager { * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) }) *

  • If any objects remained in the render queue, they are removed * from the queue. This is generally objects added to the - * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) - * shadow queue} + * {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue} * which were not rendered because of a missing shadow renderer.
  • * * - * @param vp - * @param tpf + * @param vp View port to render + * @param tpf Time per frame value */ public void renderViewPort(ViewPort vp, float tpf) { if (!vp.isEnabled()) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java index 9d44e6566..6e6d07e84 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java @@ -36,7 +36,6 @@ import com.jme3.shader.Shader; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.util.IntMap; -import java.util.HashSet; /** * The statistics class allows tracking of real-time rendering statistics. diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index c3a0ab4bb..e1d4cce7f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/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 batches = new SafeArrayList(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 batchesByGeom = new HashMap(); /** @@ -118,7 +119,6 @@ public class BatchNode extends GeometryGroupNode { public void onGeoemtryUnassociated(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> matMap = new HashMap>(); + Map> matMap = new HashMap>(); 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> map, Spatial n, boolean rebatch) { + private void gatherGeometries(Map> 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; + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index 74b14188d..f269446a5 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/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; } diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java index 009b41c64..d6eb8dc20 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java @@ -198,35 +198,42 @@ public class Curve extends Mesh { * points */ private void createNurbMesh(int nbSubSegments) { - float minKnot = spline.getMinNurbKnot(); - float maxKnot = spline.getMaxNurbKnot(); - float deltaU = (maxKnot - minKnot) / nbSubSegments; + if(spline.getControlPoints() != null && spline.getControlPoints().size() > 0) { + if(nbSubSegments == 0) { + nbSubSegments = spline.getControlPoints().size() + 1; + } else { + nbSubSegments = spline.getControlPoints().size() * nbSubSegments + 1; + } + float minKnot = spline.getMinNurbKnot(); + float maxKnot = spline.getMaxNurbKnot(); + float deltaU = (maxKnot - minKnot) / nbSubSegments; - float[] array = new float[(nbSubSegments + 1) * 3]; + float[] array = new float[(nbSubSegments + 1) * 3]; - float u = minKnot; - Vector3f interpolationResult = new Vector3f(); - for (int i = 0; i < array.length; i += 3) { - spline.interpolate(u, 0, interpolationResult); - array[i] = interpolationResult.x; - array[i + 1] = interpolationResult.y; - array[i + 2] = interpolationResult.z; - u += deltaU; - } + float u = minKnot; + Vector3f interpolationResult = new Vector3f(); + for (int i = 0; i < array.length; i += 3) { + spline.interpolate(u, 0, interpolationResult); + array[i] = interpolationResult.x; + array[i + 1] = interpolationResult.y; + array[i + 2] = interpolationResult.z; + u += deltaU; + } - //calculating indexes - int i = 0; - short[] indices = new short[nbSubSegments << 1]; - for (int j = 0; j < nbSubSegments; ++j) { - indices[i++] = (short) j; - indices[i++] = (short) (j + 1); - } + //calculating indexes + int i = 0; + short[] indices = new short[nbSubSegments << 1]; + for (int j = 0; j < nbSubSegments; ++j) { + indices[i++] = (short) j; + indices[i++] = (short) (j + 1); + } - this.setMode(Mesh.Mode.Lines); - this.setBuffer(VertexBuffer.Type.Position, 3, array); - this.setBuffer(VertexBuffer.Type.Index, 2, indices); - this.updateBound(); - this.updateCounts(); + this.setMode(Mesh.Mode.Lines); + this.setBuffer(VertexBuffer.Type.Position, 3, array); + this.setBuffer(VertexBuffer.Type.Index, 2, indices); + this.updateBound(); + this.updateCounts(); + } } private void createLinearMesh() { diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index 2d7071c9c..05ee2b4bd 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -963,7 +963,7 @@ public final class AppSettings extends HashMap { return getString("SettingsDialogImage"); } - public boolean getGammaCorrection() { + public boolean isGammaCorrection() { return getBoolean("GammaCorrection"); } diff --git a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java index c79a4675d..2d70d53d7 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java @@ -51,7 +51,8 @@ public class DefaultImageRaster extends ImageRaster { private void rangeCheck(int x, int y) { if (x < 0 || y < 0 || x >= width || y >= height) { - throw new IllegalArgumentException("x and y must be inside the image dimensions"); + throw new IllegalArgumentException("x and y must be inside the image dimensions:" + + x + ", " + y + " in:" + width + ", " + height); } } diff --git a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java index 6fc0c50f0..24af81bf6 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -401,6 +401,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. * diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert index 9c2733615..705936943 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert +++ b/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 -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index b4f1b77bd..73d0f504f 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/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 diff --git a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java index 48e809a63..98347067c 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java +++ b/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); diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java index 69f2c18ac..e2d64c8b9 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ b/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; diff --git a/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java b/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java new file mode 100644 index 000000000..88ce27732 --- /dev/null +++ b/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(); + } + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java index d3ba9b7c6..005634bcd 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java +++ b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java @@ -65,25 +65,23 @@ public class TestManyLightsSingle extends SimpleApplication { TestManyLightsSingle app = new TestManyLightsSingle(); app.start(); } - + /** * Switch mode with space bar at run time */ TechniqueDef.LightMode lm = TechniqueDef.LightMode.SinglePass; - int lightNum = 6; @Override public void simpleInitApp() { renderManager.setPreferredLightMode(lm); - renderManager.setSinglePassLightBatchSize(lightNum); - + renderManager.setSinglePassLightBatchSize(6); flyCam.setMoveSpeed(10); Node scene = (Node) assetManager.loadModel("Scenes/ManyLights/Main.scene"); rootNode.attachChild(scene); Node n = (Node) rootNode.getChild(0); - LightList lightList = n.getWorldLightList(); + final LightList lightList = n.getWorldLightList(); final Geometry g = (Geometry) n.getChild("Grid-geom-1"); g.getMaterial().setColor("Ambient", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); @@ -152,8 +150,6 @@ public class TestManyLightsSingle extends SimpleApplication { // guiNode.setCullHint(CullHint.Always); - - flyCam.setDragToRotate(true); flyCam.setMoveSpeed(50); @@ -168,27 +164,35 @@ public class TestManyLightsSingle extends SimpleApplication { helloText.setText("(Multi pass)"); } else { lm = TechniqueDef.LightMode.SinglePass; - helloText.setText("(Single pass) nb lights per batch : " + lightNum); + helloText.setText("(Single pass) nb lights per batch : " + renderManager.getSinglePassLightBatchSize()); } renderManager.setPreferredLightMode(lm); - reloadScene(g,boxGeo,cubeNodes); + reloadScene(g, boxGeo, cubeNodes); } if (name.equals("lightsUp") && isPressed) { - lightNum++; - renderManager.setSinglePassLightBatchSize(lightNum); - helloText.setText("(Single pass) nb lights per batch : " + lightNum); + renderManager.setSinglePassLightBatchSize(renderManager.getSinglePassLightBatchSize() + 1); + helloText.setText("(Single pass) nb lights per batch : " + renderManager.getSinglePassLightBatchSize()); } if (name.equals("lightsDown") && isPressed) { - lightNum--; - renderManager.setSinglePassLightBatchSize(lightNum); - helloText.setText("(Single pass) nb lights per batch : " + lightNum); + renderManager.setSinglePassLightBatchSize(renderManager.getSinglePassLightBatchSize() - 1); + helloText.setText("(Single pass) nb lights per batch : " + renderManager.getSinglePassLightBatchSize()); + } + if (name.equals("toggleOnOff") && isPressed) { + for (final Light light : lightList) { + if (light instanceof AmbientLight) { + continue; + } + + light.setEnabled(!light.isEnabled()); + } } } - }, "toggle", "lightsUp", "lightsDown"); + }, "toggle", "lightsUp", "lightsDown", "toggleOnOff"); inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addMapping("lightsUp", new KeyTrigger(KeyInput.KEY_UP)); inputManager.addMapping("lightsDown", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("toggleOnOff", new KeyTrigger(KeyInput.KEY_L)); SpotLight spot = new SpotLight(); @@ -215,12 +219,9 @@ public class TestManyLightsSingle extends SimpleApplication { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); helloText = new BitmapText(guiFont, false); helloText.setSize(guiFont.getCharSet().getRenderedSize()); - helloText.setText("(Single pass) nb lights per batch : " + lightNum); + helloText.setText("(Single pass) nb lights per batch : " + renderManager.getSinglePassLightBatchSize()); helloText.setLocalTranslation(300, helloText.getLineHeight(), 0); guiNode.attachChild(helloText); - - - } protected void reloadScene(Geometry g, Geometry boxGeo, Node cubeNodes) { @@ -234,7 +235,7 @@ public class TestManyLightsSingle extends SimpleApplication { cubeNodes.setMaterial(m); } } - + BitmapText helloText; long time; long nbFrames; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java index 5cebd5edb..3d5463421 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java +++ b/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) { diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java index 497fa8edb..3ed543bc9 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ b/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; } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index ee12d1fea..3f1139886 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -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; } diff --git a/jme3-lwjgl3/build.gradle b/jme3-lwjgl3/build.gradle index 3ee3b0f65..b70cd4303 100644 --- a/jme3-lwjgl3/build.gradle +++ b/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 files('lib/lwjgl-3.0.0b-35.jar', 'lib/lwjgl-3.0.0b-35-natives.jar') -} + + 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" +} \ No newline at end of file diff --git a/jme3-lwjgl3/lib/lwjgl-3.0.0b-35-natives.jar b/jme3-lwjgl3/lib/lwjgl-3.0.0b-35-natives.jar deleted file mode 100644 index 79b12bc5d..000000000 Binary files a/jme3-lwjgl3/lib/lwjgl-3.0.0b-35-natives.jar and /dev/null differ diff --git a/jme3-lwjgl3/lib/lwjgl-3.0.0b-35.jar b/jme3-lwjgl3/lib/lwjgl-3.0.0b-35.jar deleted file mode 100644 index d1466e8aa..000000000 Binary files a/jme3-lwjgl3/lib/lwjgl-3.0.0b-35.jar and /dev/null differ diff --git a/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java b/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java index b99a939b8..c9812d7df 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java +++ b/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); } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java index 20cd07672..cd87cc70a 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java +++ b/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); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java new file mode 100644 index 000000000..39592fe60 --- /dev/null +++ b/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]; + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java index 05a2df5db..bf5478cba 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java +++ b/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 mouseMotionEvents = new LinkedList(); private Queue mouseButtonEvents = new LinkedList(); - public GlfwMouseInput(final LwjglWindow context) { + private Map jmeToGlfwCursorMap = new HashMap(); + + 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; } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 61ea20b71..a74b19cdb 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -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,16 +83,16 @@ 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); } @@ -180,11 +179,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 +197,6 @@ public abstract class LwjglContext implements JmeContext { if (joyInput != null) { joyInput.initialize(); } - renderable.set(true); } @@ -240,26 +238,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; } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index e1f4dcf73..48cdd1cab 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -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; @@ -102,7 +102,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 +127,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,6 +178,34 @@ 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) { @@ -197,8 +225,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 @@ -216,14 +246,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { 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,12 +262,24 @@ 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 (window != 0) { + if (windowFocusCallback != null) { + windowFocusCallback.release(); + windowFocusCallback = null; + } + + if (window != NULL) { glfwDestroyWindow(window); + window = NULL; } } catch (Exception ex) { listener.handleError("Failed to destroy context", ex); @@ -296,8 +331,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 +354,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 +383,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 +412,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { } } } + + glfwPollEvents(); } private void setFrameRateLimit(int frameRateLimit) { @@ -389,11 +426,12 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { */ protected void deinitInThread() { + listener.destroy(); + destroyContext(); + super.internalDestroy(); - listener.destroy(); LOGGER.fine("Display destroyed."); - super.internalDestroy(); } public void run() { @@ -403,7 +441,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { } 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 +449,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(); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index 9881830a8..94a951e52 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/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 @@ -389,6 +392,7 @@ public class DefaultClient implements Client protected void startServices() { + log.fine("Starting client services."); // Let the services know we are finally started services.start(); } @@ -447,6 +451,10 @@ public class DefaultClient implements Client protected void dispatch( Message m ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "{0} received:{1}", new Object[]{this, m}); + } + // Pull off the connection management messages we're // interested in and then pass on the rest. if( m instanceof ClientRegistrationMessage ) { @@ -457,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 ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 5c62733e8..eceaf6d73 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -326,6 +326,10 @@ public class DefaultServer implements Server protected void dispatch( HostedConnection source, Message m ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "{0} received:{1}", new Object[]{source, m}); + } + if( source == null ) { messageListeners.messageReceived( source, m ); } else { @@ -604,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(); } diff --git a/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java b/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java index 38fc98fa8..6d6fcef7b 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java +++ b/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 ) diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java index f151e1c84..4a2e40491 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java @@ -50,26 +50,34 @@ import java.util.logging.Logger; */ public class MessageListenerRegistry implements MessageListener { - static Logger log = Logger.getLogger(MessageListenerRegistry.class.getName()); + static final Logger log = Logger.getLogger(MessageListenerRegistry.class.getName()); - private List> listeners = new CopyOnWriteArrayList>(); - private Map>> typeListeners + private final List> listeners = new CopyOnWriteArrayList>(); + private final Map>> typeListeners = new ConcurrentHashMap>>(); public MessageListenerRegistry() { } + @Override public void messageReceived( S source, Message m ) { boolean delivered = false; + boolean trace = log.isLoggable(Level.FINER); for( MessageListener l : listeners ) { + if( trace ) { + log.log(Level.FINER, "Delivering {0} to:{1}", new Object[]{m, l}); + } l.messageReceived( source, m ); delivered = true; } for( MessageListener l : getListeners(m.getClass(),false) ) { + if( trace ) { + log.log(Level.FINER, "Delivering {0} to:{1}", new Object[]{m, l}); + } l.messageReceived( source, m ); delivered = true; } diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java index bd85ececc..a9130f838 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java +++ b/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 ); } } } diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java index c8aa5ddc2..7f992e94a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java +++ b/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(); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java index e69a9e52a..4db90f68b 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java +++ b/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(); } /** diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java index 198b915dc..57ea17864 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java +++ b/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 ) diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java index 4d1decc08..d9b51d39a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java +++ b/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 ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index 7e6b18849..ab1d05bc9 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/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 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); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java index 017544b2a..3b89f7d5a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java +++ b/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; diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java index 0c6143073..0d410bb4d 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java +++ b/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 savedFields = new HashMap(); + private static Map savedCtors = new HashMap(); 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 ctor = (Constructor)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) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java index 997658a77..ebd0e5908 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java @@ -89,6 +89,10 @@ public class RmiHostedService extends AbstractHostedService { this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true); } + public RmiHostedService( byte defaultChannel ) { + this((short)-1, defaultChannel, true); + } + public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) { this.rmiId = rmiId; this.defaultChannel = defaultChannel; diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java index c04a8c7b3..8fa23a538 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java +++ b/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); } } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java index f457e426b..00b96d122 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java +++ b/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 ) { @@ -225,6 +225,7 @@ public class RpcConnection { private class ResponseHolder { private Object response; private String error; + private Throwable exception; private RpcCallMessage msg; boolean received = false; @@ -235,6 +236,7 @@ public class RpcConnection { public synchronized void setResponse( RpcResponseMessage msg ) { this.response = msg.getResult(); this.error = msg.getError(); + this.exception = msg.getThrowable(); this.received = true; notifyAll(); } @@ -250,6 +252,9 @@ public class RpcConnection { if( error != null ) { throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error); } + if( exception != null ) { + throw new RuntimeException("Error calling remote procedure:" + msg, exception); + } return response; } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java index efb0def6a..8e15514e7 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java @@ -34,6 +34,7 @@ package com.jme3.network.service.rpc.msg; import com.jme3.network.AbstractMessage; import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; import java.io.PrintWriter; import java.io.StringWriter; @@ -50,6 +51,7 @@ public class RpcResponseMessage extends AbstractMessage { private long msgId; private Object result; private String error; + private Object exception; // if it was serializable public RpcResponseMessage() { } @@ -61,12 +63,31 @@ public class RpcResponseMessage extends AbstractMessage { public RpcResponseMessage( long msgId, Throwable t ) { this.msgId = msgId; - - StringWriter sOut = new StringWriter(); - PrintWriter out = new PrintWriter(sOut); - t.printStackTrace(out); - out.close(); - this.error = sOut.toString(); + + // See if the exception is serializable + if( isSerializable(t) ) { + // Can send the exception itself + this.exception = t; + } else { + // We'll compose all of the info into a string + StringWriter sOut = new StringWriter(); + PrintWriter out = new PrintWriter(sOut); + t.printStackTrace(out); + out.close(); + this.error = sOut.toString(); + } + } + + public static boolean isSerializable( Throwable error ) { + if( error == null ) { + return false; + } + for( Throwable t = error; t != null; t = t.getCause() ) { + if( Serializer.getExactSerializerRegistration(t.getClass()) == null ) { + return false; + } + } + return true; } public long getMessageId() { @@ -81,9 +102,15 @@ public class RpcResponseMessage extends AbstractMessage { return error; } + public Throwable getThrowable() { + return (Throwable)exception; + } + @Override public String toString() { return getClass().getSimpleName() + "[#" + msgId + ", result=" + result + + (error != null ? ", error=" + error : "") + + (exception != null ? ", exception=" + exception : "") + "]"; } } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java index b24a8db5f..8af8395c3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java +++ b/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); } diff --git a/sdk/build.gradle b/sdk/build.gradle index 99c850987..1a841ef0e 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -109,6 +109,7 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ dep.dependencyProject.configurations.archives.allArtifacts.each{ artifact-> if(artifact.classifier == "sources"){ } else if(artifact.classifier == "javadoc"){ + } else if(artifact.file.name.endsWith('.pom')) { } else{ if(!jmeJarFiles.contains(artifact.file)){ jmeJarFiles.add(artifact.file) diff --git a/sdk/jme3-assetpack-support/src/com/jme3/gde/assetpack/browser/Bundle.properties b/sdk/jme3-assetpack-support/src/com/jme3/gde/assetpack/browser/Bundle.properties index 515f491de..769fbf08f 100644 --- a/sdk/jme3-assetpack-support/src/com/jme3/gde/assetpack/browser/Bundle.properties +++ b/sdk/jme3-assetpack-support/src/com/jme3/gde/assetpack/browser/Bundle.properties @@ -1,6 +1,6 @@ CTL_AssetPackBrowserAction=AssetPackBrowser -CTL_AssetPackBrowserTopComponent=AssetPackBrowser Window -HINT_AssetPackBrowserTopComponent=This is a AssetPackBrowser window +CTL_AssetPackBrowserTopComponent=AssetPackBrowser +HINT_AssetPackBrowserTopComponent=The AssetPackBrowser allows easy managing of your AssetPacks AssetPackBrowserTopComponent.jTextField1.text=search AssetPackBrowserTopComponent.jButton1.text=update AssetPackBrowserTopComponent.jButton2.text=online assetpacks diff --git a/sdk/jme3-core/src/com/jme3/gde/core/appstates/AppStateExplorerTopComponent.java b/sdk/jme3-core/src/com/jme3/gde/core/appstates/AppStateExplorerTopComponent.java index 9a8db451a..2af268d4f 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/appstates/AppStateExplorerTopComponent.java +++ b/sdk/jme3-core/src/com/jme3/gde/core/appstates/AppStateExplorerTopComponent.java @@ -67,8 +67,8 @@ persistenceType = TopComponent.PERSISTENCE_ALWAYS) preferredID = "AppStateExplorerTopComponent") @Messages({ "CTL_AppStateExplorerAction=AppStateExplorer", - "CTL_AppStateExplorerTopComponent=AppStateExplorer Window", - "HINT_AppStateExplorerTopComponent=This is a AppStateExplorer window" + "CTL_AppStateExplorerTopComponent=AppStateExplorer", + "HINT_AppStateExplorerTopComponent=The AppStateExplorer provides an Overview over your current AppState" }) public final class AppStateExplorerTopComponent extends TopComponent implements ExplorerManager.Provider { diff --git a/sdk/jme3-core/src/com/jme3/gde/core/filters/Bundle.properties b/sdk/jme3-core/src/com/jme3/gde/core/filters/Bundle.properties index 651fa1a1e..5598f0ce4 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/filters/Bundle.properties +++ b/sdk/jme3-core/src/com/jme3/gde/core/filters/Bundle.properties @@ -1,3 +1,3 @@ CTL_FilterExplorerAction=FilterExplorer -CTL_FilterExplorerTopComponent=FilterExplorer Window -HINT_FilterExplorerTopComponent=This is a FilterExplorer window +CTL_FilterExplorerTopComponent=FilterExplorer +HINT_FilterExplorerTopComponent=The FilterExplorer provides an Overview over your current Filter diff --git a/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/Bundle.properties b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/Bundle.properties index 4bf848d6a..12fc5d7ab 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/Bundle.properties +++ b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/Bundle.properties @@ -1,4 +1,4 @@ CTL_SceneExplorerAction=SceneExplorer -CTL_SceneExplorerTopComponent=SceneExplorer Window -HINT_SceneExplorerTopComponent=This is a SceneExplorer window +CTL_SceneExplorerTopComponent=SceneExplorer +HINT_SceneExplorerTopComponent=The SceneExplorer provides an Overview over the SceneGraph of your Scene. SceneExplorerTopComponent.jButton1.text=update diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java index 73797cadc..fc1d66d70 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java @@ -186,7 +186,7 @@ public class EditableMatDefFile { return ""; } catch (Exception e) { Exceptions.printStackTrace(e); - return "error generating shader " + e.getMessage(); + return "Error generating shader: " + e.getMessage(); } } diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java index 634792505..cae44f72a 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java @@ -142,6 +142,7 @@ public class MatDefDataObject extends MultiDataObject { findAssetManager(); final MatDefMetaData metaData = new MatDefMetaData(this); lookupContents.add(metaData); + lookupContents.add(new MatDefNavigatorPanel()); pf.addFileChangeListener(new FileChangeAdapter() { @Override public void fileChanged(FileEvent fe) { diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java index 5ad859fcf..d33ee26d5 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java @@ -8,6 +8,8 @@ package com.jme3.gde.materialdefinition.editor; import com.jme3.gde.materialdefinition.fileStructure.TechniqueBlock; import java.awt.Component; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.JLabel; @@ -23,7 +25,8 @@ public class MatDefEditorToolBar extends javax.swing.JPanel { private MatDefEditorlElement parent; private final DefaultComboBoxModel comboModel = new DefaultComboBoxModel(); - + private final static Logger logger = Logger.getLogger(MatDefEditorToolBar.class.getName()); + /** * Creates new form MatDefEditorToolBar */ @@ -130,6 +133,17 @@ public class MatDefEditorToolBar extends javax.swing.JPanel { }// //GEN-END:initComponents private void techniqueComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_techniqueComboBoxActionPerformed + if (techniqueComboBox.getSelectedItem() == null) { + if (techniqueComboBox.getItemCount() > 0) { + if (techniqueComboBox.getItemCount() > 1) { + logger.log(Level.WARNING, "No Technique selected, taking the first one!"); /* Don't be over verbose: When there's only one Element, you can't select itself again, thus null */ + } + techniqueComboBox.setSelectedIndex(0); /* Take the first one available */ + } else { + logger.log(Level.WARNING, "No Techniques known for this MaterialDef. Please add one using the button to the right!"); + return; + } + } parent.switchTechnique((TechniqueBlock) techniqueComboBox.getSelectedItem()); }//GEN-LAST:event_techniqueComboBoxActionPerformed diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java index 26bba097c..665a1f628 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java @@ -15,6 +15,8 @@ import com.jme3.util.blockparser.Statement; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.openide.util.WeakListeners; /** @@ -28,6 +30,8 @@ public class TechniqueBlock extends UberStatement { public static final String ADD_WORLD_PARAM = "addWorldParam"; public static final String REMOVE_WORLD_PARAM = "removeWorldParam"; protected String name; + + private static final Logger logger = Logger.getLogger(TechniqueBlock.class.getName()); protected TechniqueBlock(int lineNumber, String line) { super(lineNumber, line); @@ -102,7 +106,13 @@ public class TechniqueBlock extends UberStatement { } public List getWorldParams() { - return getWorldParameters().getWorldParams(); + WorldParametersBlock block = getWorldParameters(); + if (block != null) + return getWorldParameters().getWorldParams(); + else { + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any WorldParameters. Most likely the technique {0} is broken.", line); + return new ArrayList(); + } } public void addWorldParam(WorldParamBlock block) { @@ -180,8 +190,19 @@ public class TechniqueBlock extends UberStatement { public List getShaderNodes() { List list = new ArrayList(); - list.addAll(getBlock(VertexShaderNodesBlock.class).getShaderNodes()); - list.addAll(getBlock(FragmentShaderNodesBlock.class).getShaderNodes()); + + VertexShaderNodesBlock vert_block = getBlock(VertexShaderNodesBlock.class); + if (vert_block == null) + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any VertexShaderNode. Most likely the technique {0} is broken.", line); + else + list.addAll(vert_block.getShaderNodes()); + + FragmentShaderNodesBlock frag_block = getBlock(FragmentShaderNodesBlock.class); + if (frag_block == null) + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any FragmentShaderNode. Most likely the technique {0} is broken.", line); + else + list.addAll(frag_block.getShaderNodes()); + return list; } diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java index e400da0f4..fc92aab9c 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java @@ -148,7 +148,7 @@ public class MaterialPreviewRenderer implements SceneListener { }); } - private int lastErrorHash = 0; + private static int lastErrorHash = 0; private void smartLog(String expText, String message) { int hash = message.hashCode(); @@ -183,7 +183,8 @@ public class MaterialPreviewRenderer implements SceneListener { //compilation error, the shader code will be output to the console //the following code will output the error //System.err.println(e.getMessage()); - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage()); + //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage()); + smartLog("{0}", e.getMessage()); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java index 267751ab4..270b313e9 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java @@ -32,14 +32,16 @@ package com.jme3.gde.terraineditor.sky; import com.jme3.math.Vector3f; +import com.jme3.texture.Image; import com.jme3.texture.Texture; import java.awt.Component; import javax.swing.event.ChangeListener; import org.openide.WizardDescriptor; +import org.openide.WizardValidationException; import org.openide.util.HelpCtx; @SuppressWarnings({"unchecked", "rawtypes"}) -public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { +public class SkyboxWizardPanel2 implements WizardDescriptor.ValidatingPanel { /** * The visual component that displays this panel. If you need to access the @@ -76,10 +78,12 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { // fireChangeEvent(); // and uncomment the complicated stuff below. } - + + @Override public final void addChangeListener(ChangeListener l) { } - + + @Override public final void removeChangeListener(ChangeListener l) { } /* @@ -105,14 +109,56 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { } } */ - + + @Override + public void validate() throws WizardValidationException { + SkyboxVisualPanel2 sky = (SkyboxVisualPanel2)component; + + /* Check if there are empty textures */ + if (multipleTextures) { + if (sky.getEditorNorth().getAsText() == null) { throw new WizardValidationException(null, " Texture North: Missing texture!", null); } + if (sky.getEditorSouth().getAsText() == null) { throw new WizardValidationException(null, " Texture South: Missing texture!", null); } + if (sky.getEditorWest().getAsText() == null) { throw new WizardValidationException(null, " Texture West: Missing texture!", null); } + if (sky.getEditorEast().getAsText() == null) { throw new WizardValidationException(null, " Texture East: Missing texture!", null); } + if (sky.getEditorTop().getAsText() == null) { throw new WizardValidationException(null, " Texture Top: Missing texture!", null); } + if (sky.getEditorBottom().getAsText() == null) { throw new WizardValidationException(null, " Texture Bottom: Missing texture!", null); } + + /* Prevent Null-Pointer Exception. If this is triggered, the Texture has no Image or the AssetKey is invalid (which should never happen) */ + if (sky.getEditorNorth().getValue() == null || ((Texture)sky.getEditorNorth().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture North: Cannot load texture!", null); } + if (sky.getEditorSouth().getValue() == null || ((Texture)sky.getEditorSouth().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture South: Cannot load texture!", null); } + if (sky.getEditorWest().getValue() == null || ((Texture)sky.getEditorWest().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture West: Cannot load texture!", null); } + if (sky.getEditorEast().getValue() == null || ((Texture)sky.getEditorEast().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture East: Cannot load texture!", null); } + if (sky.getEditorTop().getValue() == null || ((Texture)sky.getEditorTop().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture Top: Cannot load texture!", null); } + if (sky.getEditorBottom().getValue() == null || ((Texture)sky.getEditorBottom().getValue()).getImage() == null) { throw new WizardValidationException(null, " Texture Bottom: Cannot load texture!", null); } + + /* Check for squares */ + Image I = ((Texture)sky.getEditorNorth().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture North: Image has to be a square (width == height)!", null); } + I = ((Texture)sky.getEditorSouth().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture South: Image has to be a square (width == height)!", null); } + I = ((Texture)sky.getEditorWest().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture West: Image has to be a square (width == height)!", null); } + I = ((Texture)sky.getEditorEast().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture East: Image has to be a square (width == height)!", null); } + I = ((Texture)sky.getEditorTop().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture Top: Image has to be a square (width == height)!", null); } + I = ((Texture)sky.getEditorBottom().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Texture Bottom: Image has to be a square (width == height)!", null); } + } else { + if (sky.getEditorSingle().getAsText() == null){ throw new WizardValidationException(null, " Single Texture: Missing texture!", null); } + if (sky.getEditorSingle().getValue() == null || ((Texture)sky.getEditorSingle().getValue()).getImage() == null){ throw new WizardValidationException(null, " Single Texture: Cannot load texture!", null); } + Image I = ((Texture)sky.getEditorSingle().getValue()).getImage(); + if (I.getWidth() != I.getHeight()) { throw new WizardValidationException(null, " Single Texture: Image has to be a square (width == height)!", null); } + } + } + // You can use a settings object to keep track of state. Normally the // settings object will be the WizardDescriptor, so you can use // WizardDescriptor.getProperty & putProperty to store information entered // by the user. - public void readSettings(Object settings) { - WizardDescriptor wiz = (WizardDescriptor) settings; - multipleTextures = (Boolean)wiz.getProperty("multipleTextures"); + @Override + public void readSettings(WizardDescriptor settings) { + multipleTextures = (Boolean)settings.getProperty("multipleTextures"); SkyboxVisualPanel2 comp = (SkyboxVisualPanel2) getComponent(); if (multipleTextures) { comp.getMultipleTexturePanel().setVisible(true); @@ -124,28 +170,27 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { } @Override - public void storeSettings(Object settings) { - WizardDescriptor wiz = (WizardDescriptor) settings; + public void storeSettings(WizardDescriptor settings) { SkyboxVisualPanel2 comp = (SkyboxVisualPanel2) getComponent(); if (multipleTextures) { - wiz.putProperty("textureSouth", (Texture)comp.getEditorSouth().getValue()); - wiz.putProperty("textureNorth", (Texture)comp.getEditorNorth().getValue()); - wiz.putProperty("textureEast", (Texture)comp.getEditorEast().getValue()); - wiz.putProperty("textureWest", (Texture)comp.getEditorWest().getValue()); - wiz.putProperty("textureTop", (Texture)comp.getEditorTop().getValue()); - wiz.putProperty("textureBottom", (Texture)comp.getEditorBottom().getValue()); + settings.putProperty("textureSouth", (Texture)comp.getEditorSouth().getValue()); + settings.putProperty("textureNorth", (Texture)comp.getEditorNorth().getValue()); + settings.putProperty("textureEast", (Texture)comp.getEditorEast().getValue()); + settings.putProperty("textureWest", (Texture)comp.getEditorWest().getValue()); + settings.putProperty("textureTop", (Texture)comp.getEditorTop().getValue()); + settings.putProperty("textureBottom", (Texture)comp.getEditorBottom().getValue()); float x = new Float(comp.getNormal1X().getText()); float y = new Float(comp.getNormal1Y().getText()); float z = new Float(comp.getNormal1Z().getText()); - wiz.putProperty("normalScale", new Vector3f(x,y,z) ); + settings.putProperty("normalScale", new Vector3f(x,y,z) ); } else { - wiz.putProperty("textureSingle", (Texture)comp.getEditorSingle().getValue()); + settings.putProperty("textureSingle", (Texture)comp.getEditorSingle().getValue()); float x = new Float(comp.getNormal2X().getText()); float y = new Float(comp.getNormal2Y().getText()); float z = new Float(comp.getNormal2Z().getText()); - wiz.putProperty("normalScale", new Vector3f(x,y,z) ); - wiz.putProperty("envMapType", comp.getEnvMapType()); - wiz.putProperty("flipY", comp.getFlipYCheckBox().isSelected()); + settings.putProperty("normalScale", new Vector3f(x,y,z) ); + settings.putProperty("envMapType", comp.getEnvMapType()); + settings.putProperty("flipY", comp.getFlipYCheckBox().isSelected()); } } } diff --git a/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties b/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties index aa85f4a47..3fdd7bf88 100644 --- a/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties +++ b/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties @@ -3,6 +3,6 @@ OpenIDE-Module-Long-Description=\ The jMonkeyEngine GDE Welcome Screen OpenIDE-Module-Name=Welcome Screen OpenIDE-Module-Short-Description=The jMonkeyEngine GDE Welcome Screen -WelcomeScreenTopComponent.http.link=http://hub.jmonkeyengine.org/wiki/doku.php/sdk:welcome:3_0?do=export_xhtmlbody +WelcomeScreenTopComponent.http.link=http://hub.jmonkeyengine.org/wiki/doku.php/sdk:welcome:3_1?do=export_xhtmlbody WelcomeScreenTopComponent.rss.link=http://hub.jmonkeyengine.org/feed/rdf/ WelcomeScreenTopComponent.local.link=nbres:/com/jme3/gde/docs/sdk/welcome/local.html diff --git a/version.gradle b/version.gradle index 40e934278..f07c9d281 100644 --- a/version.gradle +++ b/version.gradle @@ -3,27 +3,31 @@ ===================== Nightly Build Snapshot + * git tag: * Full Version: 3.1-5124 * POM Version: 3.1.0-SNAPSHOT * NBM Revision: 5124 * NBM UC Suffix: nightly/3.1/plugins Nightly Build Snapshot (PBRIsComing branch) + * git tag: * Full Version: 3.1-PBRIsComing-5124 * POM Version: 3.1.0-PBRIsComing-SNAPSHOT * NBM Revision: 5124 * NBM UC Suffix: PBRIsComing-nightly/3.1/plugins Alpha1 Release + * git tag: v3.1.0-alpha1 * Full Version: 3.1-alpha1 * POM Version: 3.1.0-alpha1 - * NBM Revision: 1 + * NBM Revision: 0 * NBM UC Suffix: stable/3.1/plugins Final Release + * git tag: v3.1.0 * Full Version: 3.1 * POM Version: 3.1.0 - * NBM Revision: 5 + * NBM Revision: 0 * NBM UC Suffix: stable/3.1/plugins */ @@ -52,59 +56,109 @@ ext { jmeNbmUcSuffix = "unknown" } +def getReleaseInfo(String tag) { + if (tag == null) { + // not a tagged commit + return null; + } + if (!tag.startsWith("v")) { + // syntax error + return null; + } + tag = tag.substring(1) + + String[] parts = tag.split("-", 2); + String mainVersion; + boolean prerelease; + String releaseName = null; + + if (parts.length == 2) { + // prerelease + prerelease = true; + mainVersion = parts[0]; + releaseName = parts[1]; + if (releaseName.size() == 0) { + // syntax error + return null; + } + } else if (parts.length == 1) { + // final release + prerelease = false; + mainVersion = parts[0]; + } else { + // error + return null; + } + + if (mainVersion.size() == 0) { + // syntax error + return null; + } + + parts = mainVersion.split("\\."); + if (parts.size() != 3) { + // syntax error + return null; + } + + String baseVersion = parts[0] + "." + parts[1]; + + return [ + "tag" : tag, + "baseVersion" : baseVersion, + "mainVersion" : mainVersion, + "prerelease" : prerelease, + "releaseName" : releaseName, + "releaseSuffix": (prerelease ? "-${releaseName}": "") + ] +} + task configureVersionInfo { try { def grgit = Grgit.open(project.file('.')) - jmeRevision = grgit.log(includes:['HEAD']).size() - jmeGitHash = grgit.head().id - jmeShortGitHash = grgit.head().abbreviatedId + def head = grgit.head() + jmeRevision = grgit.log(includes: [head]).size() + jmeGitHash = head.id + jmeShortGitHash = head.abbreviatedId jmeBranchName = grgit.branch.current.name - jmeGitTag = grgit.describe() - if (jmeGitTag == null) jmeGitTag = "" - - if (System.env.TRAVIS_BRANCH != null) { - jmeBranchName = System.env.TRAVIS_BRANCH - } - if (System.env.TRAVIS_TAG != null) { - jmeGitTag = System.env.TRAVIS_TAG - } - if (System.env.TRAVIS_PULL_REQUEST != null && - System.env.TRAVIS_PULL_REQUEST != "false") { - jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST - } + jmeGitTag = grgit.tag.list().find { it.commit == head } - jmeFullVersion = jmeMainVersion - jmePomVersion = jmeVersion - - if (jmeBranchName != "master") { - jmeFullVersion += "-${jmeBranchName}" - jmePomVersion += "-${jmeBranchName}" - - jmeNbmUcSuffix = "${jmeBranchName}-" + if (jmeGitTag != null) { + jmeGitTag = jmeGitTag.name } else { - jmeNbmUcSuffix = "" + jmeGitTag = System.env.TRAVIS_TAG } - - if (jmeVersionTag == "SNAPSHOT") { - jmeNbmUcSuffix += "nightly" + + def releaseInfo = getReleaseInfo(jmeGitTag) + if (releaseInfo != null) { + jmeFullVersion = "${releaseInfo.baseVersion}${releaseInfo.releaseSuffix}" + jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}" + jmeNbmRevision = "0" + jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins" } else { - jmeNbmUcSuffix += "stable" - } - - jmeNbmUcSuffix += "/" + jmeMainVersion + "/plugins" - - if (jmeVersionTag == "SNAPSHOT") { + // SNAPSHOT + jmeFullVersion = jmeMainVersion + jmePomVersion = jmeVersion + if (System.env.TRAVIS_BRANCH != null) { + jmeBranchName = System.env.TRAVIS_BRANCH + } + if (System.env.TRAVIS_PULL_REQUEST != null && + System.env.TRAVIS_PULL_REQUEST != "false") { + jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST + } + if (jmeBranchName != "master") { + jmeFullVersion += "-${jmeBranchName}" + jmePomVersion += "-${jmeBranchName}" + jmeNbmUcSuffix = "${jmeBranchName}-" + } else { + jmeNbmUcSuffix = "" + } + jmeNbmUcSuffix += "nightly/" + jmeMainVersion + "/plugins" jmeFullVersion += "-${jmeRevision}" jmePomVersion += "-SNAPSHOT" jmeNbmRevision = jmeRevision - } else if (jmeVersionTag == "") { - jmeNbmRevision = jmeVersionTagID - } else { - jmeFullVersion += "-${jmeVersionTag}" - jmePomVersion += "-${jmeVersionTag}" - jmeNbmRevision = jmeVersionTagID } - + logger.warn("Full Version: ${jmeFullVersion}") logger.warn("POM Version: ${jmePomVersion}") logger.warn("NBM Revision: ${jmeNbmRevision}")