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/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 c3591d707..581ff8174 100644 --- a/jme3-core/src/main/java/com/jme3/app/Application.java +++ b/jme3-core/src/main/java/com/jme3/app/Application.java @@ -661,12 +661,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)} @@ -752,4 +768,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/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/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java index 5d07e1a8d..cbd89dc8c 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -60,6 +60,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 39a30980b..f6ea68045 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 @@ -103,9 +103,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; /** @@ -169,20 +166,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. *

@@ -227,7 +228,9 @@ public abstract class Light implements Savable, Cloneable { @Override public Light clone(){ try { - return (Light) super.clone(); + Light l = (Light) super.clone(); + l.color = color.clone(); + return l; } catch (CloneNotSupportedException ex) { throw new AssertionError(); } 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 5fc3b3bd2..6f04ccef6 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -976,6 +976,7 @@ 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); } @@ -990,6 +991,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { 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 0aec00236..7b1493edb 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -49,7 +49,7 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.*; -import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; import com.jme3.shader.UniformBinding; import com.jme3.shader.UniformBindingManager; import com.jme3.system.NullRenderer; @@ -483,8 +483,8 @@ public class RenderManager { * Updates the given list of uniforms with {@link UniformBinding uniform bindings} * based on the current world state. */ - public void updateUniformBindings(Shader shader) { - uniformBindingManager.updateUniformBindings(shader); + public void updateUniformBindings(List params) { + uniformBindingManager.updateUniformBindings(params); } /** @@ -556,7 +556,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); @@ -585,37 +585,6 @@ public class RenderManager { renderGeometry(gl.get(i)); } } - - public void renderGeometryListNew(GeometryList gl) { - int size = gl.size(); - int pass = 0; - - // Keep rendering geometries in the list - // checking each time if they need more passes. - // Geometries which need more passes are added to the beginning - // of the list and then another pass is executed. - // In the end, all geometries will have their passes rendered. - while (true) { - int writeIdx = 0; - for (int i = 0; i < size; i++) { - Geometry obj = gl.get(i); - renderGeometry(obj); - boolean morePasses = true; - if (morePasses) { - // Geometry wants to be rendered again. - // Move it to the beginning of the list. - gl.set(writeIdx++, obj); - } - } - // No geometries were written to the beginning of the list - - // all passes are finished. - if (writeIdx == 0) { - return; - } - pass++; - size = writeIdx; - } - } /** * Preloads a scene for rendering. @@ -647,9 +616,7 @@ public class RenderManager { gm.getMaterial().preload(this); Mesh mesh = gm.getMesh(); - if (mesh != null - && mesh.getVertexCount() != 0 - && mesh.getTriangleCount() != 0) { + if (mesh != null) { for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) { renderer.updateBufferData(vb); @@ -674,10 +641,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 @@ -817,10 +782,8 @@ public class RenderManager { * @param singlePassLightBatchSize the number of lights. */ public void setSinglePassLightBatchSize(int singlePassLightBatchSize) { - if (singlePassLightBatchSize < 1) { - throw new IllegalArgumentException("batch size cannot be less than 1"); - } - this.singlePassLightBatchSize = singlePassLightBatchSize; + // Ensure the batch size is no less than 1 + this.singlePassLightBatchSize = singlePassLightBatchSize < 1 ? 1 : singlePassLightBatchSize; } @@ -1026,13 +989,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/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/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..878c8e6da 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-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-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index 3e76f8be7..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 @@ -462,11 +465,17 @@ public class DefaultClient implements Client this.id = (int)crm.getId(); log.log( Level.FINE, "Connection established, id:{0}.", this.id ); connecting.countDown(); - fireConnected(); + //fireConnected(); } else { // Else it's a message letting us know that the // hosted services have been started startServices(); + + // Delay firing 'connected' until the services have all + // been started to avoid odd race conditions. If there is some + // need to get some kind of event before the services have been + // started then we should create a new event step. + fireConnected(); } return; } else if( m instanceof ChannelInfoMessage ) { 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 e95c0f97f..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 @@ -608,7 +608,7 @@ public class DefaultServer implements Server // should always already be closed through all paths that I // can conceive... but it doesn't hurt to be sure. for( Endpoint p : channels ) { - if( p == null ) + if( p == null || !p.isConnected() ) continue; p.close(); } 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/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/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 c8ca8083a..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,10 +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/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-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()); } } }