This two step process is important because this is what allows
+ * circular references in the cloned object graph.
+ *
+ * @author Paul Speed
+ */
+public interface CloneFunction {
+
+ /**
+ * Performs a shallow clone of the specified object. This is similar
+ * to the JmeCloneable.clone() method in semantics and is the first part
+ * of a two part cloning process. Once the shallow clone is created, it
+ * is cached and CloneFunction.cloneFields() is called. In this way,
+ * the CloneFunction interface can completely take over the JmeCloneable
+ * style cloning for an object that doesn't otherwise implement that interface.
+ *
+ * @param cloner The cloner performing the cloning operation.
+ * @param original The original object that needs to be cloned.
+ */
+ public T cloneObject( Cloner cloner, T original );
+
+
+ /**
+ * Performs a deep clone of the specified clone's fields. This is similar
+ * to the JmeCloneable.cloneFields() method in semantics and is the second part
+ * of a two part cloning process. Once the shallow clone is created, it
+ * is cached and CloneFunction.cloneFields() is called. In this way,
+ * the CloneFunction interface can completely take over the JmeCloneable
+ * style cloning for an object that doesn't otherwise implement that interface.
+ *
+ * @param cloner The cloner performing the cloning operation.
+ * @param clone The clone previously returned from cloneObject().
+ * @param original The original object that was cloned. This is provided for
+ * the very special case where field cloning needs to refer to
+ * the original object. Mostly the necessary fields should already
+ * be on the clone.
+ */
+ public void cloneFields( Cloner cloner, T clone, T original );
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
new file mode 100644
index 000000000..cc046b28a
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A deep clone utility that provides similar object-graph-preserving
+ * qualities to typical serialization schemes. An internal registry
+ * of cloned objects is kept to be used by other objects in the deep
+ * clone process that implement JmeCloneable.
+ *
+ * By default, objects that do not implement JmeCloneable will
+ * be treated like normal Java Cloneable objects. If the object does
+ * not implement the JmeCloneable or the regular JDK Cloneable interfaces
+ * AND has no special handling defined then an IllegalArgumentException
+ * will be thrown.
+ *
+ * Enhanced object cloning is done in a two step process. First,
+ * the object is cloned using the normal Java clone() method and stored
+ * in the clone registry. After that, if it implements JmeCloneable then
+ * its cloneFields() method is called to deep clone any of the fields.
+ * This two step process has a few benefits. First, it means that objects
+ * can easily have a regular shallow clone implementation just like any
+ * normal Java objects. Second, the deep cloning of fields happens after
+ * creation wich means that the clone is available to future field cloning
+ * to resolve circular references.
+ *
+ * Similar to Java serialization, the handling of specific object
+ * types can be customized. This allows certain objects to be cloned gracefully
+ * even if they aren't normally Cloneable. This can also be used as a
+ * sort of filter to keep certain types of objects from being cloned.
+ * (For example, adding the IdentityCloneFunction for Mesh.class would cause
+ * all mesh instances to be shared with the original object graph.)
+ *
+ * By default, the Cloner registers serveral default clone functions
+ * as follows:
+ *
+ * java.util.ArrayList: ListCloneFunction
+ * java.util.LinkedList: ListCloneFunction
+ * java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction
+ * java.util.Vector: ListCloneFunction
+ * java.util.Stack: ListCloneFunction
+ * com.jme3.util.SafeArrayList: ListCloneFunction
+ *
+ *
+ * Usage:
+ *
+ * // Example 1: using an instantiated, reusable cloner.
+ * Cloner cloner = new Cloner();
+ * Foo fooClone = cloner.clone(foo);
+ * cloner.clearIndex(); // prepare it for reuse
+ * Foo fooClone2 = cloner.clone(foo);
+ *
+ * // Example 2: using the utility method that self-instantiates a temporary cloner.
+ * Foo fooClone = Cloner.deepClone(foo);
+ *
+ *
+ *
+ * @author Paul Speed
+ */
+public class Cloner {
+
+ /**
+ * Keeps track of the objects that have been cloned so far.
+ */
+ private IdentityHashMap index = new IdentityHashMap();
+
+ /**
+ * Custom functions for cloning objects.
+ */
+ private Map functions = new HashMap();
+
+ /**
+ * Cache the clone methods once for all cloners.
+ */
+ private static final Map methodCache = new ConcurrentHashMap<>();
+
+ /**
+ * Creates a new cloner with only default clone functions and an empty
+ * object index.
+ */
+ public Cloner() {
+ // Register some standard types
+ ListCloneFunction listFunction = new ListCloneFunction();
+ functions.put(java.util.ArrayList.class, listFunction);
+ functions.put(java.util.LinkedList.class, listFunction);
+ functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction);
+ functions.put(java.util.Vector.class, listFunction);
+ functions.put(java.util.Stack.class, listFunction);
+ functions.put(com.jme3.util.SafeArrayList.class, listFunction);
+ }
+
+ /**
+ * Convenience utility function that creates a new Cloner, uses it to
+ * deep clone the object, and then returns the result.
+ */
+ public static T deepClone( T object ) {
+ return new Cloner().clone(object);
+ }
+
+ /**
+ * Deeps clones the specified object, reusing previous clones when possible.
+ *
+ * Object cloning priority works as follows:
+ *
+ * If the object has already been cloned then its clone is returned.
+ * If there is a custom CloneFunction then it is called to clone the object.
+ * If the object implements Cloneable then its clone() method is called, arrays are
+ * deep cloned with entries passing through clone().
+ * If the object implements JmeCloneable then its cloneFields() method is called on the
+ * clone.
+ * Else an IllegalArgumentException is thrown.
+ *
+ *
+ * Note: objects returned by this method may not have yet had their cloneField()
+ * method called.
+ */
+ public T clone( T object ) {
+ return clone(object, true);
+ }
+
+ /**
+ * Internal method to work around a Java generics typing issue by
+ * isolating the 'bad' case into a method with suppressed warnings.
+ */
+ @SuppressWarnings("unchecked")
+ private Class objectClass( T object ) {
+ // This should be 100% allowed without a cast but Java generics
+ // is not that smart sometimes.
+ // Wrapping it in a method at least isolates the warning suppression
+ return (Class)object.getClass();
+ }
+
+ /**
+ * Deeps clones the specified object, reusing previous clones when possible.
+ *
+ * Object cloning priority works as follows:
+ *
+ * If the object has already been cloned then its clone is returned.
+ * If useFunctions is true and there is a custom CloneFunction then it is
+ * called to clone the object.
+ * If the object implements Cloneable then its clone() method is called, arrays are
+ * deep cloned with entries passing through clone().
+ * If the object implements JmeCloneable then its cloneFields() method is called on the
+ * clone.
+ * Else an IllegalArgumentException is thrown.
+ *
+ *
+ * The abililty to selectively use clone functions is useful when
+ * being called from a clone function.
+ *
+ * Note: objects returned by this method may not have yet had their cloneField()
+ * method called.
+ */
+ public T clone( T object, boolean useFunctions ) {
+ if( object == null ) {
+ return null;
+ }
+ Class type = objectClass(object);
+
+ // Check the index to see if we already have it
+ Object clone = index.get(object);
+ if( clone != null ) {
+ return type.cast(clone);
+ }
+
+ // See if there is a custom function... that trumps everything.
+ CloneFunction f = getCloneFunction(type);
+ if( f != null ) {
+ T result = f.cloneObject(this, object);
+
+ // Store the object in the identity map so that any circular references
+ // are resolvable.
+ index.put(object, result);
+
+ // Now call the function again to deep clone the fields
+ f.cloneFields(this, result, object);
+
+ return result;
+ }
+
+ if( object.getClass().isArray() ) {
+ // Perform an array clone
+ clone = arrayClone(object);
+
+ // Array clone already indexes the clone
+ } else if( object instanceof JmeCloneable ) {
+ // Use the two-step cloning semantics
+ clone = ((JmeCloneable)object).jmeClone();
+
+ // Store the object in the identity map so that any circular references
+ // are resolvable
+ index.put(object, clone);
+
+ ((JmeCloneable)clone).cloneFields(this, object);
+ } else if( object instanceof Cloneable ) {
+
+ // Perform a regular Java shallow clone
+ try {
+ clone = javaClone(object);
+ } catch( CloneNotSupportedException e ) {
+ throw new IllegalArgumentException("Object is not cloneable, type:" + type, e);
+ }
+
+ // Store the object in the identity map so that any circular references
+ // are resolvable
+ index.put(object, clone);
+ } else {
+ throw new IllegalArgumentException("Object is not cloneable, type:" + type);
+ }
+
+ return type.cast(clone);
+ }
+
+ /**
+ * Sets a custom CloneFunction for the exact Java type. Note: no inheritence
+ * checks are performed so a function must be registered for each specific type
+ * that it handles. By default ListCloneFunction is registered for
+ * ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList.
+ */
+ public void setCloneFunction( Class type, CloneFunction function ) {
+ if( function == null ) {
+ functions.remove(type);
+ } else {
+ functions.put(type, function);
+ }
+ }
+
+ /**
+ * Returns a previously registered clone function for the specified type or null
+ * if there is no custom clone function for the type.
+ */
+ @SuppressWarnings("unchecked")
+ public CloneFunction getCloneFunction( Class type ) {
+ return (CloneFunction)functions.get(type);
+ }
+
+ /**
+ * Clears the object index allowing the cloner to be reused for a brand new
+ * cloning operation.
+ */
+ public void clearIndex() {
+ index.clear();
+ }
+
+ /**
+ * Performs a raw shallow Java clone using reflection. This call does NOT
+ * check against the clone index and so will return new objects every time
+ * it is called. That's because these are shallow clones and have not (and may
+ * not ever, depending on the caller) get resolved.
+ *
+ * This method is provided as a convenient way for CloneFunctions to call
+ * clone() and objects without necessarily knowing their real type.
+ */
+ public T javaClone( T object ) throws CloneNotSupportedException {
+ Method m = methodCache.get(object.getClass());
+ if( m == null ) {
+ try {
+ // Lookup the method and cache it
+ m = object.getClass().getMethod("clone");
+ } catch( NoSuchMethodException e ) {
+ throw new CloneNotSupportedException("No public clone method found for:" + object.getClass());
+ }
+ methodCache.put(object.getClass(), m);
+
+ // Note: yes we might cache the method twice... but so what?
+ }
+
+ try {
+ Class extends T> type = objectClass(object);
+ return type.cast(m.invoke(object));
+ } catch( IllegalAccessException | InvocationTargetException e ) {
+ throw new RuntimeException("Error cloning object of type:" + object.getClass(), e);
+ }
+ }
+
+ /**
+ * Clones a primitive array by coping it and clones an object
+ * array by coping it and then running each of its values through
+ * Cloner.clone().
+ */
+ protected T arrayClone( T object ) {
+
+ // Java doesn't support the cloning of arrays through reflection unless
+ // you open access to Object's protected clone array... which requires
+ // elevated privileges. So we will do a work-around that is slightly less
+ // elegant.
+ // This should be 100% allowed without a case but Java generics
+ // is not that smart
+ Class type = objectClass(object);
+ Class elementType = type.getComponentType();
+ int size = Array.getLength(object);
+ Object clone = Array.newInstance(elementType, size);
+
+ // Store the clone for later lookups
+ index.put(object, clone);
+
+ if( elementType.isPrimitive() ) {
+ // Then our job is a bit easier
+ System.arraycopy(object, 0, clone, 0, size);
+ } else {
+ // Else it's an object array so we'll clone it and its children
+ for( int i = 0; i < size; i++ ) {
+ Object element = clone(Array.get(object, i));
+ Array.set(clone, i, element);
+ }
+ }
+
+ return type.cast(clone);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java
new file mode 100644
index 000000000..ac3dce6ee
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ * A CloneFunction implementation that simply returns the
+ * the passed object without cloning it. This is useful for
+ * forcing some object types (like Meshes) to be shared between
+ * the original and cloned object graph.
+ *
+ * @author Paul Speed
+ */
+public class IdentityCloneFunction implements CloneFunction {
+
+ /**
+ * Returns the object directly.
+ */
+ public T cloneObject( Cloner cloner, T object ) {
+ return object;
+ }
+
+ /**
+ * Does nothing.
+ */
+ public void cloneFields( Cloner cloner, T clone, T object ) {
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java
new file mode 100644
index 000000000..6b278b222
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ * Indicates an object that wishes to more actively participate in the
+ * two-part deep copying process provided by the Cloner. Objects implementing
+ * this interface can access the already cloned object graph to resolve
+ * their local dependencies in a way that will be equivalent to the
+ * original object graph. In other words, if two objects in the graph
+ * share the same target reference then the cloned version will share
+ * the cloned reference.
+ *
+ * For example, if an object wishes to deep clone one of its fields
+ * then it will call cloner.clone(object) instead of object.clone().
+ * The cloner will keep track of any clones already created for 'object'
+ * and return that instead of a new clone.
+ *
+ * Cloning of a JmeCloneable object is done in two parts. First,
+ * the standard Java clone() method is called to create a shallow clone
+ * of the object. Second, the cloner wil lcall the cloneFields() method
+ * to let the object deep clone any of its fields that should be cloned.
+ *
+ * This two part process is necessary to facilitate circular references.
+ * When an object calls cloner.clone() during its cloneFields() method, it
+ * may get only a shallow copy that will be filled in later.
+ *
+ * @author Paul Speed
+ */
+public interface JmeCloneable extends Cloneable {
+
+ /**
+ * Performs a regular shallow clone of the object. Some fields
+ * may also be cloned but generally only if they will never be
+ * shared with other objects. (For example, local Vector3fs and so on.)
+ *
+ * This method is separate from the regular clone() method
+ * so that objects might still maintain their own regular java clone()
+ * semantics (perhaps even using Cloner for those methods). However,
+ * because Java's clone() has specific features in the sense of Object's
+ * clone() implementation, it's usually best to have some path for
+ * subclasses to bypass the public clone() method that might be cloning
+ * fields and instead get at the superclass protected clone() methods.
+ * For example, through super.jmeClone() or another protected clone
+ * method that some base class eventually calls super.clone() in.
+ */
+ public Object jmeClone();
+
+ /**
+ * Implemented to perform deep cloning for this object, resolving
+ * local cloned references using the specified cloner. The object
+ * can call cloner.clone(fieldValue) to deep clone any of its fields.
+ *
+ * Note: during normal clone operations the original object
+ * will not be needed as the clone has already had all of the fields
+ * shallow copied.
+ *
+ * @param cloner The cloner that is performing the cloning operation. The
+ * cloneFields method can call back into the cloner to make
+ * clones if its subordinate fields.
+ * @param original The original object from which this object was cloned.
+ * This is provided for the very rare case that this object needs
+ * to refer to its original for some reason. In general, all of
+ * the relevant values should have been transferred during the
+ * shallow clone and this object need merely clone what it wants.
+ */
+ public void cloneFields( Cloner cloner, Object original );
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java
new file mode 100644
index 000000000..a1f269d67
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.util.List;
+
+/**
+ * A CloneFunction implementation that deep clones a list by
+ * creating a new list and cloning its values using the cloner.
+ *
+ * @author Paul Speed
+ */
+public class ListCloneFunction implements CloneFunction {
+
+ public T cloneObject( Cloner cloner, T object ) {
+ try {
+ T clone = cloner.javaClone(object);
+ return clone;
+ } catch( CloneNotSupportedException e ) {
+ throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e);
+ }
+ }
+
+ /**
+ * Clones the elements of the list.
+ */
+ @SuppressWarnings("unchecked")
+ public void cloneFields( Cloner cloner, T clone, T object ) {
+ for( int i = 0; i < clone.size(); i++ ) {
+ // Need to clone the clones... because T might
+ // have done something special in its clone method that
+ // we will have to adhere to. For example, clone may have nulled
+ // out some things or whatever that might be implementation specific.
+ // At any rate, if it's a proper clone then the clone will already
+ // have shallow versions of the elements that we can clone.
+ clone.set(i, cloner.clone(clone.get(i)));
+ }
+ }
+}
+
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
index 2a4d934d2..4c27dd90e 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
@@ -113,6 +113,8 @@ MaterialDef Phong Lighting {
//For instancing
Boolean UseInstancing
+
+ Boolean BackfaceShadows: false
}
Technique {
@@ -214,26 +216,19 @@ MaterialDef Phong Lighting {
INSTANCING : UseInstancing
}
- ForcedRenderState {
- FaceCull Off
- DepthTest On
- DepthWrite On
- PolyOffset 5 3
- ColorWrite Off
- }
-
}
Technique PostShadow15{
- VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
- FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+ VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
+ FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
+ NormalMatrix
}
Defines {
@@ -248,6 +243,7 @@ MaterialDef Phong Lighting {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
+ BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {
@@ -266,6 +262,7 @@ MaterialDef Phong Lighting {
WorldMatrix
ViewProjectionMatrix
ViewMatrix
+ NormalMatrix
}
Defines {
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
index 254806d87..3ece6268f 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
@@ -41,8 +41,7 @@ varying vec3 SpecularSum;
#ifdef NORMALMAP
uniform sampler2D m_NormalMap;
- varying vec3 vTangent;
- varying vec3 vBinormal;
+ varying vec4 vTangent;
#endif
varying vec3 vNormal;
@@ -71,7 +70,7 @@ uniform float m_Shininess;
void main(){
#if !defined(VERTEX_LIGHTING)
#if defined(NORMALMAP)
- mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
+ mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz);
if (!gl_FrontFacing)
{
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
index 1fde8e13d..c607b3891 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
@@ -39,8 +39,7 @@ attribute vec3 inNormal;
varying vec3 vPos;
#ifdef NORMALMAP
attribute vec4 inTangent;
- varying vec3 vTangent;
- varying vec3 vBinormal;
+ varying vec4 vTangent;
#endif
#else
#ifdef COLORRAMP
@@ -104,8 +103,7 @@ void main(){
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
- vTangent = TransformNormal(modelSpaceTan);
- vBinormal = cross(wvNormal, vTangent)* inTangent.w;
+ vTangent = vec4(TransformNormal(modelSpaceTan).xyz,inTangent.w);
vNormal = wvNormal;
vPos = wvPosition;
#elif !defined(VERTEX_LIGHTING)
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
index 8dd6fc5ca..68f07f98d 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
@@ -51,6 +51,8 @@ MaterialDef Unshaded {
Float PCFEdge
Float ShadowMapSize
+
+ Boolean BackfaceShadows: true
}
Technique {
@@ -147,8 +149,8 @@ MaterialDef Unshaded {
Technique PostShadow15{
- VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
- FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+ VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
+ FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
@@ -169,6 +171,7 @@ MaterialDef Unshaded {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
+ BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {
@@ -201,6 +204,7 @@ MaterialDef Unshaded {
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
+ BACKFACE_SHADOWS: BackfaceShadows
}
ForcedRenderState {
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag
index 4e42c5784..a2d191895 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag
@@ -1,4 +1,5 @@
#import "Common/ShaderLib/Shadows.glsllib"
+#import "Common/ShaderLib/GLSLCompat.glsllib"
#if defined(PSSM) || defined(FADE)
varying float shadowPosition;
@@ -8,6 +9,9 @@ varying vec4 projCoord0;
varying vec4 projCoord1;
varying vec4 projCoord2;
varying vec4 projCoord3;
+#ifndef BACKFACE_SHADOWS
+ varying float nDotL;
+#endif
#ifdef POINTLIGHT
varying vec4 projCoord4;
@@ -45,9 +49,15 @@ void main(){
if(alpha<=m_AlphaDiscardThreshold){
discard;
}
+ #endif
+ #ifndef BACKFACE_SHADOWS
+ if(nDotL > 0.0){
+ discard;
+ }
#endif
-
+
+
float shadow = 1.0;
#ifdef POINTLIGHT
@@ -70,11 +80,11 @@ void main(){
#endif
#ifdef FADE
- shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
+ shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
- shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
- gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
+ shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
+ gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
}
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
index af80bdae3..928637adf 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
@@ -29,12 +29,14 @@ MaterialDef Post Shadow {
Float PCFEdge
Float ShadowMapSize
+
+ Boolean BackfaceShadows: false
}
Technique {
- VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
- FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+ VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
+ FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
@@ -49,6 +51,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
+ BACKFACE_SHADOWS: BackfaceShadows
}
RenderState {
@@ -75,6 +78,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
+ BACKFACE_SHADOWS: BackfaceShadows
}
RenderState {
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
index 482231032..de4490820 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
@@ -1,11 +1,12 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
-uniform vec3 m_LightPos;
varying vec4 projCoord0;
varying vec4 projCoord1;
@@ -15,12 +16,14 @@ varying vec4 projCoord3;
#ifdef POINTLIGHT
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
+ uniform vec3 m_LightPos;
varying vec4 projCoord4;
varying vec4 projCoord5;
varying vec4 worldPos;
#else
+ uniform vec3 m_LightDir;
#ifndef PSSM
- uniform vec3 m_LightDir;
+ uniform vec3 m_LightPos;
varying float lightDot;
#endif
#endif
@@ -28,12 +31,15 @@ varying vec4 projCoord3;
#if defined(PSSM) || defined(FADE)
varying float shadowPosition;
#endif
-varying vec3 lightVec;
varying vec2 texCoord;
-
attribute vec3 inPosition;
+#ifndef BACKFACE_SHADOWS
+ attribute vec3 inNormal;
+ varying float nDotL;
+#endif
+
#ifdef DISCARD_ALPHA
attribute vec2 inTexCoord;
#endif
@@ -51,16 +57,17 @@ void main(){
Skinning_Compute(modelSpacePos);
#endif
gl_Position = TransformWorldViewProjection(modelSpacePos);
+ vec3 lightDir;
#if defined(PSSM) || defined(FADE)
- shadowPosition = gl_Position.z;
+ shadowPosition = gl_Position.z;
#endif
#ifndef POINTLIGHT
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
- worldPos = g_WorldMatrix * modelSpacePos;
+ worldPos = TransformWorld(modelSpacePos);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
@@ -75,8 +82,21 @@ void main(){
projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#else
#ifndef PSSM
- vec3 lightDir = worldPos.xyz - m_LightPos;
+ //Spot light
+ lightDir = worldPos.xyz - m_LightPos;
lightDot = dot(m_LightDir,lightDir);
#endif
#endif
+
+ #ifndef BACKFACE_SHADOWS
+ vec3 normal = normalize(TransformWorld(vec4(inNormal,0.0))).xyz;
+ #ifdef POINTLIGHT
+ lightDir = worldPos.xyz - m_LightPos;
+ #else
+ #ifdef PSSM
+ lightDir = m_LightDir;
+ #endif
+ #endif
+ nDotL = dot(normal, lightDir);
+ #endif
}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag
deleted file mode 100644
index 2eb9541e5..000000000
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag
+++ /dev/null
@@ -1,80 +0,0 @@
-#import "Common/ShaderLib/Shadows15.glsllib"
-
-out vec4 outFragColor;
-
-#if defined(PSSM) || defined(FADE)
-in float shadowPosition;
-#endif
-
-in vec4 projCoord0;
-in vec4 projCoord1;
-in vec4 projCoord2;
-in vec4 projCoord3;
-
-#ifdef POINTLIGHT
- in vec4 projCoord4;
- in vec4 projCoord5;
- in vec4 worldPos;
- uniform vec3 m_LightPos;
-#else
- #ifndef PSSM
- in float lightDot;
- #endif
-#endif
-
-#ifdef DISCARD_ALPHA
- #ifdef COLOR_MAP
- uniform sampler2D m_ColorMap;
- #else
- uniform sampler2D m_DiffuseMap;
- #endif
- uniform float m_AlphaDiscardThreshold;
- varying vec2 texCoord;
-#endif
-
-#ifdef FADE
-uniform vec2 m_FadeInfo;
-#endif
-
-void main(){
-
- #ifdef DISCARD_ALPHA
- #ifdef COLOR_MAP
- float alpha = texture2D(m_ColorMap,texCoord).a;
- #else
- float alpha = texture2D(m_DiffuseMap,texCoord).a;
- #endif
-
- if(alpha < m_AlphaDiscardThreshold){
- discard;
- }
- #endif
-
- float shadow = 1.0;
- #ifdef POINTLIGHT
- shadow = getPointLightShadows(worldPos, m_LightPos,
- m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
- projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
- #else
- #ifdef PSSM
- shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
- m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
- projCoord0, projCoord1, projCoord2, projCoord3);
- #else
- //spotlight
- if(lightDot < 0){
- outFragColor = vec4(1.0);
- return;
- }
- shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
- #endif
- #endif
-
- #ifdef FADE
- shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
- #endif
-
- shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
- outFragColor = vec4(shadow, shadow, shadow, 1.0);
-}
-
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
deleted file mode 100644
index 20565de7c..000000000
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
+++ /dev/null
@@ -1,82 +0,0 @@
-#import "Common/ShaderLib/Instancing.glsllib"
-#import "Common/ShaderLib/Skinning.glsllib"
-uniform mat4 m_LightViewProjectionMatrix0;
-uniform mat4 m_LightViewProjectionMatrix1;
-uniform mat4 m_LightViewProjectionMatrix2;
-uniform mat4 m_LightViewProjectionMatrix3;
-
-
-out vec4 projCoord0;
-out vec4 projCoord1;
-out vec4 projCoord2;
-out vec4 projCoord3;
-
-#ifdef POINTLIGHT
- uniform mat4 m_LightViewProjectionMatrix4;
- uniform mat4 m_LightViewProjectionMatrix5;
- out vec4 projCoord4;
- out vec4 projCoord5;
- out vec4 worldPos;
-#else
- #ifndef PSSM
- uniform vec3 m_LightPos;
- uniform vec3 m_LightDir;
- out float lightDot;
- #endif
-#endif
-
-#if defined(PSSM) || defined(FADE)
- out float shadowPosition;
-#endif
-out vec3 lightVec;
-
-out vec2 texCoord;
-
-in vec3 inPosition;
-
-#ifdef DISCARD_ALPHA
- in vec2 inTexCoord;
-#endif
-
-const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
- 0.0, 0.5, 0.0, 0.0,
- 0.0, 0.0, 0.5, 0.0,
- 0.5, 0.5, 0.5, 1.0);
-
-
-void main(){
- vec4 modelSpacePos = vec4(inPosition, 1.0);
-
- #ifdef NUM_BONES
- Skinning_Compute(modelSpacePos);
- #endif
- gl_Position = TransformWorldViewProjection(modelSpacePos);
-
- #if defined(PSSM) || defined(FADE)
- shadowPosition = gl_Position.z;
- #endif
-
- #ifndef POINTLIGHT
- vec4 worldPos=vec4(0.0);
- #endif
- // get the vertex in world space
- worldPos = TransformWorld(modelSpacePos);
-
- #ifdef DISCARD_ALPHA
- texCoord = inTexCoord;
- #endif
- // populate the light view matrices array and convert vertex to light viewProj space
- projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos;
- projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
- projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
- projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
- #ifdef POINTLIGHT
- projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
- projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
- #else
- #ifndef PSSM
- vec3 lightDir = worldPos.xyz - m_LightPos;
- lightDot = dot(m_LightDir,lightDir);
- #endif
- #endif
-}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag
index 74e59482b..b144dca60 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag
@@ -18,14 +18,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
+uniform vec2 g_ResolutionInverse;
+
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#else
+ uniform vec3 m_LightDir;
#ifndef PSSM
- uniform vec3 m_LightPos;
- uniform vec3 m_LightDir;
+ uniform vec3 m_LightPos;
#endif
#endif
@@ -39,6 +41,19 @@ vec3 getPosition(in float depth, in vec2 uv){
return pos.xyz / pos.w;
}
+vec3 approximateNormal(in vec4 worldPos,in vec2 texCoord){
+ float step = g_ResolutionInverse.x ;
+ float stepy = g_ResolutionInverse.y ;
+ float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r;
+ float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r;
+ vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,-stepy)),1.0);
+ vec4 worldPos3 = vec4(getPosition(depth3,texCoord + vec2(-step,-stepy)),1.0);
+
+ vec3 v1 = (worldPos - worldPos2).xyz;
+ vec3 v2 = (worldPos3 - worldPos2).xyz;
+ return normalize(cross(v1, v2));
+}
+
void main(){
#if !defined( RENDER_SHADOWS )
gl_FragColor = texture2D(m_Texture,texCoord);
@@ -48,6 +63,7 @@ void main(){
float depth = texture2D(m_DepthTexture,texCoord).r;
vec4 color = texture2D(m_Texture,texCoord);
+
//Discard shadow computation on the sky
if(depth == 1.0){
gl_FragColor = color;
@@ -56,6 +72,19 @@ void main(){
// get the vertex in world space
vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
+ vec3 normal = approximateNormal(worldPos, texCoord);
+
+ vec3 lightDir;
+ #ifdef PSSM
+ lightDir = m_LightDir;
+ #else
+ lightDir = worldPos.xyz - m_LightPos;
+ #endif
+ float ndotl = dot(normal, lightDir);
+ if(ndotl > -0.0){
+ gl_FragColor = color;
+ return;
+ }
#if (!defined(POINTLIGHT) && !defined(PSSM))
vec3 lightDir = worldPos.xyz - m_LightPos;
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md
index 9a78752ae..ced2f9fdb 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md
@@ -38,13 +38,15 @@ MaterialDef Post Shadow {
Texture2D Texture
Texture2D DepthTexture
+ Boolean BackfaceShadows: true
}
Technique {
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag
- WorldParameters {
+ WorldParameters {
+ ResolutionInverse
}
Defines {
@@ -59,7 +61,7 @@ MaterialDef Post Shadow {
POINTLIGHT : LightViewProjectionMatrix5
//if no shadow map don't render shadows
RENDER_SHADOWS : ShadowMap0
-
+ BACKFACE_SHADOWS : BackfaceShadows
}
}
@@ -68,7 +70,8 @@ MaterialDef Post Shadow {
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag
- WorldParameters {
+ WorldParameters {
+ ResolutionInverse
}
Defines {
@@ -79,6 +82,7 @@ MaterialDef Post Shadow {
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
+ BACKFACE_SHADOWS : BackfaceShadows
}
}
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag
index b8dcff90f..054382cd6 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag
@@ -1,5 +1,5 @@
#import "Common/ShaderLib/MultiSample.glsllib"
-#import "Common/ShaderLib/Shadows15.glsllib"
+#import "Common/ShaderLib/Shadows.glsllib"
uniform COLORTEXTURE m_Texture;
@@ -20,14 +20,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
+uniform vec2 g_ResolutionInverse;
+
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#else
+ uniform vec3 m_LightDir;
#ifndef PSSM
- uniform vec3 m_LightPos;
- uniform vec3 m_LightDir;
+ uniform vec3 m_LightPos;
#endif
#endif
@@ -41,6 +43,23 @@ vec3 getPosition(in float depth, in vec2 uv){
return pos.xyz / pos.w;
}
+#ifndef BACKFACE_SHADOWS
+ vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){
+ float step = g_ResolutionInverse.x ;
+ float stepy = g_ResolutionInverse.y ;
+ float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r;
+ float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r;
+ vec3 v1, v2;
+ vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0);
+ vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0);
+
+ v1 = normalize((worldPos1 - worldPos)).xyz;
+ v2 = normalize((worldPos2 - worldPos)).xyz;
+ return normalize(cross(v2, v1));
+
+ }
+#endif
+
vec4 main_multiSample(in int numSample){
float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r;
vec4 color = fetchTextureSample(m_Texture,texCoord,numSample);
@@ -52,12 +71,27 @@ vec4 main_multiSample(in int numSample){
// get the vertex in world space
vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
-
+
+
+ vec3 lightDir;
+ #ifdef PSSM
+ lightDir = m_LightDir;
+ #else
+ lightDir = worldPos.xyz - m_LightPos;
+ #endif
+
+ #ifndef BACKFACE_SHADOWS
+ vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample);
+ float ndotl = dot(normal, lightDir);
+ if(ndotl > 0.0){
+ return color;
+ }
+ #endif
+
#if (!defined(POINTLIGHT) && !defined(PSSM))
- vec3 lightDir = worldPos.xyz - m_LightPos;
- if( dot(m_LightDir,lightDir)<0){
- return color;
- }
+ if( dot(m_LightDir,lightDir)<0){
+ return color;
+ }
#endif
// populate the light view matrices array and convert vertex to light viewProj space
diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib
index d2f1a942b..9934db637 100644
--- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib
+++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib
@@ -1,22 +1,57 @@
-#ifdef HARDWARE_SHADOWS
- #define SHADOWMAP sampler2DShadow
- #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
-#else
- #define SHADOWMAP sampler2D
- #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
-#endif
+#if __VERSION__ >= 130
+ // Because gpu_shader5 is actually where those
+ // gather functions are declared to work on shadowmaps
+ #extension GL_ARB_gpu_shader5 : enable
+ #define IVEC2 ivec2
+ #ifdef HARDWARE_SHADOWS
+ #define SHADOWMAP sampler2DShadow
+ #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
+ #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
+ #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
+ #else
+ #define SHADOWMAP sampler2D
+ #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
+ #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
+ #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
+ #endif
-#if FILTER_MODE == 0
- #define GETSHADOW Shadow_DoShadowCompare
- #define KERNEL 1.0
-#elif FILTER_MODE == 1
+ #if FILTER_MODE == 0
+ #define GETSHADOW Shadow_Nearest
+ #define KERNEL 1.0
+ #elif FILTER_MODE == 1
+ #ifdef HARDWARE_SHADOWS
+ #define GETSHADOW Shadow_Nearest
+ #else
+ #define GETSHADOW Shadow_DoBilinear_2x2
+ #endif
+ #define KERNEL 1.0
+ #endif
+#else
+ #define IVEC2 vec2
#ifdef HARDWARE_SHADOWS
- #define GETSHADOW Shadow_DoShadowCompare
+ #define SHADOWMAP sampler2DShadow
+ #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
#else
- #define GETSHADOW Shadow_DoBilinear_2x2
+ #define SHADOWMAP sampler2D
+ #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#endif
- #define KERNEL 1.0
-#elif FILTER_MODE == 2
+
+ #if FILTER_MODE == 0
+ #define GETSHADOW Shadow_DoShadowCompare
+ #define KERNEL 1.0
+ #elif FILTER_MODE == 1
+ #ifdef HARDWARE_SHADOWS
+ #define GETSHADOW Shadow_DoShadowCompare
+ #else
+ #define GETSHADOW Shadow_DoBilinear_2x2
+ #endif
+ #define KERNEL 1.0
+ #endif
+
+
+#endif
+
+#if FILTER_MODE == 2
#define GETSHADOW Shadow_DoDither_2x2
#define KERNEL 1.0
#elif FILTER_MODE == 3
@@ -30,14 +65,13 @@
#define KERNEL 8.0
#endif
-
uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
-uniform SHADOWMAP m_ShadowMap4;
-uniform SHADOWMAP m_ShadowMap5;
+ uniform SHADOWMAP m_ShadowMap4;
+ uniform SHADOWMAP m_ShadowMap5;
#endif
#ifdef PSSM
@@ -49,73 +83,91 @@ uniform float m_ShadowIntensity;
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;
-float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){
- vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
- return SHADOWCOMPARE(tex, coord);
-}
-
-float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){
return SHADOWCOMPARE(tex, projCoord);
}
-float Shadow_BorderCheck(vec2 coord){
+float Shadow_BorderCheck(in vec2 coord){
// Fastest, "hack" method (uses 4-5 instructions)
vec4 t = vec4(coord.xy, 0.0, 1.0);
t = step(t.wwxy, t.xyzz);
return dot(t,t);
}
-float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){
+float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
- return Shadow_DoShadowCompare(tex,projCoord);
+ return SHADOWCOMPARE(tex, projCoord);
+}
+
+float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){
+ vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
+ return SHADOWCOMPARE(tex, coord);
}
-float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){
+
+float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
-
float shadow = 0.0;
- vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
- shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o);
- shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o);
- shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o);
- shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o);
- shadow *= 0.25 ;
+ IVEC2 o = IVEC2(mod(floor(gl_FragCoord.xy), 2.0));
+ shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o));
+ shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o));
+ shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o));
+ shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o));
+ shadow *= 0.25;
return shadow;
}
-float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0)
+ if (border > 0.0){
return 1.0;
+ }
+
vec4 gather = vec4(0.0);
- gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
- gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
- gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
- gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
-
- vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
- vec2 mx = mix( gather.xz, gather.yw, f.x );
- return mix( mx.x, mx.y, f.y );
+ #if __VERSION__ >= 130
+ #ifdef GL_ARB_gpu_shader5
+ vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
+ gather = SHADOWGATHER(tex, coord);
+ #else
+ gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
+ gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
+ gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
+ gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
+ #endif
+ #else
+ gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
+ gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
+ gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
+ gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
+ #endif
+
+ vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
+ vec2 mx = mix( gather.wx, gather.zy, f.x );
+ return mix( mx.x, mx.y, f.y );
}
-float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){
+
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
+
float bound = KERNEL * 0.5 - 0.5;
bound *= PCFEDGE;
for (float y = -bound; y <= bound; y += PCFEDGE){
for (float x = -bound; x <= bound; x += PCFEDGE){
- shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) +
- border,
- 0.0, 1.0);
+ #if __VERSION__ < 130
+ shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0);
+ #else
+ shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y));
+ #endif
}
}
@@ -123,51 +175,51 @@ float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
return shadow;
}
-
//12 tap poisson disk
- const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
- const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
- const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
- const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
- const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
- const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
- const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
- const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
- const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
- const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
- const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
- const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
-
-float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
+const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
+const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
+const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
+const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
+const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
+const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
+const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
+const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
+const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
+const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
+const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
+const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
+
+
+float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0)
+ if (border > 0.0){
return 1.0;
+ }
- vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale);
-
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize);
- shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize);
-
- shadow = shadow * 0.08333333333;//this is divided by 12
- return shadow;
-}
+ vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
+
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
+ shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
+ //this is divided by 12
+ return shadow * 0.08333333333;
+}
-#ifdef POINTLIGHT
- float getPointLightShadows(vec4 worldPos,vec3 lightPos,
- SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5,
- vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){
+#ifdef POINTLIGHT
+ float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
+ in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
+ in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
float shadow = 1.0;
vec3 vect = worldPos.xyz - lightPos;
vec3 absv= abs(vect);
@@ -190,42 +242,41 @@ float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
}else{
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
}
- }
+ }
return shadow;
}
#else
#ifdef PSSM
- float getDirectionalLightShadows(vec4 splits,float shadowPosition,
- SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,
- vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){
- float shadow = 1.0;
+ float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
+ in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
+ in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
+ float shadow = 1.0;
if(shadowPosition < splits.x){
- shadow = GETSHADOW(shadowMap0, projCoord0 );
+ shadow = GETSHADOW(shadowMap0, projCoord0 );
}else if( shadowPosition < splits.y){
shadowBorderScale = 0.5;
- shadow = GETSHADOW(shadowMap1, projCoord1);
+ shadow = GETSHADOW(shadowMap1, projCoord1);
}else if( shadowPosition < splits.z){
shadowBorderScale = 0.25;
- shadow = GETSHADOW(shadowMap2, projCoord2);
+ shadow = GETSHADOW(shadowMap2, projCoord2);
}else if( shadowPosition < splits.w){
shadowBorderScale = 0.125;
- shadow = GETSHADOW(shadowMap3, projCoord3);
+ shadow = GETSHADOW(shadowMap3, projCoord3);
}
return shadow;
}
#else
- float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){
- float shadow = 1.0;
+ float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
+ float shadow = 1.0;
projCoord /= projCoord.w;
- shadow = GETSHADOW(shadowMap, projCoord);
-
+ shadow = GETSHADOW(shadowMap,projCoord);
+
//a small falloff to make the shadow blend nicely into the not lighten
- //we translate the texture coordinate value to a -1,1 range so the length
+ //we translate the texture coordinate value to a -1,1 range so the length
//of the texture coordinate vector is actually the radius of the lighten area on the ground
projCoord = projCoord * 2.0 - 1.0;
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
-
}
#endif
#endif
diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib
deleted file mode 100644
index 5c9e0fa1c..000000000
--- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib
+++ /dev/null
@@ -1,242 +0,0 @@
-// Because gpu_shader5 is actually where those
-// gather functions are declared to work on shadowmaps
-#extension GL_ARB_gpu_shader5 : enable
-
-#ifdef HARDWARE_SHADOWS
- #define SHADOWMAP sampler2DShadow
- #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
- #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
- #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
-#else
- #define SHADOWMAP sampler2D
- #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
- #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
- #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
-#endif
-
-
-#if FILTER_MODE == 0
- #define GETSHADOW Shadow_Nearest
- #define KERNEL 1.0
-#elif FILTER_MODE == 1
- #ifdef HARDWARE_SHADOWS
- #define GETSHADOW Shadow_Nearest
- #else
- #define GETSHADOW Shadow_DoBilinear_2x2
- #endif
- #define KERNEL 1.0
-#elif FILTER_MODE == 2
- #define GETSHADOW Shadow_DoDither_2x2
- #define KERNEL 1.0
-#elif FILTER_MODE == 3
- #define GETSHADOW Shadow_DoPCF
- #define KERNEL 4.0
-#elif FILTER_MODE == 4
- #define GETSHADOW Shadow_DoPCFPoisson
- #define KERNEL 4.0
-#elif FILTER_MODE == 5
- #define GETSHADOW Shadow_DoPCF
- #define KERNEL 8.0
-#endif
-
-
-
-uniform SHADOWMAP m_ShadowMap0;
-uniform SHADOWMAP m_ShadowMap1;
-uniform SHADOWMAP m_ShadowMap2;
-uniform SHADOWMAP m_ShadowMap3;
-#ifdef POINTLIGHT
-uniform SHADOWMAP m_ShadowMap4;
-uniform SHADOWMAP m_ShadowMap5;
-#endif
-
-#ifdef PSSM
-uniform vec4 m_Splits;
-#endif
-uniform float m_ShadowIntensity;
-
-const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
-float shadowBorderScale = 1.0;
-
-float Shadow_BorderCheck(in vec2 coord){
- // Fastest, "hack" method (uses 4-5 instructions)
- vec4 t = vec4(coord.xy, 0.0, 1.0);
- t = step(t.wwxy, t.xyzz);
- return dot(t,t);
-}
-
-float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){
- float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0){
- return 1.0;
- }
- return SHADOWCOMPARE(tex,projCoord);
-}
-
-float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
- float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0)
- return 1.0;
-
- vec2 pixSize = pixSize2 * shadowBorderScale;
-
- float shadow = 0.0;
- ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw));
- shadow *= 0.25;
- return shadow;
-}
-
-float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
- float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0)
- return 1.0;
-
- #ifdef GL_ARB_gpu_shader5
- vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
- vec4 gather = SHADOWGATHER(tex, coord);
- #else
- vec4 gather = vec4(0.0);
- gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
- gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
- gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
- gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
- #endif
-
- vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
- vec2 mx = mix( gather.wx, gather.zy, f.x );
- return mix( mx.x, mx.y, f.y );
-}
-
-float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
-
- vec2 pixSize = pixSize2 * shadowBorderScale;
- float shadow = 0.0;
- float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0)
- return 1.0;
-
- float bound = KERNEL * 0.5 - 0.5;
- bound *= PCFEDGE;
- for (float y = -bound; y <= bound; y += PCFEDGE){
- for (float x = -bound; x <= bound; x += PCFEDGE){
- vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw);
- shadow += SHADOWCOMPARE(tex, coord);
- }
- }
-
- shadow = shadow / (KERNEL * KERNEL);
- return shadow;
-}
-
-
-//12 tap poisson disk
- const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016);
- const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201);
- const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521);
- const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819);
- const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957);
- const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841);
- const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516);
- const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126);
- const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693);
- const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707);
- const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
- const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
-
-
-float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
-
- float shadow = 0.0;
- float border = Shadow_BorderCheck(projCoord.xy);
- if (border > 0.0){
- return 1.0;
- }
-
- vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
-
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
- shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
-
- //this is divided by 12
- return shadow * 0.08333333333;
-}
-
-#ifdef POINTLIGHT
- float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
- in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
- in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
- float shadow = 1.0;
- vec3 vect = worldPos.xyz - lightPos;
- vec3 absv= abs(vect);
- float maxComp = max(absv.x,max(absv.y,absv.z));
- if(maxComp == absv.y){
- if(vect.y < 0.0){
- shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w);
- }else{
- shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
- }
- }else if(maxComp == absv.z){
- if(vect.z < 0.0){
- shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
- }else{
- shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
- }
- }else if(maxComp == absv.x){
- if(vect.x < 0.0){
- shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
- }else{
- shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
- }
- }
- return shadow;
- }
-#else
- #ifdef PSSM
- float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
- in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
- in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
- float shadow = 1.0;
- if(shadowPosition < splits.x){
- shadow = GETSHADOW(shadowMap0, projCoord0 );
- }else if( shadowPosition < splits.y){
- shadowBorderScale = 0.5;
- shadow = GETSHADOW(shadowMap1, projCoord1);
- }else if( shadowPosition < splits.z){
- shadowBorderScale = 0.25;
- shadow = GETSHADOW(shadowMap2, projCoord2);
- }else if( shadowPosition < splits.w){
- shadowBorderScale = 0.125;
- shadow = GETSHADOW(shadowMap3, projCoord3);
- }
- return shadow;
- }
- #else
- float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
- float shadow = 1.0;
- projCoord /= projCoord.w;
- shadow = GETSHADOW(shadowMap,projCoord);
-
- //a small falloff to make the shadow blend nicely into the not lighten
- //we translate the texture coordinate value to a -1,1 range so the length
- //of the texture coordinate vector is actually the radius of the lighten area on the ground
- projCoord = projCoord * 2.0 - 1.0;
- float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
- return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
-
- }
- #endif
-#endif
diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties
index 98168a14e..ae20006c0 100644
--- a/jme3-core/src/main/resources/com/jme3/system/version.properties
+++ b/jme3-core/src/main/resources/com/jme3/system/version.properties
@@ -1,11 +1,12 @@
# THIS IS AN AUTO-GENERATED FILE..
# DO NOT MODIFY!
-build.date=1900-01-01
+build.date=2016-03-25
git.revision=0
git.branch=unknown
git.hash=
git.hash.short=
git.tag=
name.full=jMonkeyEngine 3.1.0-UNKNOWN
+version.full=3.1.0-UNKNOWN
version.number=3.1.0
version.tag=SNAPSHOT
\ No newline at end of file
diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
index 9762635a1..70f918a6b 100644
--- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
+++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
@@ -180,9 +180,23 @@ public class J3MLoader implements AssetLoader {
return matchList;
}
- private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) {
- return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
- texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0;
+ private boolean isTexturePathDeclaredTheTraditionalWay(final List optionValues, final String texturePath) {
+ final boolean startsWithOldStyle = texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
+ texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip ");
+
+ if (!startsWithOldStyle) {
+ return false;
+ }
+
+ if (optionValues.size() == 1 && (optionValues.get(0).textureOption == TextureOption.Flip || optionValues.get(0).textureOption == TextureOption.Repeat)) {
+ return true;
+ } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Flip && optionValues.get(1).textureOption == TextureOption.Repeat) {
+ return true;
+ } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Repeat && optionValues.get(1).textureOption == TextureOption.Flip) {
+ return true;
+ }
+
+ return false;
}
private Texture parseTextureType(final VarType type, final String value) {
@@ -198,7 +212,7 @@ public class J3MLoader implements AssetLoader {
String texturePath = value.trim();
// If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way.
- if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) {
+ if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) {
boolean flipY = false;
if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
@@ -455,6 +469,8 @@ public class J3MLoader implements AssetLoader {
renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1]));
}else if (split[0].equals("AlphaFunc")){
renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1]));
+ }else if (split[0].equals("LineWidth")){
+ renderState.setLineWidth(Float.parseFloat(split[1]));
} else {
throw new MatParseException(null, split[0], statement);
}
@@ -636,6 +652,7 @@ public class J3MLoader implements AssetLoader {
material = new Material(def);
material.setKey(key);
+ material.setName(split[0].trim());
// material.setAssetName(fileName);
}else if (split.length == 1){
if (extending){
diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
index 6438baaac..f45eb9bdf 100644
--- a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
+++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
@@ -80,6 +80,7 @@ public class J3MLoaderTest {
final Texture textureMin = Mockito.mock(Texture.class);
final Texture textureMag = Mockito.mock(Texture.class);
final Texture textureCombined = Mockito.mock(Texture.class);
+ final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class);
final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters);
final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip);
@@ -88,6 +89,7 @@ public class J3MLoaderTest {
setupMockForTexture("Min", "min.png", false, textureMin);
setupMockForTexture("Mag", "mag.png", false, textureMag);
setupMockForTexture("Combined", "combined.png", true, textureCombined);
+ setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle);
j3MLoader.load(assetInfo);
diff --git a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m
index a7619d948..e4b15ad4a 100644
--- a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m
+++ b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m
@@ -6,6 +6,7 @@ Material Test : matdef.j3md {
Min: MinTrilinear "min.png"
Mag: MagBilinear "mag.png"
RepeatAxis: WrapRepeat_T "repeat-axis.png"
- Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png"
+ Combined: Flip MagNearest MinBilinearNoMipMaps WrapRepeat "combined.png"
+ LooksLikeOldStyle: Flip WrapRepeat "oldstyle.png"
}
}
\ No newline at end of file
diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
index 00aa103ff..3b09ec310 100644
--- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
+++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
@@ -20,5 +20,5 @@ void main(void)
Skinning_Compute(modelSpacePos,modelSpaceNormals);
#endif
normal = normalize(TransformNormal(modelSpaceNormals));
- gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+ gl_Position = TransformWorldViewProjection(modelSpacePos);
}
\ No newline at end of file
diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java
index a2251945e..f5f54958e 100644
--- a/jme3-examples/src/main/java/jme3test/TestChooser.java
+++ b/jme3-examples/src/main/java/jme3test/TestChooser.java
@@ -260,8 +260,8 @@ public class TestChooser extends JDialog {
for (int i = 0; i < appClass.length; i++) {
Class> clazz = (Class)appClass[i];
try {
- Object app = clazz.newInstance();
- if (app instanceof Application) {
+ if (Application.class.isAssignableFrom(clazz)) {
+ Object app = clazz.newInstance();
if (app instanceof SimpleApplication) {
final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
settingMethod.invoke(app, showSetting);
@@ -283,7 +283,7 @@ public class TestChooser extends JDialog {
}
} else {
final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
- mainMethod.invoke(app, new Object[]{new String[0]});
+ mainMethod.invoke(clazz, new Object[]{new String[0]});
}
// wait for destroy
System.gc();
diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloner.java b/jme3-examples/src/main/java/jme3test/app/TestCloner.java
new file mode 100644
index 000000000..4ae105603
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/app/TestCloner.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import java.util.*;
+
+import com.jme3.util.clone.*;
+
+/**
+ *
+ *
+ * @author Paul Speed
+ */
+public class TestCloner {
+
+ public static void main( String... args ) {
+
+ System.out.println("Clone test:");
+
+ Cloner cloner = new Cloner();
+
+ RegularObject ro = new RegularObject(42);
+ System.out.println("Regular Object:" + ro);
+ RegularObject roCloneLegacy = ro.clone();
+ System.out.println("Regular Object Clone:" + roCloneLegacy);
+ RegularObject roClone = cloner.clone(ro);
+ System.out.println("cloner: Regular Object Clone:" + roClone);
+
+ System.out.println("------------------------------------");
+ System.out.println();
+
+ cloner = new Cloner();
+ RegularSubclass rsc = new RegularSubclass(69, "test");
+ System.out.println("Regular subclass:" + rsc);
+ RegularSubclass rscCloneLegacy = (RegularSubclass)rsc.clone();
+ System.out.println("Regular subclass Clone:" + rscCloneLegacy);
+ RegularSubclass rscClone = cloner.clone(rsc);
+ System.out.println("cloner: Regular subclass Clone:" + rscClone);
+
+ System.out.println("------------------------------------");
+ System.out.println();
+
+ cloner = new Cloner();
+ Parent parent = new Parent("Foo", 34);
+ System.out.println("Parent:" + parent);
+ Parent parentCloneLegacy = parent.clone();
+ System.out.println("Parent Clone:" + parentCloneLegacy);
+ Parent parentClone = cloner.clone(parent);
+ System.out.println("cloner: Parent Clone:" + parentClone);
+
+ System.out.println("------------------------------------");
+ System.out.println();
+
+ cloner = new Cloner();
+ GraphNode root = new GraphNode("root");
+ GraphNode child1 = root.addLink("child1");
+ GraphNode child2 = root.addLink("child2");
+ GraphNode shared = child1.addLink("shared");
+ child2.addLink(shared);
+
+ // Add a circular reference to get fancy
+ shared.addLink(root);
+
+ System.out.println("Simple graph:");
+ root.dump(" ");
+
+ GraphNode rootClone = cloner.clone(root);
+ System.out.println("clone:");
+ rootClone.dump(" ");
+
+ System.out.println("original:");
+ root.dump(" ");
+
+ GraphNode reclone = Cloner.deepClone(root);
+ System.out.println("reclone:");
+ reclone.dump(" ");
+
+ System.out.println("------------------------------------");
+ System.out.println();
+ cloner = new Cloner();
+
+ ArrayHolder arrays = new ArrayHolder(5, 3, 7, 3, 7, 2, 1, 4);
+ System.out.println("Array holder:" + arrays);
+ ArrayHolder arraysClone = cloner.clone(arrays);
+ System.out.println("Array holder clone:" + arraysClone);
+
+
+
+ }
+
+ public static class RegularObject implements Cloneable {
+ protected int i;
+
+ public RegularObject( int i ) {
+ this.i = i;
+ }
+
+ public RegularObject clone() {
+ try {
+ return (RegularObject)super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String toString() {
+ return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+ + "[i=" + i + "]";
+ }
+ }
+
+ public static class RegularSubclass extends RegularObject {
+ protected String name;
+
+ public RegularSubclass( int i, String name ) {
+ super(i);
+ this.name = name;
+ }
+
+ public String toString() {
+ return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+ + "[i=" + i + ", name=" + name + "]";
+ }
+ }
+
+ public static class Parent implements Cloneable, JmeCloneable {
+
+ private RegularObject ro;
+ private RegularSubclass rsc;
+
+ public Parent( String name, int age ) {
+ this.ro = new RegularObject(age);
+ this.rsc = new RegularSubclass(age, name);
+ }
+
+ public Parent clone() {
+ try {
+ return (Parent)super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Parent jmeClone() {
+ // Ok to delegate to clone() in this case because no deep
+ // cloning is done there.
+ return clone();
+ }
+
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.ro = cloner.clone(ro);
+ this.rsc = cloner.clone(rsc);
+ }
+
+ public String toString() {
+ return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+ + "[ro=" + ro + ", rsc=" + rsc + "]";
+ }
+ }
+
+ public static class GraphNode implements Cloneable, JmeCloneable {
+
+ private String name;
+ private List links = new ArrayList<>();
+
+ public GraphNode( String name ) {
+ this.name = name;
+ }
+
+ public void dump( String indent ) {
+ dump(indent, new HashSet());
+ }
+
+ private void dump( String indent, Set visited ) {
+ if( visited.contains(this) ) {
+ // already been here
+ System.out.println(indent + this + " ** circular.");
+ return;
+ }
+ System.out.println(indent + this);
+ visited.add(this);
+ for( GraphNode n : links ) {
+ n.dump(indent + " ", visited);
+ }
+ visited.remove(this);
+ }
+
+ public GraphNode addLink( String name ) {
+ GraphNode node = new GraphNode(name);
+ links.add(node);
+ return node;
+ }
+
+ public GraphNode addLink( GraphNode node ) {
+ links.add(node);
+ return node;
+ }
+
+ public List getLinks() {
+ return links;
+ }
+
+ public GraphNode jmeClone() {
+ try {
+ return (GraphNode)super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.links = cloner.clone(links);
+ }
+
+ public String toString() {
+ return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+ + "[name=" + name + "]";
+ }
+ }
+
+ public static class ArrayHolder implements JmeCloneable {
+
+ private int[] intArray;
+ private int[][] intArray2D;
+ private Object[] objects;
+ private RegularObject[] regularObjects;
+ private String[] strings;
+
+ public ArrayHolder( int... values ) {
+ this.intArray = values;
+ this.intArray2D = new int[values.length][2];
+ for( int i = 0; i < values.length; i++ ) {
+ intArray2D[i][0] = values[i] + 1;
+ intArray2D[i][1] = values[i] * 2;
+ }
+ this.objects = new Object[values.length];
+ this.regularObjects = new RegularObject[values.length];
+ this.strings = new String[values.length];
+ for( int i = 0; i < values.length; i++ ) {
+ objects[i] = values[i];
+ regularObjects[i] = new RegularObject(values[i]);
+ strings[i] = String.valueOf(values[i]);
+ }
+ }
+
+ public ArrayHolder jmeClone() {
+ try {
+ return (ArrayHolder)super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void cloneFields( Cloner cloner, Object original ) {
+ intArray = cloner.clone(intArray);
+ intArray2D = cloner.clone(intArray2D);
+
+ // Boxed types are not cloneable so this will fail
+ //objects = cloner.clone(objects);
+
+ regularObjects = cloner.clone(regularObjects);
+
+ // Strings are also not cloneable
+ //strings = cloner.clone(strings);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("intArray=" + intArray);
+ for( int i = 0; i < intArray.length; i++ ) {
+ if( i == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(intArray[i]);
+ }
+ sb.append("], ");
+
+ sb.append("intArray2D=" + intArray2D);
+ for( int i = 0; i < intArray2D.length; i++ ) {
+ if( i == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append("intArray2D[" + i + "]=" + intArray2D[i]);
+ for( int j = 0; j < 2; j++ ) {
+ if( j == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(intArray2D[i][j]);
+ }
+ sb.append("], ");
+ }
+ sb.append("], ");
+
+ sb.append("objectArray=" + objects);
+ for( int i = 0; i < objects.length; i++ ) {
+ if( i == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(objects[i]);
+ }
+ sb.append("], ");
+
+ sb.append("objectArray=" + regularObjects);
+ for( int i = 0; i < regularObjects.length; i++ ) {
+ if( i == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(regularObjects[i]);
+ }
+ sb.append("], ");
+
+ sb.append("stringArray=" + strings);
+ for( int i = 0; i < strings.length; i++ ) {
+ if( i == 0 ) {
+ sb.append("[");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(strings[i]);
+ }
+ sb.append("]");
+
+ return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+ + "[" + sb + "]";
+ }
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java
index 469fb03ae..a286501bb 100644
--- a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java
+++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java
@@ -46,13 +46,15 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank
* @author normenhansen
*/
-public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener {
+public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener, JmeCloneable {
protected Spatial spatial;
protected boolean enabled = true;
@@ -94,10 +96,21 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro
createWheels();
}
+ @Override
public Control cloneForSpatial(Spatial spatial) {
throw new UnsupportedOperationException("Not supported yet.");
}
+ @Override
+ public Object jmeClone() {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
public void setSpatial(Spatial spatial) {
this.spatial = spatial;
setUserObject(spatial);
@@ -179,6 +192,7 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro
}
public void setPhysicsSpace(PhysicsSpace space) {
+ createVehicle(space);
if (space == null) {
if (this.space != null) {
this.space.removeCollisionObject(this);
diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java
index 5f058e68b..d1def4203 100644
--- a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java
+++ b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java
@@ -147,13 +147,13 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene
spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0}));
hoverControl = new PhysicsHoverControl(colShape, 500);
- hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
spaceCraft.addControl(hoverControl);
rootNode.attachChild(spaceCraft);
getPhysicsSpace().add(hoverControl);
+ hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
spaceCraft.addControl(chaseCam);
diff --git a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
index 84d186e3a..f86f956b6 100644
--- a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
+++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
@@ -128,12 +128,12 @@ public class TestMousePick extends SimpleApplication {
/** A red ball that marks the last spot that was "hit" by the "shot". */
protected void initMark() {
Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));
- arrow.setLineWidth(3);
//Sphere sphere = new Sphere(30, 30, 0.2f);
mark = new Geometry("BOOM!", arrow);
//mark = new Geometry("BOOM!", sphere);
Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mark_mat.getAdditionalRenderState().setLineWidth(3);
mark_mat.setColor("Color", ColorRGBA.Red);
mark.setMaterial(mark_mat);
}
diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
index eaf516fae..14f220374 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
@@ -40,12 +40,14 @@ import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
+import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.ssao.SSAOFilter;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
@@ -69,6 +71,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
private Geometry ground;
private Material matGroundU;
private Material matGroundL;
+ private AmbientLight al;
public static void main(String[] args) {
TestDirectionalLightShadow app = new TestDirectionalLightShadow();
@@ -99,7 +102,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
mat[1].setBoolean("UseMaterialColors", true);
- mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
+ mat[1].setColor("Ambient", ColorRGBA.White);
mat[1].setColor("Diffuse", ColorRGBA.White.clone());
@@ -110,9 +113,14 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
TangentBinormalGenerator.generate(obj[1]);
TangentBinormalGenerator.generate(obj[0]);
+ Spatial t = obj[0].clone(false);
+ t.setLocalScale(10f);
+ t.setMaterial(mat[1]);
+ rootNode.attachChild(t);
+ t.setLocalTranslation(0, 25, 0);
for (int i = 0; i < 60; i++) {
- Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
+ t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
t.setLocalScale(FastMath.nextRandomFloat() * 10f);
t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
rootNode.attachChild(t);
@@ -142,8 +150,8 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
rootNode.addLight(l);
- AmbientLight al = new AmbientLight();
- al.setColor(ColorRGBA.White.mult(0.5f));
+ al = new AmbientLight();
+ al.setColor(ColorRGBA.White.mult(0.02f));
rootNode.addLight(al);
Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
@@ -156,8 +164,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
@Override
public void simpleInitApp() {
// put the camera in a bad position
- cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f));
- cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f));
+// cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f));
+// cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f));
+
+ cam.setLocation(new Vector3f(3.3720117f, 42.838284f, -83.43792f));
+ cam.setRotation(new Quaternion(0.13833192f, -0.08969371f, 0.012581267f, 0.9862358f));
flyCam.setMoveSpeed(100);
@@ -166,7 +177,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3);
dlsr.setLight(l);
dlsr.setLambda(0.55f);
- dlsr.setShadowIntensity(0.6f);
+ dlsr.setShadowIntensity(0.8f);
dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
dlsr.displayDebug();
viewPort.addProcessor(dlsr);
@@ -174,7 +185,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3);
dlsf.setLight(l);
dlsf.setLambda(0.55f);
- dlsf.setShadowIntensity(0.6f);
+ dlsf.setShadowIntensity(0.8f);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
dlsf.setEnabled(false);
@@ -205,10 +216,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP));
inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN));
inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P));
+ inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_B));
inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
- "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance");
+ "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows");
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
@@ -255,12 +267,19 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
dlsf.setLambda(dlsr.getLambda() - 0.01f);
System.out.println("Lambda : " + dlsr.getLambda());
}
-
+ if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && keyPressed) {
+ al.setColor(ColorRGBA.White.mult((1 - dlsr.getShadowIntensity()) * 0.2f));
+ }
if (name.equals("debug") && keyPressed) {
dlsr.displayFrustum();
}
+ if (name.equals("backShadows") && keyPressed) {
+ dlsr.setRenderBackFacesShadows(!dlsr.isRenderBackFacesShadows());
+ dlsf.setRenderBackFacesShadows(!dlsf.isRenderBackFacesShadows());
+ }
+
if (name.equals("stabilize") && keyPressed) {
dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization());
dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization());
diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java
index a98ac03db..80079711d 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java
@@ -32,7 +32,10 @@
package jme3test.light;
import com.jme3.app.SimpleApplication;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.light.AmbientLight;
import com.jme3.light.PointLight;
+import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
@@ -45,7 +48,7 @@ import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.shadow.PointLightShadowFilter;
import com.jme3.shadow.PointLightShadowRenderer;
-public class TestPointLightShadows extends SimpleApplication {
+public class TestPointLightShadows extends SimpleApplication implements ActionListener{
public static final int SHADOWMAP_SIZE = 512;
public static void main(String[] args) {
@@ -55,13 +58,19 @@ public class TestPointLightShadows extends SimpleApplication {
Node lightNode;
PointLightShadowRenderer plsr;
PointLightShadowFilter plsf;
+ AmbientLight al;
@Override
- public void simpleInitApp() {
+ public void simpleInitApp () {
flyCam.setMoveSpeed(10);
cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f));
cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f));
+ al = new AmbientLight(ColorRGBA.White.mult(0.02f));
+ rootNode.addLight(al);
+
+
+
Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o");
scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
@@ -89,6 +98,7 @@ public class TestPointLightShadows extends SimpleApplication {
plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
plsr.setShadowZExtend(15);
plsr.setShadowZFadeLength(5);
+ plsr.setShadowIntensity(0.9f);
// plsr.setFlushQueues(false);
//plsr.displayFrustum();
plsr.displayDebug();
@@ -99,18 +109,27 @@ public class TestPointLightShadows extends SimpleApplication {
plsf.setLight((PointLight) scene.getLocalLightList().get(0));
plsf.setShadowZExtend(15);
plsf.setShadowZFadeLength(5);
+ plsf.setShadowIntensity(0.8f);
plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
plsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(plsf);
viewPort.addProcessor(fpp);
-
+ inputManager.addListener(this,"ShadowUp","ShadowDown");
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort);
+
}
@Override
public void simpleUpdate(float tpf) {
// lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f);
}
+
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && isPressed) {
+ al.setColor(ColorRGBA.White.mult((1 - plsr.getShadowIntensity()) * 0.2f));
+ }
+ }
}
\ No newline at end of file
diff --git a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java
index 4c74b0664..55d5a3f87 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java
@@ -67,6 +67,8 @@ import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class TestPssmShadow extends SimpleApplication implements ActionListener {
@@ -249,10 +251,20 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
time = 0;
}
+ @Override
+ public Object jmeClone() {
+ return null;
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ }
+
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
+ @Override
public Control cloneForSpatial(Spatial spatial) {
return null;
}
diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java
index 8d29ae498..7a6f35c37 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java
@@ -58,7 +58,7 @@ public class TestSpotLight extends SimpleApplication {
Geometry lightMdl;
public void setupLighting(){
AmbientLight al=new AmbientLight();
- al.setColor(ColorRGBA.White.mult(0.8f));
+ al.setColor(ColorRGBA.White.mult(0.02f));
rootNode.addLight(al);
spot=new SpotLight();
diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java
index fbf0e1b67..8df514fd3 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java
@@ -65,7 +65,7 @@ public class TestSpotLightShadows extends SimpleApplication {
public void setupLighting() {
AmbientLight al = new AmbientLight();
- al.setColor(ColorRGBA.White.mult(0.3f));
+ al.setColor(ColorRGBA.White.mult(0.02f));
rootNode.addLight(al);
rootNode.setShadowMode(ShadowMode.CastAndReceive);
@@ -132,11 +132,6 @@ public class TestSpotLightShadows extends SimpleApplication {
}, "stop");
inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1));
-
- MaterialDebugAppState s = new MaterialDebugAppState();
- s.registerBinding("Common/MatDefs/Shadow/PostShadow15.frag", rootNode);
- s.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode);
- stateManager.attach(s);
flyCam.setDragToRotate(true);
}
diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java
index 4fb58602c..f656b1fd6 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java
@@ -95,7 +95,7 @@ public class TestSpotLightTerrain extends SimpleApplication {
rootNode.addLight(sl);
AmbientLight ambLight = new AmbientLight();
- ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f));
+ ambLight.setColor(ColorRGBA.Black);
rootNode.addLight(ambLight);
cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f));
diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java
index a786d767d..21817e7c8 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java
@@ -72,7 +72,6 @@ public class TestTangentGenBadModels extends SimpleApplication {
"debug tangents geom",
TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f)
);
- debug.getMesh().setLineWidth(1);
debug.setMaterial(debugMat);
debug.setCullHint(Spatial.CullHint.Never);
debug.setLocalTransform(g.getWorldTransform());
diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java
new file mode 100644
index 000000000..9f8daf8e7
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java
@@ -0,0 +1,100 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.*;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TangentBinormalGenerator;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+
+/**
+ * test
+ *
+ * @author normenhansen
+ */
+public class TestTangentSpace extends SimpleApplication {
+
+ public static void main(String[] args) {
+ TestTangentSpace app = new TestTangentSpace();
+ app.start();
+ }
+
+ private Node debugNode = new Node("debug");
+
+ @Override
+ public void simpleInitApp() {
+ renderManager.setSinglePassLightBatchSize(2);
+ renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
+ initView();
+
+ Spatial s = assetManager.loadModel("Models/Test/BasicCubeLow.obj");
+ rootNode.attachChild(s);
+
+ Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ m.setTexture("NormalMap", assetManager.loadTexture("Models/Test/Normal_pixel.png"));
+
+ Geometry g = (Geometry)s;
+ Geometry g2 = (Geometry) g.deepClone();
+ g2.move(5, 0, 0);
+ g.getParent().attachChild(g2);
+
+ g.setMaterial(m);
+ g2.setMaterial(m);
+
+ //Regular tangent generation (left geom)
+ TangentBinormalGenerator.generate(g2.getMesh(), true);
+
+ //MikkTSPace Tangent generation (right geom)
+
+ MikktspaceTangentGenerator.generate(g);
+
+ createDebugTangents(g2);
+ createDebugTangents(g);
+
+ inputManager.addListener(new ActionListener() {
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if (name.equals("toggleDebug") && isPressed) {
+ if (debugNode.getParent() == null) {
+ rootNode.attachChild(debugNode);
+ } else {
+ debugNode.removeFromParent();
+ }
+ }
+ }
+ }, "toggleDebug");
+
+ inputManager.addMapping("toggleDebug", new KeyTrigger(KeyInput.KEY_SPACE));
+
+
+ DirectionalLight dl = new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal());
+ rootNode.addLight(dl);
+ }
+
+ private void initView() {
+ viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+ cam.setLocation(new Vector3f(8.569681f, 3.335546f, 5.4372444f));
+ cam.setRotation(new Quaternion(-0.07608022f, 0.9086564f, -0.18992864f, -0.3639813f));
+ flyCam.setMoveSpeed(10);
+ }
+
+ private void createDebugTangents(Geometry geom) {
+ Geometry debug = new Geometry(
+ "Debug " + geom.getName(),
+ TangentBinormalGenerator.genTbnLines(geom.getMesh(), 0.8f)
+ );
+ Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+ debug.setMaterial(debugMat);
+ debug.setCullHint(Spatial.CullHint.Never);
+ debug.getLocalTranslation().set(geom.getWorldTranslation());
+ debugNode.attachChild(debug);
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
index 6c4c45e2f..fc4731615 100644
--- a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
+++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
@@ -50,10 +50,11 @@ public class TestDebugShapes extends SimpleApplication {
app.start();
}
- public Geometry putShape(Mesh shape, ColorRGBA color){
+ public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){
Geometry g = new Geometry("shape", shape);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true);
+ mat.getAdditionalRenderState().setLineWidth(lineWidth);
mat.setColor("Color", color);
g.setMaterial(mat);
rootNode.attachChild(g);
@@ -62,20 +63,19 @@ public class TestDebugShapes extends SimpleApplication {
public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){
Arrow arrow = new Arrow(dir);
- arrow.setLineWidth(4); // make arrow thicker
- putShape(arrow, color).setLocalTranslation(pos);
+ putShape(arrow, color, 4).setLocalTranslation(pos);
}
public void putBox(Vector3f pos, float size, ColorRGBA color){
- putShape(new WireBox(size, size, size), color).setLocalTranslation(pos);
+ putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos);
}
public void putGrid(Vector3f pos, ColorRGBA color){
- putShape(new Grid(6, 6, 0.2f), color).center().move(pos);
+ putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos);
}
public void putSphere(Vector3f pos, ColorRGBA color){
- putShape(new WireSphere(1), color).setLocalTranslation(pos);
+ putShape(new WireSphere(1), color, 1).setLocalTranslation(pos);
}
@Override
diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java
index a36853913..2e8ecbfaf 100644
--- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java
+++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java
@@ -41,6 +41,8 @@ import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.swing.*;
import jme3test.network.TestChatServer.ChatMessage;
@@ -115,6 +117,18 @@ public class TestChatClient extends JFrame {
public static void main(String... args) throws Exception {
+ // Increate the logging level for networking...
+ System.out.println("Setting logging to max");
+ Logger networkLog = Logger.getLogger("com.jme3.network");
+ networkLog.setLevel(Level.FINEST);
+
+ // And we have to tell JUL's handler also
+ // turn up logging in a very convoluted way
+ Logger rootLog = Logger.getLogger("");
+ if( rootLog.getHandlers().length > 0 ) {
+ rootLog.getHandlers()[0].setLevel(Level.FINEST);
+ }
+
// Note: in JME 3.1 this is generally unnecessary as the server will
// send a message with all server-registered classes.
// TestChatServer.initializeClasses();
diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java
index 4d8642881..8bdb740b9 100644
--- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java
+++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java
@@ -31,6 +31,9 @@
*/
package jme3test.network;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
import com.jme3.network.*;
import com.jme3.network.serializing.Serializable;
import com.jme3.network.serializing.Serializer;
@@ -56,11 +59,15 @@ public class TestChatServer {
private boolean isRunning;
public TestChatServer() throws IOException {
- initializeClasses();
// Use this to test the client/server name version check
this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
+ // Initialize our own messages only after the server has been created.
+ // It registers some additional messages with the serializer by default
+ // that need to go before custom messages.
+ initializeClasses();
+
ChatHandler handler = new ChatHandler();
server.addMessageListener(handler, ChatMessage.class);
@@ -121,6 +128,18 @@ public class TestChatServer {
}
public static void main(String... args) throws Exception {
+
+ // Increate the logging level for networking...
+ System.out.println("Setting logging to max");
+ Logger networkLog = Logger.getLogger("com.jme3.network");
+ networkLog.setLevel(Level.FINEST);
+
+ // And we have to tell JUL's handler also
+ // turn up logging in a very convoluted way
+ Logger rootLog = Logger.getLogger("");
+ if( rootLog.getHandlers().length > 0 ) {
+ rootLog.getHandlers()[0].setLevel(Level.FINEST);
+ }
TestChatServer chatServer = new TestChatServer();
chatServer.start();
diff --git a/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java
new file mode 100644
index 000000000..6bd629823
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.scene;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+public class TestLineWidthRenderState extends SimpleApplication {
+
+ private Material mat;
+
+ public static void main(String[] args){
+ TestLineWidthRenderState app = new TestLineWidthRenderState();
+ app.start();
+ }
+
+
+
+ @Override
+ public void simpleInitApp() {
+ setDisplayFps(false);
+ setDisplayStatView(false);
+ cam.setLocation(new Vector3f(5.5826545f, 3.6192513f, 8.016988f));
+ cam.setRotation(new Quaternion(-0.04787097f, 0.9463123f, -0.16569641f, -0.27339742f));
+
+ Box b = new Box(1, 1, 1);
+ Geometry geom = new Geometry("Box", b);
+ mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.setColor("Color", ColorRGBA.Blue);
+ mat.getAdditionalRenderState().setWireframe(true);
+ mat.getAdditionalRenderState().setLineWidth(2);
+ geom.setMaterial(mat);
+ rootNode.attachChild(geom);
+
+ inputManager.addListener(new ActionListener() {
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if(name.equals("up") && isPressed){
+ mat.getAdditionalRenderState().setLineWidth(mat.getAdditionalRenderState().getLineWidth() + 1);
+ }
+ if(name.equals("down") && isPressed){
+ mat.getAdditionalRenderState().setLineWidth(Math.max(mat.getAdditionalRenderState().getLineWidth() - 1, 1));
+ }
+ }
+ }, "up", "down");
+ inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_U));
+ inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_J));
+ }
+}
\ No newline at end of file
diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle
index dabbe62a0..b26434cdd 100644
--- a/jme3-plugins/build.gradle
+++ b/jme3-plugins/build.gradle
@@ -14,4 +14,5 @@ sourceSets {
dependencies {
compile project(':jme3-core')
+ testCompile project(':jme3-desktop')
}
diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java
new file mode 100644
index 000000000..0535c4582
--- /dev/null
+++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java
@@ -0,0 +1,78 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Saves a Material to a j3m file with proper formatting.
+ *
+ * usage is :
+ *
+ * J3MExporter exporter = new J3MExporter();
+ * exporter.save(material, myFile);
+ * //or
+ * exporter.save(material, myOutputStream);
+ *
+ *
+ * @author tsr
+ * @author nehon (documentation and safety check)
+ */
+public class J3MExporter implements JmeExporter {
+
+ private final J3MRootOutputCapsule rootCapsule;
+
+ /**
+ * Create a J3MExporter
+ */
+ public J3MExporter() {
+ rootCapsule = new J3MRootOutputCapsule(this);
+ }
+
+ @Override
+ public void save(Savable object, OutputStream f) throws IOException {
+
+ if (!(object instanceof Material)) {
+ throw new IllegalArgumentException("J3MExporter can only save com.jme3.material.Material class");
+ }
+
+ OutputStreamWriter out = new OutputStreamWriter(f, Charset.forName("UTF-8"));
+
+ rootCapsule.clear();
+ object.write(this);
+ rootCapsule.writeToStream(out);
+
+ out.flush();
+ }
+
+ @Override
+ public void save(Savable object, File f) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(f)) {
+ save(object, fos);
+ }
+ }
+
+ @Override
+ public OutputCapsule getCapsule(Savable object) {
+ if ((object instanceof Material) || (object instanceof MaterialDef)) {
+ return rootCapsule;
+ }
+
+ return rootCapsule.getCapsule(object);
+ }
+
+}
diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java
new file mode 100644
index 000000000..7dd6a2a59
--- /dev/null
+++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java
@@ -0,0 +1,383 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.material.MatParam;
+import com.jme3.material.MatParamTexture;
+import com.jme3.math.*;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.IntMap;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author tsr
+ */
+public class J3MOutputCapsule implements OutputCapsule {
+
+ private final HashMap parameters;
+ protected final J3MExporter exporter;
+
+ public J3MOutputCapsule(J3MExporter exporter) {
+ this.exporter = exporter;
+ parameters = new HashMap<>();
+ }
+
+ public void writeToStream(OutputStreamWriter out) throws IOException {
+ for (String key : parameters.keySet()) {
+ out.write(" ");
+ writeParameter(out, key, parameters.get(key));
+ out.write("\n");
+ }
+ }
+
+ protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException {
+ out.write(name);
+ out.write(" : ");
+ out.write(value);
+ }
+
+ public void clear() {
+ parameters.clear();
+ }
+
+ protected void putParameter(String name, String value) {
+ parameters.put(name, value);
+ }
+
+ @Override
+ public void write(boolean value, String name, boolean defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+
+ putParameter(name, ((value) ? "On" : "Off"));
+ }
+
+ @Override
+ public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException {
+ for (String key : map.keySet()) {
+ Savable value = map.get(key);
+ if (defVal == null || !defVal.containsKey(key) || !defVal.get(key).equals(value)) {
+ putParameter(key, format(value));
+ }
+ }
+ }
+
+ protected String format(Savable value) {
+ if (value instanceof MatParamTexture) {
+ return formatMatParamTexture((MatParamTexture) value);
+ }
+ if (value instanceof MatParam) {
+ return formatMatParam((MatParam) value);
+ }
+
+ throw new UnsupportedOperationException(value.getClass() + ": Not supported yet.");
+ }
+
+ private String formatMatParam(MatParam param){
+ VarType type = param.getVarType();
+ Object val = param.getValue();
+ switch (type) {
+ case Boolean:
+ case Float:
+ case Int:
+ return val.toString();
+ case Vector2:
+ Vector2f v2 = (Vector2f) val;
+ return v2.getX() + " " + v2.getY();
+ case Vector3:
+ Vector3f v3 = (Vector3f) val;
+ return v3.getX() + " " + v3.getY() + " " + v3.getZ();
+ case Vector4:
+ // can be either ColorRGBA, Vector4f or Quaternion
+ if (val instanceof Vector4f) {
+ Vector4f v4 = (Vector4f) val;
+ return v4.getX() + " " + v4.getY() + " "
+ + v4.getZ() + " " + v4.getW();
+ } else if (val instanceof ColorRGBA) {
+ ColorRGBA color = (ColorRGBA) val;
+ return color.getRed() + " " + color.getGreen() + " "
+ + color.getBlue() + " " + color.getAlpha();
+ } else if (val instanceof Quaternion) {
+ Quaternion quat = (Quaternion) val;
+ return quat.getX() + " " + quat.getY() + " "
+ + quat.getZ() + " " + quat.getW();
+ } else {
+ throw new UnsupportedOperationException("Unexpected Vector4 type: " + val);
+ }
+
+ default:
+ return null; // parameter type not supported in J3M
+ }
+ }
+
+ protected static String formatMatParamTexture(MatParamTexture param) {
+ StringBuilder ret = new StringBuilder();
+ Texture tex = (Texture) param.getValue();
+ TextureKey key;
+ if (tex != null) {
+ key = (TextureKey) tex.getKey();
+
+ if (key != null && key.isFlipY()) {
+ ret.append("Flip ");
+ }
+
+ ret.append(formatWrapMode(tex, Texture.WrapAxis.S));
+ ret.append(formatWrapMode(tex, Texture.WrapAxis.T));
+ ret.append(formatWrapMode(tex, Texture.WrapAxis.R));
+
+ //Min and Mag filter
+ Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
+ if (tex.getImage().hasMipmaps() || (key != null && key.isGenerateMips())) {
+ def = Texture.MinFilter.Trilinear;
+ }
+ if (tex.getMinFilter() != def) {
+ ret.append("Min").append(tex.getMinFilter().name()).append(" ");
+ }
+
+ if (tex.getMagFilter() != Texture.MagFilter.Bilinear) {
+ ret.append("Mag").append(tex.getMagFilter().name()).append(" ");
+ }
+
+ ret.append("\"").append(key.getName()).append("\"");
+ }
+
+ return ret.toString();
+ }
+
+ protected static String formatWrapMode(Texture texVal, Texture.WrapAxis axis) {
+ WrapMode mode;
+ try {
+ mode = texVal.getWrap(axis);
+ } catch (IllegalArgumentException e) {
+ //this axis doesn't exist on the texture
+ return "";
+ }
+ if (mode != WrapMode.EdgeClamp) {
+ return "Wrap" + mode.name() + "_" + axis.name() + " ";
+ }
+ return "";
+ }
+
+ @Override
+ public void write(Enum value, String name, Enum defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+
+ putParameter(name, value.toString());
+ }
+
+ @Override
+ public void write(float value, String name, float defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+
+ putParameter(name, Float.toString(value));
+ }
+
+ @Override
+ public void write(float[] value, String name, float[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(float[][] value, String name, float[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(double value, String name, double defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(double[] value, String name, double[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(double[][] value, String name, double[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(long value, String name, long defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(long[] value, String name, long[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(long[][] value, String name, long[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(short value, String name, short defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(short[] value, String name, short[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(short[][] value, String name, short[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(boolean[] value, String name, boolean[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(String value, String name, String defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(String[] value, String name, String[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(String[][] value, String name, String[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(BitSet value, String name, BitSet defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(Savable object, String name, Savable defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(Savable[] objects, String name, Savable[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeByteBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeSavableMap(Map extends Savable, ? extends Savable> map, String name, Map extends Savable, ? extends Savable> defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void writeIntSavableMap(IntMap extends Savable> map, String name, IntMap extends Savable> defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(byte value, String name, byte defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(byte[] value, String name, byte[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(byte[][] value, String name, byte[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(int value, String name, int defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(int[] value, String name, int[] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void write(int[][] value, String name, int[][] defVal) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+}
diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java
new file mode 100644
index 000000000..02c8df02c
--- /dev/null
+++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java
@@ -0,0 +1,82 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+
+/**
+ *
+ * @author tsr
+ */
+public class J3MRenderStateOutputCapsule extends J3MOutputCapsule {
+ protected final static HashMap NAME_MAP;
+ protected String offsetUnit;
+
+ static {
+ NAME_MAP = new HashMap<>();
+ NAME_MAP.put( "wireframe", "Wireframe");
+ NAME_MAP.put( "cullMode", "FaceCull");
+ NAME_MAP.put( "depthWrite", "DepthWrite");
+ NAME_MAP.put( "depthTest", "DepthTest");
+ NAME_MAP.put( "blendMode", "Blend");
+ NAME_MAP.put( "alphaFallOff", "AlphaTestFalloff");
+ NAME_MAP.put( "offsetFactor", "PolyOffset");
+ NAME_MAP.put( "colorWrite", "ColorWrite");
+ NAME_MAP.put( "pointSprite", "PointSprite");
+ NAME_MAP.put( "depthFunc", "DepthFunc");
+ NAME_MAP.put( "alphaFunc", "AlphaFunc");
+ NAME_MAP.put( "lineWidth", "LineWidth");
+ }
+ public J3MRenderStateOutputCapsule(J3MExporter exporter) {
+ super(exporter);
+ }
+
+ public OutputCapsule getCapsule(Savable object) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ offsetUnit = "";
+ }
+
+ @Override
+ public void writeToStream(OutputStreamWriter out) throws IOException {
+ out.write(" AdditionalRenderState {\n");
+ super.writeToStream(out);
+ out.write(" }\n");
+ }
+
+ @Override
+ protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException {
+ out.write(name);
+ out.write(" ");
+ out.write(value);
+
+ if( "PolyOffset".equals(name) ) {
+ out.write(" ");
+ out.write(offsetUnit);
+ }
+ }
+
+ @Override
+ protected void putParameter(String name, String value ) {
+ if( "offsetUnits".equals(name) ) {
+ offsetUnit = value;
+ return;
+ }
+
+ if( !NAME_MAP.containsKey(name) )
+ return;
+
+ super.putParameter(NAME_MAP.get(name), value);
+ }
+}
diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java
new file mode 100644
index 000000000..40130b705
--- /dev/null
+++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java
@@ -0,0 +1,99 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+
+/**
+ * @author tsr
+ */
+public class J3MRootOutputCapsule extends J3MOutputCapsule {
+
+ private final HashMap outCapsules;
+ private String name;
+ private String materialDefinition;
+ private Boolean isTransparent;
+
+ public J3MRootOutputCapsule(J3MExporter exporter) {
+ super(exporter);
+ outCapsules = new HashMap<>();
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ isTransparent = null;
+ name = "";
+ materialDefinition = "";
+ outCapsules.clear();
+
+ }
+
+ public OutputCapsule getCapsule(Savable object) {
+ if (!outCapsules.containsKey(object)) {
+ outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter));
+ }
+
+ return outCapsules.get(object);
+ }
+
+ @Override
+ public void writeToStream(OutputStreamWriter out) throws IOException {
+ out.write("Material " + name + " : " + materialDefinition + " {\n\n");
+ if (isTransparent != null)
+ out.write(" Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n");
+
+ out.write(" MaterialParameters {\n");
+ super.writeToStream(out);
+ out.write(" }\n\n");
+
+ for (J3MOutputCapsule c : outCapsules.values()) {
+ c.writeToStream(out);
+ }
+ out.write("}\n");
+ }
+
+ @Override
+ public void write(String value, String name, String defVal) throws IOException {
+ switch (name) {
+ case "material_def":
+ materialDefinition = value;
+ break;
+ case "name":
+ this.name = value;
+ break;
+ default:
+ throw new UnsupportedOperationException(name + " string material parameter not supported yet");
+ }
+ }
+
+ @Override
+ public void write(boolean value, String name, boolean defVal) throws IOException {
+ if( value == defVal)
+ return;
+
+ switch (name) {
+ case "is_transparent":
+ isTransparent = value;
+ break;
+ default:
+ throw new UnsupportedOperationException(name + " boolean material parameter not supported yet");
+ }
+ }
+
+ @Override
+ public void write(Savable object, String name, Savable defVal) throws IOException {
+ if(object != null && !object.equals(defVal)) {
+ object.write(exporter);
+ }
+ }
+
+}
diff --git a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java
new file mode 100644
index 000000000..be8fc3573
--- /dev/null
+++ b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009-2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material.plugin;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.material.plugin.export.material.J3MExporter;
+import com.jme3.material.plugins.J3MLoader;
+import com.jme3.math.ColorRGBA;
+import com.jme3.system.JmeSystem;
+import com.jme3.texture.Texture;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.Assert.assertTrue;
+
+public class TestMaterialWrite {
+
+ private AssetManager assetManager;
+
+ @Before
+ public void init() {
+ assetManager = JmeSystem.newAssetManager(
+ TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg"));
+
+
+ }
+
+
+ @Test
+ public void testWriteMat() throws Exception {
+
+ Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+
+ mat.setBoolean("UseMaterialColors", true);
+ mat.setColor("Diffuse", ColorRGBA.White);
+ mat.setColor("Ambient", ColorRGBA.DarkGray);
+ mat.setFloat("AlphaDiscardThreshold", 0.5f);
+
+ mat.setFloat("Shininess", 2.5f);
+
+ Texture tex = assetManager.loadTexture("Common/Textures/MissingTexture.png");
+ tex.setMagFilter(Texture.MagFilter.Nearest);
+ tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+ tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);
+ tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat);
+
+ mat.setTexture("DiffuseMap", tex);
+ mat.getAdditionalRenderState().setDepthWrite(false);
+ mat.getAdditionalRenderState().setDepthTest(false);
+ mat.getAdditionalRenderState().setLineWidth(5);
+ mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+
+ final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+ J3MExporter exporter = new J3MExporter();
+ try {
+ exporter.save(mat, stream);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ System.err.println(stream.toString());
+
+ J3MLoader loader = new J3MLoader();
+ AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) {
+ @Override
+ public InputStream openStream() {
+ return new ByteArrayInputStream(stream.toByteArray());
+ }
+ };
+ Material mat2 = (Material)loader.load(info);
+
+ assertTrue(mat.contentEquals(mat2));
+ }
+
+}
diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java
index 4c41e739e..c43c491ec 100644
--- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java
+++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java
@@ -40,6 +40,8 @@ import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
@@ -67,6 +69,14 @@ public class NormalRecalcControl extends AbstractControl {
}
+ @Override
+ public Object jmeClone() {
+ NormalRecalcControl control = (NormalRecalcControl)super.jmeClone();
+ control.setEnabled(true);
+ return control;
+ }
+
+ @Override
public Control cloneForSpatial(Spatial spatial) {
NormalRecalcControl control = new NormalRecalcControl(terrain);
control.setSpatial(spatial);
diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java
index 8ad2425ad..f52b1ca89 100644
--- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java
+++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java
@@ -46,6 +46,8 @@ import com.jme3.scene.control.Control;
import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -298,9 +300,32 @@ public class TerrainLodControl extends AbstractControl {
+
+ @Override
+ public Object jmeClone() {
+ if (spatial instanceof Terrain) {
+ TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras);
+ cloned.setLodCalculator(lodCalculator.clone());
+ cloned.spatial = spatial;
+ return cloned;
+ }
+ return null;
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.lodCalculator = cloner.clone(lodCalculator);
+
+ try {
+ // Not deep clone of the cameras themselves
+ this.cameras = cloner.javaClone(cameras);
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException("Error cloning", e);
+ }
+ }
-
+ @Override
public Control cloneForSpatial(Spatial spatial) {
if (spatial instanceof Terrain) {
List cameraClone = new ArrayList();
diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
index 91967d5c4..5d732d0c0 100644
--- a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
+++ b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
@@ -1,11 +1,11 @@
Material Signpost : Common/MatDefs/Light/Lighting.j3md {
MaterialParameters {
Shininess: 4.0
- DiffuseMap: Models/Sign Post/Sign Post.jpg
- NormalMap: Models/Sign Post/Sign Post_normal.jpg
- SpecularMap: Models/Sign Post/Sign Post_specular.jpg
+ DiffuseMap: "Models/Sign Post/Sign Post.jpg"
+ NormalMap: "Models/Sign Post/Sign Post_normal.jpg"
+ SpecularMap: "Models/Sign Post/Sign Post_specular.jpg"
UseMaterialColors : true
- Ambient : 0.5 0.5 0.5 1.0
+ Ambient : 1.0 1.0 1.0 1.0
Diffuse : 1.0 1.0 1.0 1.0
Specular : 1.0 1.0 1.0 1.0
}
diff --git a/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj
new file mode 100644
index 000000000..fb00257de
--- /dev/null
+++ b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj
@@ -0,0 +1,46 @@
+# Blender v2.76 (sub 0) OBJ File: 'BasicCube.blend'
+# www.blender.org
+o Cube
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 -0.999999
+v 0.999999 1.000000 1.000001
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+vt 0.500000 0.250043
+vt 0.749957 0.250043
+vt 0.749957 0.500000
+vt 0.000087 0.500000
+vt 0.000087 0.250043
+vt 0.250043 0.250043
+vt 0.250043 0.500000
+vt 0.250043 0.000087
+vt 0.500000 0.000087
+vt 0.999913 0.250043
+vt 0.999913 0.500000
+vt 0.500000 0.500000
+vt 0.500000 0.749957
+vt 0.250044 0.749957
+vn 0.577300 -0.577300 0.577300
+vn -0.577300 -0.577300 0.577300
+vn -0.577300 -0.577300 -0.577300
+vn -0.577300 0.577300 -0.577300
+vn -0.577300 0.577300 0.577300
+vn 0.577300 0.577300 0.577300
+vn 0.577300 0.577300 -0.577300
+vn 0.577300 -0.577300 -0.577300
+s 1
+f 2/1/1 3/2/2 4/3/3
+f 8/4/4 7/5/5 6/6/6
+f 5/7/7 6/6/6 2/1/1
+f 6/6/6 7/8/5 3/9/2
+f 3/2/2 7/10/5 8/11/4
+f 1/12/8 4/13/3 8/14/4
+f 1/12/8 2/1/1 4/3/3
+f 5/7/7 8/4/4 6/6/6
+f 1/12/8 5/7/7 2/1/1
+f 2/1/1 6/6/6 3/9/2
+f 4/3/3 3/2/2 8/11/4
+f 5/7/7 1/12/8 8/14/4
diff --git a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o
index 1d56eefa9..d67a2263d 100644
Binary files a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o and b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o differ
diff --git a/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png
new file mode 100644
index 000000000..d9adc6971
Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png differ