Adds the jMonkeyEngine library to the build.

Adds the jMonkeyEngine open source 3D game engine to the build. This
is built as a static library and is only used by the Finsky client.

Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..4b02ec4
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, \
+    engine/src/android \
+    engine/src/core \
+    engine/src/core-plugins \
+    engine/src/ogre)
+
+LOCAL_MODULE := jmonkeyengine
+LOCAL_SDK_VERSION := 9
+LOCAL_MODULE_TAGS := optional
+
+# jMonkeyEngine needs these resources, but they will eventually get
+# stripped out even if they're compiled into the jar. You will need
+# to duplicate them into your project's assets. See the README for
+# more info.
+# LOCAL_JAVA_RESOURCE_DIRS := engine/src/core-data
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..34a719f
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,29033 @@
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/SkeletonLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/AnimData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/matext/package.html <==
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java <==
+/*
+ * 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/MaterialLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/ogre/com/jme3/scene/plugins/ogre/OgreMeshKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/Common/MatDefs/Texture3D/tex3D.frag <==
+==> engine/src/blender/Common/MatDefs/Texture3D/tex3D.j3md <==
+==> engine/src/blender/Common/MatDefs/Texture3D/tex3D.vert <==
+==> engine/src/blender/.DS_Store <==
+==> engine/src/blender/com/jme3/.DS_Store <==
+==> engine/src/blender/com/jme3/asset/BlenderKey.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/jme3/asset/GeneratedTextureKey.java <==
+/**

+ * This key is mostly used to distinguish between textures that are loaded from

+ * the given assets and those being generated automatically. Every generated

+ * texture will have this kind of key attached.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java <==
+/**

+ * Class that holds information about the mesh.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorNoise.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorStucci.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMagic.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorDistnoise.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMusgrave.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorWood.java <==
+/*
+ *
+ * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java <==
+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> DXT1

+ * <li> DXT3

+ * <li> DXT5

+ * Not yet supported (but will be):

+ * <li> DXT1A:

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java <==
+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> RGBA8

+ * <li> ABGR8

+ * <li> BGR8

+ * <li> RGB8

+ * Not yet supported (but will be):

+ * <li> ARGB4444:

+ * <li> RGB10:

+ * <li> RGB111110F:

+ * <li> RGB16:

+ * <li> RGB16F:

+ * <li> RGB16F_to_RGB111110F:

+ * <li> RGB16F_to_RGB9E5:

+ * <li> RGB32F:

+ * <li> RGB565:

+ * <li> RGB5A1:

+ * <li> RGB9E5:

+ * <li> RGBA16:

+ * <li> RGBA16F

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java <==
+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> Luminance8

+ * <li> Luminance8Alpha8

+ * Not yet supported (but will be):

+ * <li> Luminance16:

+ * <li> Luminance16Alpha16:

+ * <li> Luminance16F:

+ * <li> Luminance16FAlpha16F:

+ * <li> Luminance32F:

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java <==
+/**

+ * An interface for texture blending classes (the classes that mix the texture

+ * pixels with the material colors).

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java <==
+/**

+ * An abstract class that contains the basic methods used by the classes that

+ * will derive from it.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */abstract class AbstractTextureBlender implements TextureBlender {

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java <==
+/**

+ * This class creates the texture blending class depending on the texture type.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/NoiseGenerator.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorVoronoi.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorClouds.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java <==
+/**
+ * This class helps with projection calculations.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class UVProjectionGenerator {
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorBlend.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java <==
+/**

+ * The class that stores the pixel values of a texture.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGenerator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMarble.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/textures/noiseconstants.dat <==
+==> engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java <==
+/**

+ * A class that is used in mesh calculations.

+ * @author Marcin Roguski

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java <==
+/**

+ * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize

+ * floating point operations errors.

+ * @author Marcin Roguski

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java <==
+/**

+ * A class that is used to load cameras into the scene.

+ * @author Marcin Roguski

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java <==
+/**

+ * This class helps to compute values from interpolation curves for features

+ * like animation or constraint influence. The curves are 3rd degree bezier

+ * curves.

+ * 

+ * @author Marcin Roguski

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java <==
+/**

+ * This class is used to calculate bezier curves value for the given frames. The

+ * Ipo (interpolation object) consists of several b-spline curves (connected 3rd

+ * degree bezier curves) of a different type.

+ * 

+ * @author Marcin Roguski

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java <==
+/**

+ * This class holds the basic data that describes a bone.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java <==
+/**

+ * The purpose of this class is to imitate bone's movement when calculating inverse kinematics.

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java <==
+/**
+ * The blender object's custom properties.
+ * This class is valid for all versions of blender.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java <==
+/**

+ * This modifier allows to array modifier to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class MirrorModifier extends Modifier {

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java <==
+/**

+ * This modifier allows to array modifier to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ArrayModifier extends Modifier {

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java <==
+/**

+ * This modifier allows to add animation to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ObjectAnimationModifier extends Modifier {

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java <==
+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java <==
+/**

+ * This class represents an object's modifier. The modifier object can be varied

+ * and the user needs to know what is the type of it for the specified type

+ * name. For example "ArmatureModifierData" type specified in blender is

+ * represented by AnimData object from jMonkeyEngine.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java <==
+/**

+ * This modifier allows to add particles to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ParticlesModifier extends Modifier {

+==> engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java <==
+==> engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java <==
+/**
+ * An interface used in calculating alpha mask during particles' texture calculations.
+ * @author Marcin Roguski (Kaelthas)
+ */
+/*package*/ interface IAlphaMask {
+==> engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java <==
+/**
+ * This class holds the data about the material.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java <==
+/**

+ * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to

+ * another structure.

+ * @author Marcin Roguski

+ */

+/*package*/

+==> engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+ * This exception is thrown when blend file data is somehow invalid.

+==> engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintChildOf.java <==
+/**

+ * This class represents 'ChildOf' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintChildOf extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java <==
+/**

+ * The implementation of a constraint.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintTransform.java <==
+/**

+ * This class represents 'Transform' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintTransform extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintClampTo.java <==
+/**

+ * This class represents 'Clamp to' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintClampTo extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRigidBodyJoint.java <==
+/**

+ * This class represents 'Rigid body joint' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintRigidBodyJoint extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/Feature.java <==
+/**

+ * This class represents either owner or target of the constraint. It has the

+ * common methods that take the evalueation space of the feature.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class Feature {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintMinMax.java <==
+/**

+ * This class represents 'Min max' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintMinMax extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintFollowPath.java <==
+/**

+ * This class represents 'Follow path' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintFollowPath extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintNull.java <==
+/**

+ * This class represents 'Null' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintNull extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDistLimit.java <==
+/**

+ * This class represents 'Dist limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintDistLimit extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPython.java <==
+/**

+ * This class represents 'Python' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintPython extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLimit.java <==
+/**

+ * This class represents 'Loc limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLocLimit extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintAction.java <==
+/**

+ * This class represents 'Action' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintAction extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/BlenderTrack.java <==
+/**

+ * This class holds either the bone track or spatial track. Is made to improve

+ * code readability.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */final class BlenderTrack implements Track {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLockTrack.java <==
+/**

+ * This class represents 'Action' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLockTrack extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLike.java <==
+/**

+ * This class represents 'Size like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSizeLike extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPivot.java <==
+/**

+ * The pivot constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintPivot extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLimit.java <==
+/**

+ * This class represents 'Size limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSizeLimit extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintShrinkWrap.java <==
+/**

+ * This class represents 'Shrink wrap' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintShrinkWrap extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java <==
+/**

+ * This class should be used for constraint calculations.

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDampTrack.java <==
+/**

+ * The damp track constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintDampTrack extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSplineInverseKinematic.java <==
+/**

+ * The spline inverse kinematic constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSplineInverseKinematic extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLike.java <==
+/**

+ * This class represents 'Loc like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLocLike extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintStretchTo.java <==
+/**

+ * This class represents 'Stretch to' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintStretchTo extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintInverseKinematics.java <==
+/**

+ * This class represents 'Inverse kinematics' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintInverseKinematics extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLike.java <==
+/**

+ * This class represents 'Rot like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintRotLike extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLimit.java <==
+/**

+ * This class represents 'Rot limit' constraint type in blender.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ConstraintRotLimit extends Constraint {

+==> engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java <==
+/*

+ * 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.

+ */

+==> engine/src/blender/com/.DS_Store <==
+==> engine/src/jbullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/objects/PhysicsGhostObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/objects/VehicleWheel.java <==
+/*
+ * 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/objects/PhysicsCharacter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/objects/PhysicsVehicle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/objects/PhysicsRigidBody.java <==
+/*
+ * 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/PhysicsSpace.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/PhysicsRayTestResult.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java <==
+/*
+ * 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/CollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java <==
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A simple point, line, triangle or quad collisionShape based on one to four points-
+ * @author normenhansen
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster
+ * than using a regular mesh.
+ * There are a couple tricks though:
+ *	-No rotation or translation is supported.
+ *	-The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being
+ *	equal on either side. If not, the whole collision box is shifted vertically and things don't collide
+ *	as they should.
+ * 
+ * @author Brent Owens
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/util/Converter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/Point2PointJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/SixDofJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/ConeJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/HingeJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/SliderJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ *
+==> engine/src/jbullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jbullet/com/jme3/bullet/joints/PhysicsJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/system/Natives.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/system/JmeDesktopSystem.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/system/JmeCanvasContext.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/system/awt/AwtPanel.java <==
+==> engine/src/desktop/com/jme3/system/awt/PaintMode.java <==
+==> engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java <==
+==> engine/src/desktop/com/jme3/texture/plugins/AWTLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/util/Screenshots.java <==
+==> engine/src/desktop/com/jme3/app/Monkey.png <==
+==> engine/src/desktop/com/jme3/app/AppletHarness.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/app/SettingsDialog.java <==
+/*
+ * 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.
+ */
+==> engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java <==
+/**
+ * Released under BSD License
+ * @author monceaux, normenhansen
+ */
+==> engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java <==
+/**
+ * A Video recording AppState that records the screen output into an AVI file with
+ * M-JPEG content. The file should be playable on any OS in any video player.<br/>
+ * The video recording starts when the state is attached and stops when it is detached
+ * or the application is quit. You can set the fileName of the file to be written when the
+ * state is detached, else the old file will be overwritten. If you specify no file
+ * the AppState will attempt to write a file into the user home directory, made unique
+ * by a timestamp.
+ * @author normenhansen, Robert McIntyre
+ */
+==> engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java <==
+==> engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/jme3tools/converters/MipMapGenerator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/jme3tools/converters/ImageToAwt.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/desktop/jme3tools/navigation/RLSailing.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A utility class to package up a rhumb line sailing
+ * 
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/MapModel3D.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A representation of the actual map in terms of lat/long and x,y,z co-ordinates.
+ * The Map class contains various helper methods such as methods for determining
+ * the world unit positions for lat/long coordinates and vice versa. This map projection
+ * does not handle screen/pixel coordinates.
+ *
+ * @author Benjamin Jakobus (thanks to Cormac Gebruers)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/NavCalculator.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A utlity class for performing position calculations
+ *
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/InvalidPositionException.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/desktop/jme3tools/navigation/Coordinate.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Coordinate class. Used to store a coordinate in [DD]D MM.M format.
+ *
+ * @author Benjamin Jakobus (based on JMarine by Benjamin Jakobus and Cormac Gebruers)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/NumUtil.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Provides various helper methods for number conversions (such as degree to radian
+ * conversion, decimal degree to radians etc)
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/MapModel2D.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A representation of the actual map in terms of lat/long and x,y co-ordinates.
+ * The Map class contains various helper methods such as methods for determining
+ * the pixel positions for lat/long co-ordinates and vice versa.
+ *
+ * @author Cormac Gebruers
+ * @author Benjamin Jakobus
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/Position.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * This class represents the position of an entity in the world.
+ * 
+ * @author Benjamin Jakobus (based on JMarine by Cormac Gebruers and Benjamin Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/GCSailing.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A utility class to package up a great circle sailing.
+ * 
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ *
+ * @version 1.0
+ * @since 1.0
+ */
+==> engine/src/desktop/jme3tools/navigation/StringUtil.java <==
+/*

+ * To change this template, choose Tools | Templates

+ * and open the template in the editor.

+ */

+/**

+ * A collection of String utilities.

+ *

+ * @author Benjamin Jakobus

+ * @version 1.0

+ */

+==> engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.vert <==
+==> engine/src/terrain/Common/MatDefs/Terrain/Terrain.j3md <==
+==> engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.frag <==
+==> engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag <==
+==> engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.vert <==
+==> engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.j3md <==
+==> engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.j3md <==
+==> engine/src/terrain/Common/MatDefs/Terrain/Terrain.frag <==
+==> engine/src/terrain/Common/MatDefs/Terrain/Terrain.vert <==
+==> engine/src/terrain/com/jme3/terrain/heightmap/HeightMapGrid.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Anthyon
+ */
+/**
+ * @Deprecated in favor of TerrainGridTileLoader
+ */
+==> engine/src/terrain/com/jme3/terrain/heightmap/ImageHeightmap.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A heightmap that is built off an image.
+ * If you want to be able to supply different Image types to 
+ * ImageBaseHeightMapGrid, you need to implement this interface,
+ * and have that class extend Abstract heightmap.
+ * 
+ * @author bowens
+ * @deprecated
+ */
+==> engine/src/terrain/com/jme3/terrain/heightmap/CombinerHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/FaultHeightMap.java <==
+/*

+ * 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/Namer.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Anthyon
+ */
+==> engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Loads Terrain grid tiles with image heightmaps.
+ * By default it expects a 16-bit grayscale image as the heightmap, but
+ * you can also call setImageType(BufferedImage.TYPE_) to set it to be a different
+ * image type. If you do this, you must also set a custom ImageHeightmap that will
+ * understand and be able to parse the image. By default if you pass in an image of type
+ * BufferedImage.TYPE_3BYTE_BGR, it will use the ImageBasedHeightMap for you.
+ * 
+ * @author Anthyon, Brent Owens
+ */
+/**
+ * @Deprecated in favor of ImageTileLoader
+ */
+==> engine/src/terrain/com/jme3/terrain/heightmap/AbstractHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/RawHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.java <==
+/*

+ * 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/FluidSimHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/heightmap/HeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/terrain/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/GeoMap.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/Terrain.java <==
+/*

+ * 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/ProgressMonitor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Monitor the progress of an expensive terrain operation.
+==> engine/src/terrain/com/jme3/terrain/noise/filter/ThermalErodeFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/IterativeFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/OptimizedErode.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/AbstractFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/PerturbFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/filter/SmoothFilter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/Color.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+/**

+ * Helper class for working with colors and gradients

+ * 

+ * @author Anthyon

+==> engine/src/terrain/com/jme3/terrain/noise/Filter.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/Basis.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/ShaderUtils.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/modulator/CatRom2.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/modulator/NoiseModulator.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/modulator/Modulator.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/basis/NoiseAggregator.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+/**

+ * A simple aggregator basis. Takes two basis functions and a rate and return

+==> engine/src/terrain/com/jme3/terrain/noise/basis/Noise.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/basis/ImprovedNoise.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+/**

+==> engine/src/terrain/com/jme3/terrain/noise/basis/FilteredBasis.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/noise/fractal/Fractal.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+/**

+ * Interface for a general fractal basis.

+==> engine/src/terrain/com/jme3/terrain/noise/fractal/FractalSum.java <==
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/NormalRecalcControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java <==
+/**
+ * Computes the entropy value δ (delta) for a given terrain block and
+ * LOD level.
+ * See the geomipmapping paper section
+ * "2.3.1 Choosing the appropriate GeoMipMap level"
+ *
+ * @author Kirill Vainer
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java <==
+/*

+ * 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java <==
+/**

+ * An LRU cache, based on <code>LinkedHashMap</code>.

+ * 

+ * <p>

+ * This cache has a fixed maximum number of elements (<code>cacheSize</code>).

+ * If the cache is full and another entry is added, the LRU (least recently

+ * used) entry is dropped.

+ * 

+ * <p>

+ * This class is thread-safe. All methods of this class are synchronized.

+ * 

+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPickData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPicker.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java <==
+/*

+ * 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.

+ */

+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglCanvas.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglSmoothingTimer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglDisplay.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglContext.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglTimer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/lwjgl/com/jme3/renderer/lwjgl/TextureUtil.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java <==
+==> engine/src/lwjgl/com/jme3/input/lwjgl/LwjglKeyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/input/lwjgl/LwjglMouseInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/lwjgl/com/jme3/input/lwjgl/JInputJoyInput.java <==
+==> engine/src/networking/com/jme3/network/HostedConnection.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/package.html <==
+==> engine/src/networking/com/jme3/network/Network.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/ErrorListener.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/Endpoint.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/package.html <==
+==> engine/src/networking/com/jme3/network/kernel/Kernel.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/udp/UdpEndpoint.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/udp/UdpKernel.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/udp/UdpConnector.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/AbstractKernel.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/EndpointEvent.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/ConnectorException.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/KernelException.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/tcp/NioEndpoint.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/tcp/SocketConnector.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/tcp/SelectorKernel.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/Connector.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/NamedThreadFactory.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/kernel/Envelope.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+/**
+==> engine/src/networking/com/jme3/network/rmi/RmiSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/LocalObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/ObjectStore.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/MethodDef.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/RemoteObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/rmi/ObjectDef.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/Message.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/Filter.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/MessageConnection.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/ArraySerializer.java <==
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine, Java Game Networking
+ * 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/ByteSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/DoubleSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/Vector3Serializer.java <==
+/**
+ * @author Kirill Vainer
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/SerializableSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/BooleanSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/SavableSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/StringSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/LongSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/CharSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/EnumSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/IntSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/MapSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/GZIPSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/FloatSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/CollectionSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/DateSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/ShortSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/ZIPSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/serializers/FieldSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine, Java Game Networking
+ * 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/SerializerRegistration.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/networking/com/jme3/network/serializing/SerializerException.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/Serializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/serializing/Serializable.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/Client.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/NetworkClient.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/MessageListener.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/package.html <==
+==> engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/TcpConnectorFactory.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/KernelFactory.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/DefaultServer.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/MessageProtocol.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/ConnectorFactory.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/KernelAdapter.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/DefaultClient.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/ConnectorAdapter.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/base/NioKernelFactory.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/message/DisconnectMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/message/GZIPCompressedMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/message/ZIPCompressedMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/message/ChannelInfoMessage.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/message/ClientRegistrationMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/message/CompressedMessage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/networking/com/jme3/network/Server.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/AbstractMessage.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/ConnectionListener.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/Filters.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/networking/com/jme3/network/ClientStateListener.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/core/checkers/quals/Dependent.java <==
+/**
+ * Refines the qualified type of the annotated field or variable based on the
+ * qualified type of the receiver.  The annotation declares a relationship
+ * between multiple type qualifier hierarchies.
+ *
+ * <p><b>Example:</b>
+ * Consider a field, {@code lock}, that is only initialized if the
+ * enclosing object (the receiver), is marked as {@code ThreadSafe}.
+ * Such a field can be declared as:
+ *
+ * <pre><code>
+ *   private @Nullable @Dependent(result=NonNull.class, when=ThreadSafe.class)
+ *     Lock lock;
+ * </code></pre>
+ */
+==> engine/src/core/checkers/quals/SubtypeOf.java <==
+/**
+ * A meta-annotation to specify all the qualifiers that the given qualifier
+ * is a subtype of.  This provides a declarative way to specify the type
+ * qualifier hierarchy.  (Alternatively, the hierarchy can be defined
+ * procedurally by subclassing {@link checkers.types.QualifierHierarchy} or
+ * {@link checkers.types.TypeHierarchy}.)
+ *
+ * <p>
+ * Example:
+ * <pre> @SubtypeOf( { Nullable.class } )
+ * public @interface NonNull { }
+ * </pre>
+ *
+ * <p>
+ *
+ * If a qualified type is a subtype of the same type without any qualifier,
+ * then use <code>Unqualified.class</code> in place of a type qualifier
+ * class.  For example, to express that <code>@Encrypted <em>C</em></code>
+ * is a subtype of <code><em>C</em></code> (for every class
+ * <code><em>C</em></code>), and likewise for <code>@Interned</code>, write:
+ *
+ * <pre> @SubtypeOf(Unqualified.class)
+ * public @interface Encrypted { }
+ *
+ * &#64;SubtypeOf(Unqualified.class)
+ * public @interface Interned { }
+ * </pre>
+ *
+ * <p>
+ *
+ * For the root type qualifier in the qualifier hierarchy (i.e., the
+==> engine/src/core/checkers/quals/DefaultLocation.java <==
+/**
+ * Specifies the locations to which a {@link DefaultQualifier} annotation applies.
+ *
+ * @see DefaultQualifier
+ */
+==> engine/src/core/checkers/quals/DefaultQualifier.java <==
+/**
+ * Applied to a declaration of a package, type, method, variable, etc.,
+ * specifies that the given annotation should be the default.  The default is
+ * applied to all types within the declaration for which no other
+ * annotation is explicitly written.
+ * If multiple DefaultQualifier annotations are in scope, the innermost one
+ * takes precedence.
+ * DefaultQualifier takes precedence over {@link DefaultQualifierInHierarchy}.
+ * <p>
+ *
+ * If you wish to write multiple @DefaultQualifier annotations (for
+ * unrelated type systems, or with different {@code locations} fields) at
+ * the same location, use {@link DefaultQualifiers}.
+ *
+ * @see DefaultLocation
+ */
+==> engine/src/core/checkers/quals/DefaultQualifierInHierarchy.java <==
+/**
+ * Indicates that the annotated qualifier is the default qualifier in the
+ * qualifier hierarchy:  it applies if the programmer writes no explicit
+ * qualifier.
+ * <p>
+ *
+ * The {@link DefaultQualifier} annotation, which targets Java code elements,
+ * takes precedence over {@code DefaultQualifierInHierarchy}.
+ * <p>
+ *
+ * Each type qualifier hierarchy may have at most one qualifier marked as
+ * {@code DefaultQualifierInHierarchy}.
+ *
+ * @see checkers.quals.DefaultQualifier
+ */
+==> engine/src/core/checkers/quals/Unused.java <==
+/**
+ * Declares that the field may not be accessed if the receiver is of the
+ * specified qualifier type (or any supertype).
+ *
+ * This property is verified by the checker that type-checks the {@code
+ * when} element value qualifier.
+ *
+ * <p><b>Example</b>
+ * Consider a class, {@code Table}, with a locking field, {@code lock}.  The
+ * lock is used when a {@code Table} instance is shared across threads.  When
+ * running in a local thread, the {@code lock} field ought not to be used.
+ *
+ * You can declare this behavior in the following way:
+ *
+ * <pre><code>
+ * class Table {
+ *   private @Unused(when=LocalToThread.class) final Lock lock;
+ *   ...
+ * }
+ * </code></pre>
+ *
+ * The checker for {@code @LocalToThread} would issue an error for the following code:
+ *
+ * <pre>  @LocalToThread Table table = ...;
+ *   ... table.lock ...;
+ * </pre>
+ *
+ */
+==> engine/src/core/checkers/quals/package-info.java <==
+/**
+ * Contains the basic annotations to be used by all type systems
+ * and meta-annotations to qualify annotations (qualifiers).
+ *
+ * They may serve as documentation for the type qualifiers, and aid the
+ * Checker Framework to infer the relations between the type qualifiers.
+ *
+ * @checker.framework.manual #writing-a-checker Writing a checker
+ */
+==> engine/src/core/checkers/quals/DefaultQualifiers.java <==
+/**
+ * Specifies the annotations to be included in a type without having to provide
+ * them explicitly.
+ *
+ * This annotation permits specifying multiple default qualifiers for more
+ * than one type system.  It is necessary because Java forbids multiple
+ * annotations of the same name at a single location.
+ *
+ * Example:
+ * <!-- &nbsp; is a hack that prevents @ from being the first charater on the line, which confuses Javadoc -->
+ * <code><pre>
+ * &nbsp; @DefaultQualifiers({
+ * &nbsp;     @DefaultQualifier("NonNull"),
+ * &nbsp;     @DefaultQualifier(value = "Interned", locations = ALL_EXCEPT_LOCALS),
+ * &nbsp;     @DefaultQualifier("Tainted")
+ * &nbsp; })
+ * </pre></code>
+ *
+ * @see DefaultQualifier
+ */
+==> engine/src/core/checkers/quals/TypeQualifier.java <==
+/**
+ * A meta-annotation indicating that the annotated annotation is a type
+ * qualifier.
+ *
+ * Examples of such qualifiers: {@code @ReadOnly}, {@code @NonNull}
+ */
+==> engine/src/core/checkers/quals/Unqualified.java <==
+/**
+ * A special annotation intended solely for representing an unqualified type in
+ * the qualifier hierarchy, as an argument to {@link SubtypeOf#value()},
+ * in the type qualifiers declarations.
+ *
+ * <p>
+ * Programmers cannot write this in source code.
+ */
+==> engine/src/core/com/jme3/animation/LoopMode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/animation/SkeletonControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * The Skeleton control deforms a model according to a skeleton, 
+ * It handles the computation of the deformation matrices and performs 
+ * the transformations on the mesh
+ * 
+ * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ */
+==> engine/src/core/com/jme3/animation/package.html <==
+==> engine/src/core/com/jme3/animation/PoseTrack.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/animation/BoneAnimation.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
+==> engine/src/core/com/jme3/animation/Pose.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/animation/AnimControl.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/animation/Bone.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/animation/CompactArray.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/animation/CompactVector3Array.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/animation/SpatialTrack.java <==
+/**

+ * This class represents the track for spatial animation.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+==> engine/src/core/com/jme3/animation/Track.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/animation/Animation.java <==
+/*
+ * Copyright (c) 2009-2011 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.
+ */
+==> engine/src/core/com/jme3/animation/BoneTrack.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/animation/Skeleton.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/animation/AnimationFactory.java <==
+/*
+ * Copyright (c) 2009-2011 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.
+ */
+==> engine/src/core/com/jme3/animation/CompactQuaternionArray.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/animation/SpatialAnimation.java <==
+/**

+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)

+ */

+==> engine/src/core/com/jme3/animation/AnimEventListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/animation/AnimChannel.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/JmeSystemDelegate.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/SystemListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/system/JmeContext.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/NullContext.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/JmeVersion.java <==
+==> engine/src/core/com/jme3/system/JmeSystem.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/NanoTimer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/system/NullRenderer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/system/Timer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * <code>Timer</code> is the base class for a high resolution timer. It is
+==> engine/src/core/com/jme3/system/Platform.java <==
+==> engine/src/core/com/jme3/system/AppSettings.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/system/Annotations.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioParam.java <==
+==> engine/src/core/com/jme3/audio/SeekableStream.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/core/com/jme3/audio/LowPassFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioStream.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioRenderer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/audio/ListenerParam.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/Filter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioContext.java <==
+/*
+ * Copyright (c) 2009-2011 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.
+ */
+/**
+==> engine/src/core/com/jme3/audio/Environment.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/Listener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/audio/AudioNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shadow/PssmShadowUtil.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shadow/PssmShadowRenderer.java <==
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * 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.
+ */
+==> engine/src/core/com/jme3/shadow/ShadowCamera.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/shadow/BasicShadowRenderer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/shadow/ShadowUtil.java <==
+/*

+ * 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.

+ */

+==> engine/src/core/com/jme3/renderer/Renderer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/RendererException.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/renderer/package.html <==
+==> engine/src/core/com/jme3/renderer/queue/GuiComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/queue/NullComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/queue/GeometryComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/queue/TransparentComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/renderer/queue/GeometryList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/queue/RenderQueue.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/RenderContext.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/GL1Renderer.java <==
+/**
+ * Renderer sub-interface that is used for non-shader based renderers.
+ * <p>
+ * The <code>GL1Renderer</code> provides a single call, 
+ * {@link #setFixedFuncBinding(com.jme3.material.FixedFuncBinding, java.lang.Object) }
+ * which allows to set fixed functionality state.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/renderer/RenderManager.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/renderer/ViewPort.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/renderer/Statistics.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/IDList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/Camera.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/renderer/Caps.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/math/Line.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/AbstractTriangle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/LineSegment.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Spline.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/core/com/jme3/math/package.html <==
+==> engine/src/core/com/jme3/math/Matrix3f.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/Eigen3f.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/CurveAndSurfaceMath.java <==
+/**

+ * This class offers methods to help with curves and surfaces calculations.

+ * @author Marcin Roguski (Kealthas)

+ */

+==> engine/src/core/com/jme3/math/Transform.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Quaternion.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Triangle.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Matrix4f.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/FastMath.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/Vector4f.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/Plane.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Rectangle.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/ColorRGBA.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/core/com/jme3/math/Ray.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/Vector2f.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/math/Ring.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/math/Vector3f.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/collision/UnsupportedCollisionException.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/collision/CollisionResult.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/bih/BIHNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/bih/BIHTree.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/bih/BIHTriangle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/MotionAllowedListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/Collidable.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/collision/SweepSphere.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/collision/CollisionResults.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/font/LineWrapMode.java <==
+/**
+ * Line-wrap type for BitmapText
+ * @author YongHoon
+ */
+==> engine/src/core/com/jme3/font/BitmapCharacter.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/font/Letters.java <==
+/**

+ * Manage and align LetterQuads

+ * @author YongHoon

+ */

+==> engine/src/core/com/jme3/font/Kerning.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/font/LetterQuad.java <==
+/**

+ * LetterQuad contains the position, color, uv texture information for a character in text.

+ * @author YongHoon

+ */

+==> engine/src/core/com/jme3/font/StringBlock.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/font/BitmapText.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/font/BitmapCharacterSet.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/font/Rectangle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/font/BitmapFont.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/font/ColorTags.java <==
+/**

+ * Contains the color information tagged in a text string

+ * Format: \#rgb#

+ *         \#rgba#

+ *         \#rrggbb#

+ *         \#rrggbbaa#

+ * @author YongHoon

+ */

+==> engine/src/core/com/jme3/font/BitmapTextPage.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/texture/Image.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/texture/Texture.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/texture/FrameBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/texture/TextureArray.java <==
+/**
+ * This class implements a Texture array
+ * warning, this feature is only supported on opengl 3.0 version.
+ * To check if a hardware supports TextureArray check : 
+ * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+ * @author phate666
+ */
+==> engine/src/core/com/jme3/texture/Texture3D.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/texture/Texture2D.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/texture/TextureCubeMap.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/BufferUtils.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/SkyFactory.java <==
+/**
+ * <code>SkyFactory</code> is used to create jME {@link Spatial}s that can
+ * be attached to the scene to display a sky image in the background.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/util/IntMap.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/NativeObjectManager.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/TangentBinormalGenerator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/SafeArrayList.java <==
+/*
+ * Copyright (c) 2009-2011 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.
+ */
+==> engine/src/core/com/jme3/util/JmeFormatter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/PlaceholderAssets.java <==
+==> engine/src/core/com/jme3/util/xml/SAXUtil.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/NativeObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/util/LittleEndien.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/SortUtil.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/ListMap.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java <==
+==> engine/src/core/com/jme3/util/blockparser/Statement.java <==
+==> engine/src/core/com/jme3/util/TempVars.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/material/Technique.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/package.html <==
+==> engine/src/core/com/jme3/material/TechniqueDef.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/Material.java <==
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * 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.
+ */
+==> engine/src/core/com/jme3/material/MaterialList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/FixedFuncBinding.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/material/RenderState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/MatParam.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/MaterialDef.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/material/MatParamTexture.java <==
+==> engine/src/core/com/jme3/export/ReadListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/JmeImporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/FormatVersion.java <==
+/**
+ * Specifies the version of the format for jME3 object (j3o) files.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/export/SavableClassUtil.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/OutputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/JmeExporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/InputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/export/Savable.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/package.html <==
+==> engine/src/core/com/jme3/asset/Asset.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/asset/AssetEventListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/asset/AssetCache.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/AssetLoadException.java <==
+/**
+ * <code>AssetLoadException</code> is thrown when the {@link AssetManager}
+ * is able to find the requested asset, but there was a problem while loading
+ * it.
+ *
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/asset/DesktopAssetManager.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/asset/AssetNotFoundException.java <==
+/**
+ * <code>AssetNotFoundException</code> is thrown when the {@link AssetManager}
+ * is unable to locate the requested asset using any of the registered
+ * {@link AssetLocator}s.
+ *
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/asset/AssetManager.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/AssetConfig.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/AssetLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/asset/TextureKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/AssetLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/ThreadingManager.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/AssetInfo.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/ModelKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/Desktop.cfg <==
+==> engine/src/core/com/jme3/asset/MaterialKey.java <==
+/**
+ * Used for loading {@link Material materials} only (not material definitions).
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/asset/AssetKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/asset/ImplHandler.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/app/package.html <==
+==> engine/src/core/com/jme3/app/StatsView.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/app/state/package.html <==
+==> engine/src/core/com/jme3/app/state/AppState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/app/state/AbstractAppState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/app/state/AppStateManager.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/app/SimpleApplication.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/app/StatsAppState.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/app/FlyCamAppState.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/app/Application.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/app/DebugKeysAppState.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/app/AppTask.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/Particle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/package.html <==
+==> engine/src/core/com/jme3/effect/ParticleComparator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/ParticleMesh.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/ParticlePointMesh.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java <==
+/**
+ * This influencer calculates initial velocity with the use of the emitter's shape.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java <==
+/**
+ * An interface that defines the methods to affect initial velocity of the particles.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java <==
+/**
+ * This influencer does not influence particle at all.
+ * It makes particles not to move.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java <==
+/**
+ * This emitter influences the particles so that they move all in the same direction.
+ * The direction may vary a little if the velocity variation is non zero.
+ * This influencer is default for the particle emitter.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java <==
+/**
+ * This emiter shape emits the particles from the given shape's vertices
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java <==
+/**
+ * This emiter shape emits the particles from the given shape's faces.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java <==
+/**
+ * This emiter shape emits the particles from the given shape's interior constrained by its convex hull
+ * (a geometry that tightly wraps the mesh). So in case of multiple meshes some vertices may appear
+ * in a space between them.
+ * @author Marcin Roguski (Kaelthas)
+ */
+==> engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/effect/ParticleEmitter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/effect/ParticleTriMesh.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/UniformBinding.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/Attribute.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/shader/Shader.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/shader/Uniform.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/DefineList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/ShaderUtils.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/ShaderKey.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/ShaderVariable.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/shader/VarType.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java <==
+/**
+ * <code>WrappedIndexBuffer</code> converts vertex indices from a non list based
+ * mesh mode such as {@link Mode#TriangleStrip} or {@link Mode#LineLoop}
+ * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}.
+ * As it is often more convenient to read vertex data in list format
+ * than in a non-list format, using this class is recommended to avoid
+ * convoluting classes used to process mesh data from an external source.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/scene/mesh/package.html <==
+==> engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/mesh/IndexBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java <==
+/**
+ * IndexBuffer implementation that generates vertex indices sequentially
+ * based on a specific Mesh {@link Mode}.
+ * The generated indices are as if the mesh is in the given mode
+ * but contains no index buffer, thus this implementation will
+ * return the indices if the index buffer was there and contained sequential
+ * triangles.
+ * Example:
+ * <ul>
+ * <li>{@link Mode#Triangles}: 0, 1, 2 | 3, 4, 5 | 6, 7, 8 | ...</li>
+ * <li>{@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...</li>
+ * <li>{@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...</li>
+ * </ul>
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/scene/package.html <==
+==> engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java <==
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. The adapter version of the interface simply separates 
+ * between the {@link Geometry geometries} and the {@link Node nodes} by
+ * supplying visit methods that take them.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+==> engine/src/core/com/jme3/scene/Mesh.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/scene/SimpleBatchNode.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * 
+ * SimpleBatchNode  comes with some restrictions, but can yield better performances.
+ * Geometries to be batched has to be attached directly to the BatchNode
+ * You can't attach a Node to a SimpleBatchNode
+ * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
+ * @see BatchNode
+ * @author Nehon
+ */
+==> engine/src/core/com/jme3/scene/BatchNode.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/scene/UserData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/CameraNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/AssetLinkNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/VertexBuffer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/Geometry.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/WireBox.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/WireSphere.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/scene/debug/Arrow.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/SkeletonPoints.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/SkeletonWire.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/Grid.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/debug/WireFrustum.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Line.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Sphere.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/scene/shape/Cylinder.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/scene/shape/Curve.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/PQTorus.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Box.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Quad.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Torus.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/StripBox.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Dome.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/shape/Surface.java <==
+/**

+ * This class represents a surface described by knots, weights and control points.

+ * Currently the following types are supported:

+ * a) NURBS

+ * @author Marcin Roguski (Kealthas)

+ */

+==> engine/src/core/com/jme3/scene/shape/AbstractBox.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/Spatial.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/Node.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/LightNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/SceneGraphVisitor.java <==
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. 
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+==> engine/src/core/com/jme3/scene/CollisionData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/package.html <==
+==> engine/src/core/com/jme3/scene/control/LodControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/AbstractControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/BillboardControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/LightControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/AreaUtils.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/CameraControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/UpdateControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/scene/control/Control.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/KeyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/TouchInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/Joystick.java <==
+/**
+ * A joystick represents a single joystick that is installed in the system.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/controls/Trigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/controls/AnalogListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/controls/package.html <==
+==> engine/src/core/com/jme3/input/controls/ActionListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/controls/KeyTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/controls/TouchTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/controls/InputListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/controls/TouchListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/package.html <==
+==> engine/src/core/com/jme3/input/event/package.html <==
+==> engine/src/core/com/jme3/input/event/MouseButtonEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/event/JoyButtonEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/event/JoyAxisEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/event/KeyInputEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/event/TouchEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * <code>TouchEvent</code> represents a single event from multi-touch input devices
+==> engine/src/core/com/jme3/input/event/InputEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/event/MouseMotionEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/KeyNames.java <==
+==> engine/src/core/com/jme3/input/Input.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/RawInputListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/dummy/package.html <==
+==> engine/src/core/com/jme3/input/dummy/DummyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/dummy/DummyKeyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/dummy/DummyMouseInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/input/InputManager.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/input/MouseInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/FlyByCamera.java <==
+/*

+ * 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.

+ */

+==> engine/src/core/com/jme3/input/JoyInput.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/input/ChaseCamera.java <==
+/*

+ * 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.

+ */

+==> engine/src/core/com/jme3/bounding/OrientedBoundingBox.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/core/com/jme3/bounding/Intersection.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/bounding/BoundingSphere.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/bounding/BoundingVolume.java <==
+/*

+ * 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.

+ */

+==> engine/src/core/com/jme3/bounding/BoundingBox.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/cinematic/PlayState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/core/com/jme3/cinematic/MotionPathListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/MotionPath.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/KeyFrame.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/Cinematic.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/events/RotationTrack.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+==> engine/src/core/com/jme3/cinematic/events/CinematicEvent.java <==
+/*
+ * 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.
+ */
+==> engine/src/core/com/jme3/cinematic/events/SoundTrack.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/core/com/jme3/cinematic/events/ScaleTrack.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+==> engine/src/core/com/jme3/cinematic/events/PositionTrack.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+==> engine/src/core/com/jme3/cinematic/events/MotionTrack.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/events/AnimationTrack.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/cinematic/TimeLine.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/post/package.html <==
+==> engine/src/core/com/jme3/post/Filter.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/post/SceneProcessor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/post/PreDepthProcessor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/post/FilterPostProcessor.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/post/HDRRenderer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/light/SpotLight.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/light/package.html <==
+==> engine/src/core/com/jme3/light/AmbientLight.java <==
+/**
+ * An ambient light adds a constant color to the scene.
+ * <p>
+ * Ambient lights are unaffected by the surface normal, and are constant
+ * regardless of the model's location. The material's ambient color is
+ * multiplied by the ambient light color to get the final ambient color of
+ * an object.
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core/com/jme3/light/PointLight.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core/com/jme3/light/Light.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/light/LightList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/light/DirectionalLight.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core/com/jme3/ui/Picture.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_SixDofJoint */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    getRotationalLimitMotor
+ * Signature: (JI)J
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    getTranslationalLimitMotor
+ * Signature: (J)J
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    setLinearUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+==> engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_PhysicsSpace */
+/* Inaccessible static: pQueueTL */
+/* Inaccessible static: physicsSpaceTL */
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    createPhysicsSpace
+ * Signature: (FFFFFFIZ)J
+ */
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    stepSimulation
+ * Signature: (JFIF)V
+ */
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+==> engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_motors_RotationalLimitMotor */
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getLoLimit
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setLoLimit
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getHiLimit
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+==> engine/src/bullet-native/jmeClasses.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp <==
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral1
+ * Signature: (J)F
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_util_DebugShapeFactory */
+/*
+ * Class:     com_jme3_bullet_util_DebugShapeFactory
+ * Method:    getVertices
+ * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_PhysicsRigidBody */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_PhysicsCharacter */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_PlaneCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_PlaneCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;F)J
+ */
+==> engine/src/bullet-native/jmePhysicsSpace.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_GImpactCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+ * Method:    createShape
+ * Signature: (J)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_HullCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_HullCollisionShape
+ * Method:    createShape
+ * Signature: (Ljava/nio/ByteBuffer;)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_CapsuleCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CapsuleCollisionShape
+ * Method:    createShape
+ * Signature: (IFF)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_PhysicsGhostObject */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_SphereCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SphereCollisionShape
+ * Method:    createShape
+ * Signature: (F)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_SimplexCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+==> engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.cpp <==
+/**
+ * Author: Normen Hansen
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    enableString
+ * Signature: (JIZ)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setStiffness
+ * Signature: (JIF)V
+ */
+/*
+==> engine/src/bullet-native/jmeMotionState.h <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+==> engine/src/bullet-native/android/Android.mk <==
+==> engine/src/bullet-native/android/Application.mk <==
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_VehicleWheel */
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getWheelLocation
+ * Signature: (JILcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getWheelRotation
+ * Signature: (JILcom/jme3/math/Matrix3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    applyInfo
+ * Signature: (JIFFFFFFFFZF)V
+ */
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_BoxCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_BoxCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;)J
+ */
+==> engine/src/bullet-native/jmeMotionState.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_HingeJoint */
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    enableMotor
+ * Signature: (JZFF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getEnableAngularMotor
+ * Signature: (J)Z
+ */
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getMotorTargetVelocity
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_ConeCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_ConeCollisionShape
+ * Method:    createShape
+ * Signature: (IFF)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_PhysicsJoint */
+/*
+ * Class:     com_jme3_bullet_joints_PhysicsJoint
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_PhysicsJoint
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_CylinderCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CylinderCollisionShape
+ * Method:    createShape
+ * Signature: (ILcom/jme3/math/Vector3f;)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_HeightfieldCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_HeightfieldCollisionShape
+ * Method:    createShape
+ * Signature: (IILjava/nio/ByteBuffer;FFFIZ)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_util_NativeMeshUtil */
+/*
+ * Class:     com_jme3_bullet_util_NativeMeshUtil
+ * Method:    createTriangleIndexVertexArray
+ * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J
+ */
+==> engine/src/bullet-native/jmeClasses.h <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+==> engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_PhysicsCollisionEvent */
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral1
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_ConeJoint */
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    setLimit
+ * Signature: (JFFF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    setAngularOnly
+ * Signature: (JZ)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_PhysicsCollisionObject */
+==> engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen, CJ Hare
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/jmePhysicsSpace.h <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/jmeBulletUtil.h <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_CompoundCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    createShape
+ * Signature: ()J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    addChildShape
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    removeChildShape
+ * Signature: (JJ)J
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_SixDofSpringJoint */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    enableSpring
+ * Signature: (JIZ)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setStiffness
+ * Signature: (JIF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setDamping
+ * Signature: (JIF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+==> engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_infos_RigidBodyMotionState */
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    createMotionState
+ * Signature: ()J
+ */
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    applyTransform
+ * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z
+ */
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    getWorldLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_Point2PointJoint */
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setDamping
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setImpulseClamp
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setTau
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+==> engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_objects_PhysicsVehicle */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_CollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    getMargin
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    setLocalScaling
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    setMargin
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+==> engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_motors_TranslationalLimitMotor */
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+==> engine/src/bullet-native/jmeBulletUtil.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.cpp <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Author: Normen Hansen
+ */
+==> engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_collision_shapes_MeshCollisionShape */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+ * Method:    createShape
+ * Signature: (J)J
+ */
+/*
+ * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+==> engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.h <==
+/* DO NOT EDIT THIS FILE - it is machine generated */
+/* Header for class com_jme3_bullet_joints_SliderJoint */
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getLowerLinLimit
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setLowerLinLimit
+ * Signature: (JF)V
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getUpperLinLimit
+ * Signature: (J)F
+ */
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+==> engine/src/android/res/values/strings.xml <==
+==> engine/src/android/res/menu/options.xml <==
+==> engine/src/android/res/layout/tests.xml <==
+==> engine/src/android/res/layout/about.xml <==
+==> engine/src/android/com/jme3/system/android/JmeAndroidSystem.java <==
+==> engine/src/android/com/jme3/system/android/AndroidTimer.java <==
+/*
+ * Copyright (c) 2003-2009 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.
+ */
+==> engine/src/android/com/jme3/system/android/OGLESContext.java <==
+/*
+ * Copyright (c) 2003-2009 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.
+ */
+==> engine/src/android/com/jme3/system/android/AndroidConfigChooser.java <==
+/**
+ * AndroidConfigChooser is used to determine the best suited EGL Config
+ * @author larynx
+ *
+ */
+==> engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java <==
+==> engine/src/android/com/jme3/audio/android/AndroidAudioData.java <==
+==> engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/android/com/jme3/renderer/android/Android22Workaround.java <==
+==> engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/com/jme3/renderer/android/TextureUtil.java <==
+==> engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java <==
+==> engine/src/android/com/jme3/util/RingBuffer.java <==
+/**
+ * Ring buffer (fixed size queue) implementation using a circular array (array with wrap-around).
+ */
+==> engine/src/android/com/jme3/util/FastInteger.java <==
+/**
+ * The wrapper for the primitive type {@code int}.
+ * <p>
+ * As with the specification, this implementation relies on code laid out in <a
+ * href="http://www.hackersdelight.org/">Henry S. Warren, Jr.'s Hacker's
+ * Delight, (Addison Wesley, 2002)</a> as well as <a
+ * href="http://aggregate.org/MAGIC/">The Aggregate's Magic Algorithms</a>.
+ *
+ * @see java.lang.Number
+ * @since 1.1
+ */
+==> engine/src/android/com/jme3/util/AndroidLogHandler.java <==
+==> engine/src/android/com/jme3/asset/AndroidImageInfo.java <==
+/**
+==> engine/src/android/com/jme3/asset/AndroidAssetManager.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/android/com/jme3/asset/plugins/AndroidLocator.java <==
+==> engine/src/android/com/jme3/app/R.java <==
+/* AUTO-GENERATED FILE.  DO NOT MODIFY.

+ *

+ * This class was automatically generated by the

+ * aapt tool from the resource data it found.  It

+ * should not be modified by hand.

+ */

+==> engine/src/android/com/jme3/app/AndroidHarness.java <==
+/**

+ * <code>AndroidHarness</code> wraps a jme application object and runs it on

+ * Android

+==> engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java <==
+/**
+ * AndroidTouchInputListener is an inputlistener interface which defines callbacks/events for android touch screens
+ * For use with class AndroidInput
+ * @author larynx
+ *
+ */
+==> engine/src/android/com/jme3/input/android/AndroidInput.java <==
+/**
+ * <code>AndroidInput</code> is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs
+ * @author larynx
+ *
+ */
+==> engine/src/android/jme3test/android/R.java <==
+/* AUTO-GENERATED FILE.  DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found.  It
+ * should not be modified by hand.
+ */
+==> engine/src/android/jme3test/android/SimpleSoundTest.java <==
+==> engine/src/android/jme3test/android/DemoMainActivity.java <==
+==> engine/src/android/jme3test/android/DemoLaunchEntry.java <==
+/**
+ * Name (=appClass) and Description of one demo launch inside the main apk
+ * @author larynx
+ *
+ */
+==> engine/src/android/jme3test/android/TestAmbient.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/TestMovingParticle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/AndroidManifest.xml <==
+==> engine/src/android/jme3test/android/DemoAndroidHarness.java <==
+==> engine/src/android/jme3test/android/TestNormalMapping.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/TestBumpModel.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/TestUnshadedModel.java <==
+==> engine/src/android/jme3test/android/DemoLaunchAdapter.java <==
+/**
+ * The view adapter which gets a list of LaunchEntries and displaqs them
+ * @author larynx
+ *
+ */
+==> engine/src/android/jme3test/android/TestSkyLoadingLagoon.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/TestSkyLoadingPrimitives.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/android/jme3test/android/SimpleTexturedTest.java <==
+/*
+ * Android 2.2+ SimpleTextured test.
+ *
+ * created: Mon Nov  8 00:08:22 EST 2010
+ */
+==> engine/src/android/jme3tools/android/Fixed.java <==
+/**
+ *	Fixed point maths class. This can be tailored for specific needs by
+ *	changing the bits allocated to the 'fraction' part (see <code>FIXED_POINT
+ *	</code>, which would also require <code>SIN_PRECALC</code> and <code>
+ *	COS_PRECALC</code> updating).
+ *
+ *  <p><a href="http://blog.numfum.com/2007/09/java-fixed-point-maths.html">
+ *  http://blog.numfum.com/2007/09/java-fixed-point-maths.html</a></p>
+ *
+ *	@version 1.0
+ *	@author CW
+ * 
+ * @deprecated Most devices with OpenGL ES 2.0 have an FPU. Please use
+ * floats instead of this class for decimal math.
+ */
+==> engine/src/core-plugins/com/jme3/audio/plugins/WAVLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/font/plugins/BitmapFontLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/DXTFlipper.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/PFMLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/texture/plugins/ImageFlipper.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryIdContentPair.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryOutputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryClassField.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/ByteUtils.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/ClasspathLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/UrlLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/FileLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/ZipLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/asset/plugins/UrlAssetInfo.java <==
+/**
+ * Handles loading of assets from a URL
+ * 
+ * @author Kirill Vainer
+ */
+==> engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/.DS_Store <==
+==> engine/src/xml/com/jme3/export/xml/XMLExporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/xml/com/jme3/export/xml/DOMSerializer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/xml/com/jme3/export/xml/DOMOutputCapsule.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/xml/com/jme3/export/xml/XMLImporter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/core-data/Common/ShaderLib/Fog.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Common.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Bump.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Texture.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Shadow.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Tangent.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/MultiSample.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Math.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Hdr.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Optics.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Parallax.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Lighting.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Splatting.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Skinning.glsllib <==
+==> engine/src/core-data/Common/ShaderLib/Ubo.glsllib <==
+==> engine/src/core-data/Common/Materials/WhiteColor.j3m <==
+==> engine/src/core-data/Common/Materials/VertexColor.j3m <==
+==> engine/src/core-data/Common/Materials/RedColor.j3m <==
+==> engine/src/core-data/Common/MatDefs/Gui/Gui.j3md <==
+==> engine/src/core-data/Common/MatDefs/Gui/Gui.frag <==
+==> engine/src/core-data/Common/MatDefs/Gui/Gui.vert <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PreShadow.vert <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadow.vert <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PreShadow.frag <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PreShadow.j3md <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadow.j3md <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadow.frag <==
+==> engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert <==
+==> engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.j3md <==
+==> engine/src/core-data/Common/MatDefs/Blur/RadialBlur15.frag <==
+==> engine/src/core-data/Common/MatDefs/Blur/RadialBlur.j3md <==
+==> engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.j3md <==
+==> engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.frag <==
+==> engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.frag <==
+==> engine/src/core-data/Common/MatDefs/Blur/RadialBlur.frag <==
+==> engine/src/core-data/Common/MatDefs/Hdr/LogLum.frag <==
+==> engine/src/core-data/Common/MatDefs/Hdr/ToneMap.j3md <==
+==> engine/src/core-data/Common/MatDefs/Hdr/ToneMap.frag <==
+==> engine/src/core-data/Common/MatDefs/Hdr/LogLum.j3md <==
+==> engine/src/core-data/Common/MatDefs/Light/Deferred.frag <==
+==> engine/src/core-data/Common/MatDefs/Light/Lighting.frag <==
+==> engine/src/core-data/Common/MatDefs/Light/Deferred.vert <==
+==> engine/src/core-data/Common/MatDefs/Light/GBuf.frag <==
+==> engine/src/core-data/Common/MatDefs/Light/Lighting.vert <==
+==> engine/src/core-data/Common/MatDefs/Light/Glow.frag <==
+==> engine/src/core-data/Common/MatDefs/Light/GBuf.vert <==
+==> engine/src/core-data/Common/MatDefs/Light/Deferred.j3md <==
+==> engine/src/core-data/Common/MatDefs/Light/Lighting.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Sky.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/Particle.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Particle.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Sky.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/VertexColor.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/SolidColor.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Unshaded.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/ShowNormals.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/Unshaded.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/ShowNormals.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/Particle.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/Sky.j3md <==
+==> engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.frag <==
+==> engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/ShowNormals.vert <==
+==> engine/src/core-data/Common/MatDefs/Misc/WireColor.j3md <==
+==> engine/src/core-data/Interface/Fonts/Console.fnt <==
+==> engine/src/core-data/Interface/Fonts/Console.png <==
+==> engine/src/core-data/Interface/Fonts/Default.png <==
+==> engine/src/core-data/Interface/Fonts/Default.fnt <==
+==> engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java <==
+==> engine/src/tools/jme3tools/optimize/Octnode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/optimize/OCTTriangle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/optimize/FastOctnode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/optimize/TestCollector.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/optimize/TextureAtlas.java <==
+/*
+ *  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.
+ */
+==> engine/src/tools/jme3tools/optimize/TriangleCollector.java <==
+/*
+ * 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.
+ */
+==> engine/src/tools/jme3tools/optimize/Octree.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/optimize/pvsnotes <==
+==> engine/src/tools/jme3tools/savegame/SaveGame.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Tool for saving Savables as SaveGame entries in a system-dependent way.
+ * @author normenhansen
+ */
+==> engine/src/tools/jme3tools/converters/Converter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/converters/RGB565.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+==> engine/src/tools/jme3tools/converters/FolderConverter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/converters/model/ModelConverter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/TriStrip.java <==
+/*

+ * Copyright (c) 2003-2009 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/IntVec.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/tools/jme3tools/converters/model/strip/StripInfo.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/tools/jme3tools/converters/model/strip/VertexCache.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+/**

+==> engine/src/tools/jme3tools/converters/model/strip/Stripifier.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/tools/jme3tools/converters/model/FloatToFixed.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-common/com/jme3/bullet/BulletAppState.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * <code>BulletAppState</code> allows using bullet physics in an Application.
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/PhysicsTickListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Implement this interface to be called from the physics thread on a physics update.
+==> engine/src/bullet-common/com/jme3/bullet/collision/RagdollCollisionListener.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/bullet-common/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionListener.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * Interface for Objects that want to be informed about collision events in the physics space
+==> engine/src/bullet-common/com/jme3/bullet/util/CollisionShapeFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/VehicleControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/PhysicsControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/KinematicRagdollControl.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollUtils.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollPreset.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/GhostControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A GhostControl moves with the spatial it is attached to and can be used to check
+ * overlaps with other physics objects (e.g. aggro radius).
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/CharacterControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet-common/com/jme3/bullet/control/RigidBodyControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/heightmap.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/water_normalmap.dds <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/caustics.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/foam2.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/foam.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/dudv_map.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Textures/foam3.jpg <==
+==> engine/src/core-effects/Common/MatDefs/Water/Water.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Water/SimpleWater.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Water/simple_water.vert <==
+/*
+==> engine/src/core-effects/Common/MatDefs/Water/Water.frag <==
+==> engine/src/core-effects/Common/MatDefs/Water/Water15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Water/simple_water.frag <==
+/*
+==> engine/src/core-effects/Common/MatDefs/SSAO/Textures/random.png <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur15.frag <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssao15.frag <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/normal.vert <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.j3md <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssao.j3md <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.frag <==
+/*

+==> engine/src/core-effects/Common/MatDefs/SSAO/normal.frag <==
+==> engine/src/core-effects/Common/MatDefs/SSAO/ssao.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/bloomFinal15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/LightScattering15.vert <==
+==> engine/src/core-effects/Common/MatDefs/Post/BloomFinal.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Post.vert <==
+==> engine/src/core-effects/Common/MatDefs/Post/bloomExtract.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/LightScattering.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/GammaCorrection15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/FXAA.vert <==
+==> engine/src/core-effects/Common/MatDefs/Post/BloomExtract.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fog.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/bloomExtract15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/LightScattering.vert <==
+==> engine/src/core-effects/Common/MatDefs/Post/DepthOfField.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Overlay.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fog.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Overlay15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Overlay.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Posterization.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/LightScattering.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fade15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fog15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/DepthOfField.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/CrossHatch15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Posterization15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Posterization.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/FXAA.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/LightScattering15.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Post15.vert <==
+==> engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/CrossHatch.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/bloomFinal.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fade.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/FXAA.j3md <==
+==> engine/src/core-effects/Common/MatDefs/Post/CrossHatch.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/Fade.frag <==
+==> engine/src/core-effects/Common/MatDefs/Post/CartoonEdge15.frag <==
+==> engine/src/core-effects/com/jme3/water/SimpleWaterProcessor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/water/ReflectionProcessor.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * Reflection Processor
+ * Used to render the reflected scene in an off view port
+ */
+==> engine/src/core-effects/com/jme3/water/WaterFilter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core-effects/com/jme3/post/ssao/SSAOFilter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/GammaCorrectionFilter.java <==
+/**
+ * 
+ * @author Phate666
+ * @version 1.0 initial version
+ * @version 1.1 added luma
+ */
+==> engine/src/core-effects/com/jme3/post/filters/ColorOverlayFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/CartoonEdgeFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/FadeFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/RadialBlurFilter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/FogFilter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/PosterizationFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/LightScatteringFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter
+ * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostPorcessor
+ * @author Nehon
+ */
+==> engine/src/core-effects/com/jme3/post/filters/BloomFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/CrossHatchFilter.java <==
+/*
+ * 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/DepthOfFieldFilter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/core-effects/com/jme3/post/filters/FXAAFilter.java <==
+/**
+ * <a href="http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/" rel="nofollow">http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-<span class="domtooltips" title="OpenGL (Open Graphics Library) is a standard specification defining a cross-language, cross-platform API for writing applications that produce 2D and 3D computer graphics." id="domtooltipsspan11">opengl</span>-test-radeon-geforce/3/</a>
+ * <a href="http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf" rel="nofollow">http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf</a>
+ *
+ * @author Phate666 (adapted to jme3)
+ *
+ */
+==> engine/src/niftygui/Common/MatDefs/Nifty/Nifty.j3md <==
+==> engine/src/niftygui/Common/MatDefs/Nifty/Nifty.vert <==
+==> engine/src/niftygui/Common/MatDefs/Nifty/Nifty.frag <==
+==> engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/niftygui/com/jme3/niftygui/SoundDeviceJme.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/niftygui/com/jme3/niftygui/SoundHandleJme.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/niftygui/com/jme3/niftygui/RenderImageJme.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/niftygui/com/jme3/niftygui/RenderDeviceJme.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/niftygui/com/jme3/niftygui/RenderFontJme.java <==
+/*

+ * 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.

+ */

+==> engine/src/niftygui/com/jme3/cinematic/events/GuiTrack.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ * 
+==> engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/PhysicsSpace.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/PhysicsRayTestResult.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/bullet/com/jme3/bullet/collision/shapes/CollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java <==
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ * A simple point, line, triangle or quad collisionShape based on one to four points-
+ * @author normenhansen
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java <==
+/*

+ * To change this template, choose Tools | Templates

+ * and open the template in the editor.

+ */

+/**

+ * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster

+ * than using a regular mesh.

+ * There are a couple tricks though:

+ *	-No rotation or translation is supported.

+ *	-The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being

+ *	equal on either side. If not, the whole collision box is shifted vertically and things don't collide

+ *	as they should.

+ * 

+ * @author Brent Owens

+ */

+==> engine/src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/bullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/bullet/com/jme3/bullet/util/DebugShapeFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/util/NativeMeshUtil.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/bullet/com/jme3/bullet/util/DebugMeshCallback.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/SixDofSpringJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/Point2PointJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/SixDofJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/ConeJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/HingeJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/SliderJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+/**
+ *
+==> engine/src/bullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/bullet/com/jme3/bullet/joints/PhysicsJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/animation/SubtitleTrack.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/test/jme3test/animation/TestCinematic.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/animation/TestMotionPath.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/animation/TestCameraMotionPath.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloTerrain.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloLoop.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloAudio.java <==
+/** Sample 11 - playing 3D audio. */
+==> engine/src/test/jme3test/helloworld/HelloInput.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/helloworld/HelloEffects.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloTerrainCollision.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloPicking.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/helloworld/HelloJME3.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloAssets.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloPhysics.java <==
+/**

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/helloworld/HelloAnimation.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloCollision.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloMaterial.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/helloworld/HelloNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/blender/TestBlenderLoader.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestWav.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestAmbient.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestDoppler.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestOgg.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestMusicStreaming.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestMusicPlayer.form <==
+==> engine/src/test/jme3test/audio/TestMusicPlayer.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/audio/TestReverb.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/terrain/TerrainTestCollision.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/terrain/TerrainTestReadWrite.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/terrain/TerrainFractalGridTest.java <==
+==> engine/src/test/jme3test/terrain/TerrainGridAlphaMapTest.java <==
+==> engine/src/test/jme3test/terrain/TerrainGridTest.java <==
+==> engine/src/test/jme3test/terrain/TerrainGridSerializationTest.java <==
+==> engine/src/test/jme3test/terrain/TerrainGridTileLoaderTest.java <==
+==> engine/src/test/jme3test/terrain/TerrainTestAdvanced.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/terrain/TerrainTest.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/renderer/TestMultiViews.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/renderer/TestParallelProjection.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/math/TestHalfFloat.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/collision/TestTriangleCollision.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/collision/TestRayCasting.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/collision/TestMousePick.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/collision/RayTrace.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/TestChooser.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/water/TestPostWater.java <==
+==> engine/src/test/jme3test/water/TestSceneWater.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/water/TestPostWaterLake.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/water/WaterUI.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/water/TestSimpleWater.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/texture/tex3DThumb.vert <==
+==> engine/src/test/jme3test/texture/TestTextureArray.java <==
+==> engine/src/test/jme3test/texture/TestSkyLoading.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/texture/UnshadedArray.frag <==
+==> engine/src/test/jme3test/texture/TestTexture3D.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+==> engine/src/test/jme3test/texture/tex3D.frag <==
+==> engine/src/test/jme3test/texture/tex3D.j3md <==
+==> engine/src/test/jme3test/texture/UnshadedArray.vert <==
+==> engine/src/test/jme3test/texture/tex3DThumb.j3md <==
+==> engine/src/test/jme3test/texture/tex3D.vert <==
+==> engine/src/test/jme3test/texture/tex3DThumb.frag <==
+==> engine/src/test/jme3test/texture/UnshadedArray.j3md <==
+==> engine/src/test/jme3test/texture/TestTexture3DLoading.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+==> engine/src/test/jme3test/material/TestSimpleBumps.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/material/TestNormalMapping.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/material/TestParallax.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/material/TestBumpModel.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/material/TestUnshadedModel.java <==
+==> engine/src/test/jme3test/material/TestColoredTexture.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/export/TestOgreConvert.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/export/TestAssetLinkNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/stress/TestLeakingGL.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/stress/TestBatchLod.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/stress/TestLodStress.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/asset/TestManyLocators.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/asset/TestAssetCache.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/asset/TestUrlLoading.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/asset/TestOnlineJar.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/asset/TestAbsoluteLocators.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestIDList.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestTempVars.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/state/RootNodeState.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/state/TestAppStates.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestBareBonesApp.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestChangeAppIcon.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestApplication.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestReleaseDirectMemory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/app/TestContextRestart.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/awt/TestSafeCanvas.java <==
+==> engine/src/test/jme3test/awt/TestAwtPanels.java <==
+==> engine/src/test/jme3test/awt/TestCanvas.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/awt/TestApplet.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/awt/AppHarness.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/batching/TestBatchNode.java <==
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/test/jme3test/batching/TestBatchNodeTower.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/*
+ * Copyright (c) 2009-2010 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
+==> engine/src/test/jme3test/batching/TestBatchNodeCluster.java <==
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+==> engine/src/test/jme3test/tools/TestTextureAtlas.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/tools/TestSaveGame.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/tools/TestOctree.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/conversion/TestMipMapGen.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/conversion/TestTriangleStrip.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/niftygui/TestNiftyGui.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/niftygui/TestNiftyToMesh.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/niftygui/TestNiftyExamples.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/effect/TestEverything.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/effect/TestParticleExportingCloning.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/effect/TestMovingParticle.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/effect/TestPointSprite.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/effect/TestExplosionEffect.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestFancyCar.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestQ3.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestBrickWall.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/bullet/TestSimplePhysics.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestPhysicsCharacter.java <==
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved. <p/>

+ * 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. <p/> * 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. <p/> * 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. <p/> 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.

+ */

+==> engine/src/test/jme3test/bullet/TestCcd.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/bullet/TestAttachDriver.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestAttachGhostObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/PhysicsTestHelper.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/test/jme3test/bullet/TestCollisionGroups.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestBoneRagdoll.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestHoveringTank.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/BombControl.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/test/jme3test/bullet/PhysicsHoverControl.java <==
+/*
+ * 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.
+ */
+==> engine/src/test/jme3test/bullet/TestRagDoll.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author normenhansen
+ */
+==> engine/src/test/jme3test/bullet/TestBrickTower.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/*
+ * Copyright (c) 2009-2010 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
+==> engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestPhysicsReadWrite.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestGhostObject.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestWalkingChar.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestCollisionShapeFactory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestPhysicsCar.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestRagdollCharacter.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestLocalPhysics.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java <==
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+/**
+ *
+ * @author Nehon
+ */
+==> engine/src/test/jme3test/bullet/TestCollisionListener.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/scene/TestSceneLoading.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/scene/TestUserData.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/TestControls.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/combomoves/ComboMove.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/combomoves/ComboMoveExecution.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/combomoves/TestComboMoves.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/TestCameraNode.java <==
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * 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.
+ */
+==> engine/src/test/jme3test/input/TestChaseCamera.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/input/TestJoystick.java <==
+==> engine/src/test/jme3test/bounding/TestRayCollision.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/TestMonkeyHead.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/TestOgreLoading.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/model/anim/TestOgreAnim.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/anim/TestCustomAnim.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/anim/TestAnimBlendBug.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/anim/TestAnimationFactory.java <==
+==> engine/src/test/jme3test/model/anim/TestSpatialAnim.java <==
+==> engine/src/test/jme3test/model/anim/TestBlenderAnim.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/model/anim/TestBlenderObjectAnim.java <==
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+==> engine/src/test/jme3test/model/shape/TestCylinder.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/shape/TestDebugShapes.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/shape/TestBillboard.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/shape/TestCustomMesh.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/shape/TestBox.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/shape/TestSphere.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/TestHoverTank.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/model/TestObjLoading.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestBloom.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestRenderToTexture.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestCrossHatch.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestDepthOfField.java <==
+/**
+ * test
+ * @author Nehon
+ */
+==> engine/src/test/jme3test/post/SSAOUI.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestRenderToMemory.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestCartoonEdge.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestPostFilters.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestSSAO.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestFog.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestPosterization.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestTransparentCartoonEdge.java <==
+==> engine/src/test/jme3test/post/TestLightScattering.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/LightScatteringUI.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestFBOPassthrough.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestTransparentSSAO.java <==
+==> engine/src/test/jme3test/post/BloomUI.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestMultiRenderTarget.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestHDR.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestMultiViewsFilters.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/post/TestMultiplesFilters.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestLightNode.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestSpotLightTerrain.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestTangentGenBadUV.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestTransparentShadow.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestTangentGenBadModels.java <==
+/**
+ *
+ * @author Kirusha
+ */
+==> engine/src/test/jme3test/light/TestShadow.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestTangentGen.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestSimpleLighting.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestLightRadius.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestSpotLight.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestPssmShadow.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/light/TestEnvironmentMapping.java <==
+/**
+ * test
+ * @author nehon
+ */
+==> engine/src/test/jme3test/light/TestManyLights.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/gui/TestSoftwareMouse.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/gui/TestZOrder.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/gui/TestBitmapText3D.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/gui/TestOrtho.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/gui/TestBitmapFont.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestRemoteCall.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestLatency.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestChatClient.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/test/jme3test/network/TestNetworkStress.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestChatServer.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/test/jme3test/network/MovingAverage.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestThroughput.java <==
+/*
+ * Copyright (c) 2011 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.
+ */
+==> engine/src/test/jme3test/network/TestMessages.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/network/TestSerialization.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+==> engine/src/test/jme3test/games/CubeField.java <==
+/*
+ * Copyright (c) 2009-2010 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.
+ */
diff --git a/README b/README
new file mode 100644
index 0000000..e14b34f
--- /dev/null
+++ b/README
@@ -0,0 +1,37 @@
+jMonkeyEngine3 on Android README
+
+This is the source tree for the engine portion of jMonkeyEngine, from a
+Subversion snapshot taken on 2012.02.24. It includes the engine/src directory
+from their source repository, but not the rest as it is quite large.
+
+The Android makefile does not build the entire library, but only these
+directories:
+
+engine/src/android
+engine/src/core
+engine/src/core-plugins
+engine/src/ogre
+
+which should be enough to use the core parts of the library.
+
+jMonkeyEngine relies on material and shader resources that are intended to be
+saved in the Java classpath and picked up by the classloader. On Android, they
+are saved in the APK and AssetManager is used to load them in. This works if
+you build the APK in something like Eclipse and simply include a source jar,
+but that will not work in the Android.mk build environment. Even if the
+resources are included in the static library jar build here, they will be
+stripped out when dex compiles the libraries into the APK. If you want to use
+this library, you will need to duplicate the resources you need into the assets
+directory of your project. As long as the path under assets/ is the same, the
+Android-specific asset loader will find them, e.g. if asked to load
+"Common/MatDefs/Misc/Unshaded.j3md", it will find it under
+"assets/Common/MatDefs/Misc/Unshaded.j3md".
+
+Links:
+
+http://jmonkeyengine.org/
+http://code.google.com/p/jmonkeyengine/checkout
+
+THe NOTICES file was generated with this command:
+
+find engine/src -type f | xargs head -n 35 | grep "^[ /=][*=]" > NOTICE
diff --git a/engine/src/android/com/jme3/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java
new file mode 100644
index 0000000..6631003
--- /dev/null
+++ b/engine/src/android/com/jme3/app/AndroidHarness.java
@@ -0,0 +1,398 @@
+package com.jme3.app;

+

+import android.app.Activity;

+import android.app.AlertDialog;

+import android.content.DialogInterface;

+import android.content.pm.ActivityInfo;

+import android.graphics.drawable.Drawable;

+import android.graphics.drawable.NinePatchDrawable;

+import android.opengl.GLSurfaceView;

+import android.os.Bundle;

+import android.view.ViewGroup.LayoutParams;

+import android.view.*;

+import android.widget.FrameLayout;

+import android.widget.ImageView;

+import android.widget.TextView;

+import com.jme3.audio.AudioRenderer;

+import com.jme3.audio.android.AndroidAudioRenderer;

+import com.jme3.input.android.AndroidInput;

+import com.jme3.input.controls.TouchListener;

+import com.jme3.input.event.TouchEvent;

+import com.jme3.system.AppSettings;

+import com.jme3.system.JmeSystem;

+import com.jme3.system.android.AndroidConfigChooser.ConfigType;

+import com.jme3.system.android.JmeAndroidSystem;

+import com.jme3.system.android.OGLESContext;

+import com.jme3.util.JmeFormatter;

+import java.io.PrintWriter;

+import java.io.StringWriter;

+import java.util.logging.Handler;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * <code>AndroidHarness</code> wraps a jme application object and runs it on

+ * Android

+ *

+ * @author Kirill

+ * @author larynx

+ */

+public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {

+

+    protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());

+    /**

+     * The application class to start

+     */

+    protected String appClass = "jme3test.android.Test";

+    /**

+     * The jme3 application object

+     */

+    protected Application app = null;

+    /**

+     * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is

+     * RGBA8888 or better if supported by the hardware

+     */

+    protected ConfigType eglConfigType = ConfigType.FASTEST;

+    /**

+     * If true all valid and not valid egl configs are logged

+     */

+    protected boolean eglConfigVerboseLogging = false;

+    /**

+     * If true MouseEvents are generated from TouchEvents

+     */

+    protected boolean mouseEventsEnabled = true;

+    /**

+     * Flip X axis

+     */

+    protected boolean mouseEventsInvertX = true;

+    /**

+     * Flip Y axis

+     */

+    protected boolean mouseEventsInvertY = true;

+    /**

+     * Title of the exit dialog, default is "Do you want to exit?"

+     */

+    protected String exitDialogTitle = "Do you want to exit?";

+    /**

+     * Message of the exit dialog, default is "Use your home key to bring this

+     * app into the background or exit to terminate it."

+     */

+    protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";

+    /**

+     * Set the screen window mode. If screenFullSize is true, then the

+     * notification bar and title bar are removed and the screen covers the

+     * entire display.   If screenFullSize is false, then the notification bar

+     * remains visible if screenShowTitle is true while screenFullScreen is

+     * false, then the title bar is also displayed under the notification bar.

+     */

+    protected boolean screenFullScreen = true;

+    /**

+     * if screenShowTitle is true while screenFullScreen is false, then the

+     * title bar is also displayed under the notification bar

+     */

+    protected boolean screenShowTitle = true;

+    /**

+     * Splash Screen picture Resource ID. If a Splash Screen is desired, set

+     * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If

+     * splashPicID = 0, then no splash screen will be displayed.

+     */

+    protected int splashPicID = 0;

+    /**

+     * Set the screen orientation, default is SENSOR

+     * ActivityInfo.SCREEN_ORIENTATION_* constants package

+     * android.content.pm.ActivityInfo

+     *

+     * SCREEN_ORIENTATION_UNSPECIFIED SCREEN_ORIENTATION_LANDSCAPE

+     * SCREEN_ORIENTATION_PORTRAIT SCREEN_ORIENTATION_USER

+     * SCREEN_ORIENTATION_BEHIND SCREEN_ORIENTATION_SENSOR (default)

+     * SCREEN_ORIENTATION_NOSENSOR

+     */

+    protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;

+    protected OGLESContext ctx;

+    protected GLSurfaceView view = null;

+    protected boolean isGLThreadPaused = true;

+    private ImageView splashImageView = null;

+    private FrameLayout frameLayout = null;

+    final private String ESCAPE_EVENT = "TouchEscape";

+

+    static {

+        try {

+            System.loadLibrary("bulletjme");

+        } catch (UnsatisfiedLinkError e) {

+        }

+        JmeSystem.setSystemDelegate(new JmeAndroidSystem());

+    }

+

+    @Override

+    public void onCreate(Bundle savedInstanceState) {

+        super.onCreate(savedInstanceState);

+

+        Logger log = logger;

+        boolean bIsLogFormatSet = false;

+        do {

+            if (log.getHandlers().length == 0) {

+                log = logger.getParent();

+                if (log != null) {

+                    for (Handler h : log.getHandlers()) {

+                        //h.setFormatter(new SimpleFormatter());

+                        h.setFormatter(new JmeFormatter());

+                        bIsLogFormatSet = true;

+                    }

+                }

+            }

+        } while (log != null && !bIsLogFormatSet);

+

+        JmeAndroidSystem.setResources(getResources());

+        JmeAndroidSystem.setActivity(this);

+

+        if (screenFullScreen) {

+            requestWindowFeature(Window.FEATURE_NO_TITLE);

+            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

+                    WindowManager.LayoutParams.FLAG_FULLSCREEN);

+        } else {

+            if (!screenShowTitle) {

+                requestWindowFeature(Window.FEATURE_NO_TITLE);

+            }

+        }

+

+        setRequestedOrientation(screenOrientation);

+

+        // Create Settings

+        AppSettings settings = new AppSettings(true);

+

+        // Create the input class

+        AndroidInput input = new AndroidInput(this);

+        input.setMouseEventsInvertX(mouseEventsInvertX);

+        input.setMouseEventsInvertY(mouseEventsInvertY);

+        input.setMouseEventsEnabled(mouseEventsEnabled);

+

+        // Create application instance

+        try {

+            if (app == null) {

+                @SuppressWarnings("unchecked")

+                Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);

+                app = clazz.newInstance();

+            }

+

+            app.setSettings(settings);

+            app.start();

+            ctx = (OGLESContext) app.getContext();

+            view = ctx.createView(input, eglConfigType, eglConfigVerboseLogging);

+

+            // Set the screen reolution

+            WindowManager wind = this.getWindowManager();

+            Display disp = wind.getDefaultDisplay();

+            ctx.getSettings().setResolution(disp.getWidth(), disp.getHeight());

+

+            AppSettings s = ctx.getSettings();

+            logger.log(Level.INFO, "Settings: Width {0} Height {1}", new Object[]{s.getWidth(), s.getHeight()});

+

+            layoutDisplay();

+        } catch (Exception ex) {

+            handleError("Class " + appClass + " init failed", ex);

+            setContentView(new TextView(this));

+        }

+    }

+

+    @Override

+    protected void onRestart() {

+        super.onRestart();

+        if (app != null) {

+            app.restart();

+        }

+

+        logger.info("onRestart");

+    }

+

+    @Override

+    protected void onStart() {

+        super.onStart();

+        logger.info("onStart");

+    }

+

+    @Override

+    protected void onResume() {

+        super.onResume();

+        if (view != null) {

+            view.onResume();

+        }

+

+        //resume the audio

+        AudioRenderer result = app.getAudioRenderer();

+        if (result != null) {

+            if (result instanceof AndroidAudioRenderer) {

+                AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;

+                renderer.resumeAll();

+            }

+        }

+

+        isGLThreadPaused = false;

+        logger.info("onResume");

+    }

+

+    @Override

+    protected void onPause() {

+        super.onPause();

+        if (view != null) {

+            view.onPause();

+        }

+

+        //pause the audio

+        AudioRenderer result = app.getAudioRenderer();

+        if (result != null) {

+            logger.info("pause: " + result.getClass().getSimpleName());

+            if (result instanceof AndroidAudioRenderer) {

+                AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;

+                renderer.pauseAll();

+            }

+        }

+

+        isGLThreadPaused = true;

+        logger.info("onPause");

+    }

+

+    @Override

+    protected void onStop() {

+        super.onStop();

+

+        logger.info("onStop");

+    }

+

+    @Override

+    protected void onDestroy() {

+        if (app != null) {

+            app.stop(!isGLThreadPaused);

+        }

+

+        logger.info("onDestroy");

+        super.onDestroy();

+    }

+

+    public Application getJmeApplication() {

+        return app;

+    }

+

+    /**

+     * Called when an error has occurred. By default, will show an error message

+     * to the user and print the exception/error to the log.

+     */

+    public void handleError(final String errorMsg, final Throwable t) {

+        String stackTrace = "";

+        String title = "Error";

+

+        if (t != null) {

+            // Convert exception to string

+            StringWriter sw = new StringWriter(100);

+            t.printStackTrace(new PrintWriter(sw));

+            stackTrace = sw.toString();

+            title = t.toString();

+        }

+

+        final String finalTitle = title;

+        final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")

+                + "\n" + stackTrace;

+

+        logger.log(Level.SEVERE, finalMsg);

+

+        runOnUiThread(new Runnable() {

+

+            @Override

+            public void run() {

+                AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)

+                        .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();

+                dialog.show();

+            }

+        });

+    }

+

+    /**

+     * Called by the android alert dialog, terminate the activity and OpenGL

+     * rendering

+     *

+     * @param dialog

+     * @param whichButton

+     */

+    public void onClick(DialogInterface dialog, int whichButton) {

+        if (whichButton != -2) {

+            if (app != null) {

+                app.stop(true);

+            }

+            this.finish();

+        }

+    }

+

+    /**

+     * Gets called by the InputManager on all touch/drag/scale events

+     */

+    @Override

+    public void onTouch(String name, TouchEvent evt, float tpf) {

+        if (name.equals(ESCAPE_EVENT)) {

+            switch (evt.getType()) {

+                case KEY_UP:

+                    runOnUiThread(new Runnable() {

+

+                        @Override

+                        public void run() {

+                            AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)

+                                    .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();

+                            dialog.show();

+                        }

+                    });

+                    break;

+                default:

+                    break;

+            }

+        }

+    }

+

+    public void layoutDisplay() {

+        logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);

+        if (splashPicID != 0) {

+            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(

+                    LayoutParams.FILL_PARENT,

+                    LayoutParams.FILL_PARENT,

+                    Gravity.CENTER);

+

+            frameLayout = new FrameLayout(this);

+            splashImageView = new ImageView(this);

+

+            Drawable drawable = this.getResources().getDrawable(splashPicID);

+            if (drawable instanceof NinePatchDrawable) {

+                splashImageView.setBackgroundDrawable(drawable);

+            } else {

+                splashImageView.setImageResource(splashPicID);

+            }

+

+            frameLayout.addView(view);

+            frameLayout.addView(splashImageView, lp);

+

+            setContentView(frameLayout);

+            logger.log(Level.INFO, "Splash Screen Created");

+        } else {

+            logger.log(Level.INFO, "Splash Screen Skipped.");

+            setContentView(view);

+        }

+    }

+

+    public void removeSplashScreen() {

+        logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);

+        if (splashPicID != 0) {

+            if (frameLayout != null) {

+                if (splashImageView != null) {

+                    this.runOnUiThread(new Runnable() {

+

+                        @Override

+                        public void run() {

+                            splashImageView.setVisibility(View.INVISIBLE);

+                            frameLayout.removeView(splashImageView);

+                        }

+                    });

+                } else {

+                    logger.log(Level.INFO, "splashImageView is null");

+                }

+            } else {

+                logger.log(Level.INFO, "frameLayout is null");

+            }

+        }

+    }

+}

diff --git a/engine/src/android/com/jme3/app/R.java b/engine/src/android/com/jme3/app/R.java
new file mode 100644
index 0000000..4db44e3
--- /dev/null
+++ b/engine/src/android/com/jme3/app/R.java
@@ -0,0 +1,20 @@
+/* AUTO-GENERATED FILE.  DO NOT MODIFY.

+ *

+ * This class was automatically generated by the

+ * aapt tool from the resource data it found.  It

+ * should not be modified by hand.

+ */

+

+package com.jme3.app;

+

+public final class R {

+    public static final class attr {

+    }

+    public static final class layout {

+        public static final int main=0x7f020000;

+    }

+    public static final class string {

+        public static final int app_name=0x7f030000;

+        public static final int jme3_appclass=0x7f030001;

+    }

+}

diff --git a/engine/src/android/com/jme3/asset/AndroidAssetManager.java b/engine/src/android/com/jme3/asset/AndroidAssetManager.java
new file mode 100644
index 0000000..c6f0b17
--- /dev/null
+++ b/engine/src/android/com/jme3/asset/AndroidAssetManager.java
@@ -0,0 +1,115 @@
+/*

+ * Copyright (c) 2009-2010 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.asset;

+

+import com.jme3.asset.plugins.AndroidLocator;

+import com.jme3.asset.plugins.ClasspathLocator;

+import com.jme3.audio.plugins.AndroidAudioLoader;

+import com.jme3.texture.Texture;

+import com.jme3.texture.plugins.AndroidImageLoader;

+import java.net.URL;

+import java.util.logging.Logger;

+

+/**

+ * <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android

+ *

+ * @author larynx

+ */

+public class AndroidAssetManager extends DesktopAssetManager {

+

+    private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName());

+

+    public AndroidAssetManager() {

+        this(null);

+    }

+

+    @Deprecated

+    public AndroidAssetManager(boolean loadDefaults) {

+        //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));

+        this(null);

+    }

+

+    /**

+     * AndroidAssetManager constructor

+     * If URL == null then a default list of locators and loaders for android is set

+     * @param configFile

+     */

+    public AndroidAssetManager(URL configFile) {

+        System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");

+

+        // Set Default Android config        	       

+        this.registerLocator("", AndroidLocator.class);

+        this.registerLocator("", ClasspathLocator.class);

+        this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");

+        this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav");

+        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");

+        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");

+        this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");

+        this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds");

+        this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm");

+        this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr");

+        this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga");

+        this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");

+        this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj");

+        this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl");

+        this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml");

+        this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml");

+        this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material");

+        this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene");

+        this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");

+

+        logger.info("AndroidAssetManager created.");

+    }

+

+    /**

+     * Loads a texture. 

+     *

+     * @return

+     */

+    @Override

+    public Texture loadTexture(TextureKey key) {

+        Texture tex = (Texture) loadAsset(key);

+

+        // XXX: This will improve performance on some really

+        // low end GPUs (e.g. ones with OpenGL ES 1 support only)

+        // but otherwise won't help on the higher ones. 

+        // Strongly consider removing this.

+        tex.setMagFilter(Texture.MagFilter.Nearest);

+        tex.setAnisotropicFilter(0);

+        if (tex.getMinFilter().usesMipMapLevels()) {

+            tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap);

+        } else {

+            tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);

+        }

+        return tex;

+    }

+}

diff --git a/engine/src/android/com/jme3/asset/AndroidImageInfo.java b/engine/src/android/com/jme3/asset/AndroidImageInfo.java
new file mode 100644
index 0000000..c8eb539
--- /dev/null
+++ b/engine/src/android/com/jme3/asset/AndroidImageInfo.java
@@ -0,0 +1,101 @@
+package com.jme3.asset;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+  * <code>AndroidImageInfo</code> is set in a jME3 image via the {@link Image#setEfficientData(java.lang.Object)}
+  * method to retrieve a {@link Bitmap} when it is needed by the renderer. 
+  * User code may extend <code>AndroidImageInfo</code> and provide their own implementation of the 
+  * {@link AndroidImageInfo#loadBitmap()} method to acquire a bitmap by their own means.
+  *
+  * @author Kirill Vainer
+  */
+public class AndroidImageInfo {
+    
+    protected AssetInfo assetInfo;
+    protected Bitmap bitmap;
+    protected Format format;
+
+    public AndroidImageInfo(AssetInfo assetInfo) {
+        this.assetInfo = assetInfo;
+    }
+    
+    public Bitmap getBitmap(){
+        if (bitmap == null || bitmap.isRecycled()){
+            try {
+                loadBitmap();
+            } catch (IOException ex) {
+                // If called first inside AssetManager, the error will propagate
+                // correctly. Assuming that if the first calls succeeds
+                // then subsequent calls will as well.
+                throw new AssetLoadException("Failed to load image " + assetInfo.getKey(), ex);
+            }
+        }
+        return bitmap;
+    }
+    
+    
+    
+    public Format getFormat(){
+        return format;
+    }
+    
+    /**
+     * Loads the bitmap directly from the asset info, possibly updating
+     * or creating the image object.
+     */
+    protected void loadBitmap() throws IOException{
+        InputStream in = null;
+        try {
+            in = assetInfo.openStream();
+            bitmap = BitmapFactory.decodeStream(in);
+            if (bitmap == null) {
+                throw new IOException("Failed to load image: " + assetInfo.getKey().getName());
+            }
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+
+        switch (bitmap.getConfig()) {
+            case ALPHA_8:
+                format = Image.Format.Alpha8;
+                break;
+            case ARGB_4444:
+                format = Image.Format.ARGB4444;
+                break;
+            case ARGB_8888:
+                format = Image.Format.RGBA8;
+                break;
+            case RGB_565:
+                format = Image.Format.RGB565;
+                break;
+            default:
+                // This should still work as long
+                // as renderer doesn't check format
+                // but just loads bitmap directly.
+                format = null;
+        }
+
+        TextureKey texKey = (TextureKey) assetInfo.getKey();
+        if (texKey.isFlipY()) {
+            // Flip the image, then delete the old one.
+            Matrix flipMat = new Matrix();
+            flipMat.preScale(1.0f, -1.0f);
+            Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipMat, false);
+            bitmap.recycle();
+            bitmap = newBitmap;
+
+            if (bitmap == null) {
+                throw new IOException("Failed to flip image: " + texKey);
+            }
+        }  
+    }
+}
diff --git a/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java b/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java
new file mode 100644
index 0000000..01b1cab
--- /dev/null
+++ b/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java
@@ -0,0 +1,90 @@
+package com.jme3.asset.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.system.android.JmeAndroidSystem;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AndroidLocator implements AssetLocator {
+
+    private static final Logger logger = Logger.getLogger(AndroidLocator.class.getName());
+    
+    private android.content.res.AssetManager androidManager;
+    private String rootPath = "";
+
+    private class AndroidAssetInfo extends AssetInfo {
+
+        private InputStream in;
+        private final String assetPath;
+
+        public AndroidAssetInfo(com.jme3.asset.AssetManager assetManager, AssetKey<?> key, String assetPath, InputStream in) {
+            super(assetManager, key);
+            this.assetPath = assetPath;
+            this.in = in;
+        }
+        
+        @Override
+        public InputStream openStream() {
+            if (in != null){
+                // Reuse the already existing stream (only once)
+                InputStream in2 = in;
+                in = null;
+                return in2;
+            }else{
+                // Create a new stream for subsequent invocations.
+                try {
+                    return androidManager.open(assetPath);
+                } catch (IOException ex) {
+                    throw new AssetLoadException("Failed to open asset " + assetPath, ex);
+                }
+            }
+        }
+    }
+
+    private AndroidAssetInfo create(AssetManager assetManager, AssetKey key, String assetPath) throws IOException {
+        try {
+            InputStream in = androidManager.open(assetPath);
+            if (in == null){
+                return null;
+            }else{
+                return new AndroidAssetInfo(assetManager, key, assetPath, in);
+            }
+        } catch (IOException ex) {
+            // XXX: Prefer to show warning here?
+            // Should only surpress exceptions for "file missing" type errors.
+            return null;
+        }
+    }
+    
+    public AndroidLocator() {
+        androidManager = JmeAndroidSystem.getResources().getAssets();
+    }
+
+    public void setRootPath(String rootPath) {
+        this.rootPath = rootPath;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public AssetInfo locate(com.jme3.asset.AssetManager manager, AssetKey key) {
+        String assetPath = rootPath + key.getName();
+        // Fix path issues
+        if (assetPath.startsWith("/")) {
+            // Remove leading /
+            assetPath = assetPath.substring(1);
+        }
+        assetPath = assetPath.replace("//", "/");
+        try {
+            return create(manager, key, assetPath);
+        }catch (IOException ex){
+            // This is different handling than URL locator
+            // since classpath locating would return null at the getResource() 
+            // call, otherwise there's a more critical error...
+            throw new AssetLoadException("Failed to open asset " + assetPath, ex);
+        }
+    }
+}
diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioData.java b/engine/src/android/com/jme3/audio/android/AndroidAudioData.java
new file mode 100644
index 0000000..37f41a8
--- /dev/null
+++ b/engine/src/android/com/jme3/audio/android/AndroidAudioData.java
@@ -0,0 +1,62 @@
+package com.jme3.audio.android;

+

+import com.jme3.asset.AssetKey;

+import com.jme3.audio.AudioData;

+import com.jme3.audio.AudioRenderer;

+import com.jme3.util.NativeObject;

+

+public class AndroidAudioData extends AudioData {

+

+    protected AssetKey<?> assetKey;

+    protected float currentVolume = 0f;

+

+    public AndroidAudioData(){

+        super();

+    }

+    

+    protected AndroidAudioData(int id){

+        super(id);

+    }

+    

+    public AssetKey<?> getAssetKey() {

+        return assetKey;

+    }

+

+    public void setAssetKey(AssetKey<?> assetKey) {

+        this.assetKey = assetKey;

+    }

+

+    @Override

+    public DataType getDataType() {

+        return DataType.Buffer;

+    }

+

+    @Override

+    public float getDuration() {

+        return 0; // TODO: ???

+    }

+

+    @Override

+    public void resetObject() {

+        this.id = -1;

+        setUpdateNeeded();  

+    }

+

+    @Override

+    public void deleteObject(Object rendererObject) {

+        ((AudioRenderer)rendererObject).deleteAudioData(this);

+    }

+

+    public float getCurrentVolume() {

+        return currentVolume;

+    }

+

+    public void setCurrentVolume(float currentVolume) {

+        this.currentVolume = currentVolume;

+    }

+

+    @Override

+    public NativeObject createDestructableClone() {

+        return new AndroidAudioData(id);

+    }

+}

diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java
new file mode 100644
index 0000000..383def5
--- /dev/null
+++ b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java
@@ -0,0 +1,498 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.audio.android;

+

+import android.app.Activity;

+import android.content.Context;

+import android.content.res.AssetFileDescriptor;

+import android.content.res.AssetManager;

+import android.media.AudioManager;

+import android.media.MediaPlayer;

+import android.media.SoundPool;

+import android.util.Log;

+

+import com.jme3.asset.AssetKey;

+import com.jme3.audio.AudioNode.Status;

+import com.jme3.audio.*;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import java.io.IOException;

+import java.util.HashMap;

+import java.util.concurrent.atomic.AtomicBoolean;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class is the android implementation for {@link AudioRenderer}

+ * 

+ * @author larynx

+ * @author plan_rich

+ */

+public class AndroidAudioRenderer implements AudioRenderer,

+        SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {

+

+    private static final Logger logger = Logger.getLogger(AndroidAudioRenderer.class.getName());

+    private final static int MAX_NUM_CHANNELS = 16;

+    private final HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();

+    private SoundPool soundPool = null;

+    private final Vector3f listenerPosition = new Vector3f();

+    // For temp use

+    private final Vector3f distanceVector = new Vector3f();

+    private final Context context;

+    private final AssetManager assetManager;

+    private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();

+    private Listener listener;

+    private boolean audioDisabled = false;

+    private final AudioManager manager;

+

+    public AndroidAudioRenderer(Activity context) {

+        this.context = context;

+        manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

+        context.setVolumeControlStream(AudioManager.STREAM_MUSIC);

+        assetManager = context.getAssets();

+    }

+

+    @Override

+    public void initialize() {

+        soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,

+                0);

+        soundPool.setOnLoadCompleteListener(this);

+    }

+

+    @Override

+    public void updateSourceParam(AudioNode src, AudioParam param) {

+        // logger.log(Level.INFO, "updateSourceParam " + param);

+

+        if (audioDisabled) {

+            return;

+        }

+

+        if (src.getChannel() < 0) {

+            return;

+        }

+

+        switch (param) {

+            case Position:

+                if (!src.isPositional()) {

+                    return;

+                }

+

+                Vector3f pos = src.getWorldTranslation();

+                break;

+            case Velocity:

+                if (!src.isPositional()) {

+                    return;

+                }

+

+                Vector3f vel = src.getVelocity();

+                break;

+            case MaxDistance:

+                if (!src.isPositional()) {

+                    return;

+                }

+                break;

+            case RefDistance:

+                if (!src.isPositional()) {

+                    return;

+                }

+                break;

+            case ReverbFilter:

+                if (!src.isPositional() || !src.isReverbEnabled()) {

+                    return;

+                }

+                break;

+            case ReverbEnabled:

+                if (!src.isPositional()) {

+                    return;

+                }

+

+                if (src.isReverbEnabled()) {

+                    updateSourceParam(src, AudioParam.ReverbFilter);

+                }

+                break;

+            case IsPositional:

+                break;

+            case Direction:

+                if (!src.isDirectional()) {

+                    return;

+                }

+

+                Vector3f dir = src.getDirection();

+                break;

+            case InnerAngle:

+                if (!src.isDirectional()) {

+                    return;

+                }

+                break;

+            case OuterAngle:

+                if (!src.isDirectional()) {

+                    return;

+                }

+                break;

+            case IsDirectional:

+                if (src.isDirectional()) {

+                    updateSourceParam(src, AudioParam.Direction);

+                    updateSourceParam(src, AudioParam.InnerAngle);

+                    updateSourceParam(src, AudioParam.OuterAngle);

+                } else {

+                }

+                break;

+            case DryFilter:

+                if (src.getDryFilter() != null) {

+                    Filter f = src.getDryFilter();

+                    if (f.isUpdateNeeded()) {

+                        // updateFilter(f);

+                    }

+                }

+                break;

+            case Looping:

+                if (src.isLooping()) {

+                }

+                break;

+            case Volume:

+

+                soundPool.setVolume(src.getChannel(), src.getVolume(),

+                        src.getVolume());

+

+                break;

+            case Pitch:

+

+                break;

+        }

+

+    }

+

+    @Override

+    public void updateListenerParam(Listener listener, ListenerParam param) {

+        // logger.log(Level.INFO, "updateListenerParam " + param);

+        if (audioDisabled) {

+            return;

+        }

+

+        switch (param) {

+            case Position:

+                listenerPosition.set(listener.getLocation());

+

+                break;

+            case Rotation:

+                Vector3f dir = listener.getDirection();

+                Vector3f up = listener.getUp();

+

+                break;

+            case Velocity:

+                Vector3f vel = listener.getVelocity();

+

+                break;

+            case Volume:

+                // alListenerf(AL_GAIN, listener.getVolume());

+                break;

+        }

+

+    }

+

+    @Override

+    public void update(float tpf) {

+        float distance;

+        float volume;

+

+        // Loop over all mediaplayers

+        for (AudioNode src : musicPlaying.keySet()) {

+

+            MediaPlayer mp = musicPlaying.get(src);

+            {

+                // Calc the distance to the listener

+                distanceVector.set(listenerPosition);

+                distanceVector.subtractLocal(src.getLocalTranslation());

+                distance = FastMath.abs(distanceVector.length());

+

+                if (distance < src.getRefDistance()) {

+                    distance = src.getRefDistance();

+                }

+                if (distance > src.getMaxDistance()) {

+                    distance = src.getMaxDistance();

+                }

+                volume = src.getRefDistance() / distance;

+

+                AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();

+

+                if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {

+                    // Left / Right channel get the same volume by now, only

+                    // positional

+                    mp.setVolume(volume, volume);

+

+                    audioData.setCurrentVolume(volume);

+                }

+            }

+        }

+    }

+

+    public void setListener(Listener listener) {

+        if (audioDisabled) {

+            return;

+        }

+

+        if (this.listener != null) {

+            // previous listener no longer associated with current

+            // renderer

+            this.listener.setRenderer(null);

+        }

+

+        this.listener = listener;

+        this.listener.setRenderer(this);

+

+    }

+

+    @Override

+    public void cleanup() {

+        // Cleanup sound pool

+        if (soundPool != null) {

+            soundPool.release();

+            soundPool = null;

+        }

+

+        // Cleanup media player

+        for (AudioNode src : musicPlaying.keySet()) {

+            MediaPlayer mp = musicPlaying.get(src);

+            {

+                mp.stop();

+                mp.release();

+                src.setStatus(Status.Stopped);

+            }

+        }

+        musicPlaying.clear();

+    }

+

+    @Override

+    public void onCompletion(MediaPlayer mp) {

+        mp.seekTo(0);

+        mp.stop();

+        // XXX: This has bad performance -> maybe change overall structure of

+        // mediaplayer in this audiorenderer?

+        for (AudioNode src : musicPlaying.keySet()) {

+            if (musicPlaying.get(src) == mp) {

+                src.setStatus(Status.Stopped);

+                break;

+            }

+        }

+    }

+

+    /**

+     * Plays using the {@link SoundPool} of Android. Due to hard limitation of

+     * the SoundPool: After playing more instances of the sound you only have

+     * the channel of the last played instance.

+     * 

+     * It is not possible to get information about the state of the soundpool of

+     * a specific streamid, so removing is not possilbe -> noone knows when

+     * sound finished.

+     */

+    public void playSourceInstance(AudioNode src) {

+        if (audioDisabled) {

+            return;

+        }

+

+        AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();

+

+        if (!(audioData.getAssetKey() instanceof AudioKey)) {

+            throw new IllegalArgumentException("Asset is not a AudioKey");

+        }

+

+        AudioKey assetKey = (AudioKey) audioData.getAssetKey();

+

+        try {

+            if (audioData.getId() < 0) { // found something to load

+                int soundId = soundPool.load(

+                        assetManager.openFd(assetKey.getName()), 1);

+                audioData.setId(soundId);

+            }

+

+            int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);

+

+            if (channel == 0) {

+                soundpoolStillLoading.put(audioData.getId(), src);

+            } else {

+                src.setChannel(channel); // receive a channel at the last

+                // playing at least

+            }

+        } catch (IOException e) {

+            logger.log(Level.SEVERE,

+                    "Failed to load sound " + assetKey.getName(), e);

+            audioData.setId(-1);

+        }

+    }

+

+    @Override

+    public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {

+        AudioNode src = soundpoolStillLoading.remove(sampleId);

+

+        if (src == null) {

+            logger.warning("Something went terribly wrong! onLoadComplete"

+                    + " had sampleId which was not in the HashMap of loading items");

+            return;

+        }

+

+        AudioData audioData = src.getAudioData();

+

+        if (status == 0) // load was successfull

+        {

+            int channelIndex;

+            channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);

+            src.setChannel(channelIndex);

+        }

+    }

+

+    public void playSource(AudioNode src) {

+        if (audioDisabled) {

+            return;

+        }

+

+        AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();

+

+        MediaPlayer mp = musicPlaying.get(src);

+        if (mp == null) {

+            mp = new MediaPlayer();

+            mp.setOnCompletionListener(this);

+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);

+        }

+

+        try {

+            AssetKey<?> key = audioData.getAssetKey();

+

+            AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()

+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),

+                    afd.getLength());

+            mp.prepare();

+            mp.setLooping(src.isLooping());

+            mp.start();

+            src.setChannel(0);

+            src.setStatus(Status.Playing);

+            musicPlaying.put(src, mp);

+

+        } catch (IllegalStateException e) {

+            e.printStackTrace();

+        } catch (Exception e) {

+            e.printStackTrace();

+        }

+    }

+

+    /**

+     * Pause the current playing sounds. Both from the {@link SoundPool} and the

+     * active {@link MediaPlayer}s

+     */

+    public void pauseAll() {

+        if (soundPool != null) {

+            soundPool.autoPause();

+            for (MediaPlayer mp : musicPlaying.values()) {

+                mp.pause();

+            }

+        }

+    }

+

+    /**

+     * Resume all paused sounds.

+     */

+    public void resumeAll() {

+        if (soundPool != null) {

+            soundPool.autoResume();

+            for (MediaPlayer mp : musicPlaying.values()) {

+                mp.start(); //no resume -> api says call start to resume

+            }

+        }

+    }

+

+    public void pauseSource(AudioNode src) {

+        if (audioDisabled) {

+            return;

+        }

+

+        MediaPlayer mp = musicPlaying.get(src);

+        if (mp != null) {

+            mp.pause();

+            src.setStatus(Status.Paused);

+        } else {

+            int channel = src.getChannel();

+            if (channel != -1) {

+                soundPool.pause(channel); // is not very likley to make

+            }											// something useful :)

+        }

+    }

+

+    public void stopSource(AudioNode src) {

+        if (audioDisabled) {

+            return;

+        }

+

+        // can be stream or buffer -> so try to get mediaplayer

+        // if there is non try to stop soundpool

+        MediaPlayer mp = musicPlaying.get(src);

+        if (mp != null) {

+            mp.stop();

+            src.setStatus(Status.Paused);

+        } else {

+            int channel = src.getChannel();

+            if (channel != -1) {

+                soundPool.pause(channel); // is not very likley to make

+                // something useful :)

+            }

+        }

+

+    }

+

+    @Override

+    public void deleteAudioData(AudioData ad) {

+

+        for (AudioNode src : musicPlaying.keySet()) {

+            if (src.getAudioData() == ad) {

+                MediaPlayer mp = musicPlaying.remove(src);

+                mp.stop();

+                mp.release();

+                src.setStatus(Status.Stopped);

+                src.setChannel(-1);

+                ad.setId(-1);

+                break;

+            }

+        }

+

+        if (ad.getId() > 0) {

+            soundPool.unload(ad.getId());

+            ad.setId(-1);

+        }

+    }

+

+    @Override

+    public void setEnvironment(Environment env) {

+        // not yet supported

+    }

+

+    @Override

+    public void deleteFilter(Filter filter) {

+    }

+}

diff --git a/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java b/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java
new file mode 100644
index 0000000..fc9c03e
--- /dev/null
+++ b/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java
@@ -0,0 +1,19 @@
+package com.jme3.audio.plugins;

+

+import com.jme3.asset.AssetInfo;

+import com.jme3.asset.AssetLoader;

+import com.jme3.audio.android.AndroidAudioData;

+import java.io.IOException;

+

+public class AndroidAudioLoader implements AssetLoader 

+{

+

+    @Override

+    public Object load(AssetInfo assetInfo) throws IOException 

+    {

+        AndroidAudioData result = new AndroidAudioData();

+        result.setAssetKey( assetInfo.getKey() );

+        return result;

+    }

+

+}

diff --git a/engine/src/android/com/jme3/input/android/AndroidInput.java b/engine/src/android/com/jme3/input/android/AndroidInput.java
new file mode 100644
index 0000000..ca1141d
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidInput.java
@@ -0,0 +1,619 @@
+package com.jme3.input.android;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.TouchInput;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.input.event.TouchEvent.Type;
+import com.jme3.math.Vector2f;
+import com.jme3.util.RingBuffer;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidInput</code> is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs
+ * @author larynx
+ *
+ */
+public class AndroidInput extends GLSurfaceView implements TouchInput,
+        GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener {
+
+    final private static int MAX_EVENTS = 1024;
+    // Custom settings
+    public boolean mouseEventsEnabled = true;
+    public boolean mouseEventsInvertX = false;
+    public boolean mouseEventsInvertY = false;
+    public boolean keyboardEventsEnabled = false;
+    public boolean dontSendHistory = false;
+    // Used to transfer events from android thread to GLThread
+    final private RingBuffer<TouchEvent> eventQueue = new RingBuffer<TouchEvent>(MAX_EVENTS);
+    final private RingBuffer<TouchEvent> eventPoolUnConsumed = new RingBuffer<TouchEvent>(MAX_EVENTS);
+    final private RingBuffer<TouchEvent> eventPool = new RingBuffer<TouchEvent>(MAX_EVENTS);
+    final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
+    // Internal
+    private ScaleGestureDetector scaledetector;
+    private GestureDetector detector;
+    private int lastX;
+    private int lastY;
+    private final static Logger logger = Logger.getLogger(AndroidInput.class.getName());
+    private boolean isInitialized = false;
+    private RawInputListener listener = null;
+    private static final int[] ANDROID_TO_JME = {
+        0x0, // unknown
+        0x0, // key code soft left
+        0x0, // key code soft right
+        KeyInput.KEY_HOME,
+        KeyInput.KEY_ESCAPE, // key back
+        0x0, // key call
+        0x0, // key endcall
+        KeyInput.KEY_0,
+        KeyInput.KEY_1,
+        KeyInput.KEY_2,
+        KeyInput.KEY_3,
+        KeyInput.KEY_4,
+        KeyInput.KEY_5,
+        KeyInput.KEY_6,
+        KeyInput.KEY_7,
+        KeyInput.KEY_8,
+        KeyInput.KEY_9,
+        KeyInput.KEY_MULTIPLY,
+        0x0, // key pound
+        KeyInput.KEY_UP,
+        KeyInput.KEY_DOWN,
+        KeyInput.KEY_LEFT,
+        KeyInput.KEY_RIGHT,
+        KeyInput.KEY_RETURN, // dpad center
+        0x0, // volume up
+        0x0, // volume down
+        KeyInput.KEY_POWER, // power (?)
+        0x0, // camera
+        0x0, // clear
+        KeyInput.KEY_A,
+        KeyInput.KEY_B,
+        KeyInput.KEY_C,
+        KeyInput.KEY_D,
+        KeyInput.KEY_E,
+        KeyInput.KEY_F,
+        KeyInput.KEY_G,
+        KeyInput.KEY_H,
+        KeyInput.KEY_I,
+        KeyInput.KEY_J,
+        KeyInput.KEY_K,
+        KeyInput.KEY_L,
+        KeyInput.KEY_M,
+        KeyInput.KEY_N,
+        KeyInput.KEY_O,
+        KeyInput.KEY_P,
+        KeyInput.KEY_Q,
+        KeyInput.KEY_R,
+        KeyInput.KEY_S,
+        KeyInput.KEY_T,
+        KeyInput.KEY_U,
+        KeyInput.KEY_V,
+        KeyInput.KEY_W,
+        KeyInput.KEY_X,
+        KeyInput.KEY_Y,
+        KeyInput.KEY_Z,
+        KeyInput.KEY_COMMA,
+        KeyInput.KEY_PERIOD,
+        KeyInput.KEY_LMENU,
+        KeyInput.KEY_RMENU,
+        KeyInput.KEY_LSHIFT,
+        KeyInput.KEY_RSHIFT,
+        //        0x0, // fn
+        //        0x0, // cap (?)
+
+        KeyInput.KEY_TAB,
+        KeyInput.KEY_SPACE,
+        0x0, // sym (?) symbol
+        0x0, // explorer
+        0x0, // envelope
+        KeyInput.KEY_RETURN, // newline/enter
+        KeyInput.KEY_DELETE,
+        KeyInput.KEY_GRAVE,
+        KeyInput.KEY_MINUS,
+        KeyInput.KEY_EQUALS,
+        KeyInput.KEY_LBRACKET,
+        KeyInput.KEY_RBRACKET,
+        KeyInput.KEY_BACKSLASH,
+        KeyInput.KEY_SEMICOLON,
+        KeyInput.KEY_APOSTROPHE,
+        KeyInput.KEY_SLASH,
+        KeyInput.KEY_AT, // at (@)
+        KeyInput.KEY_NUMLOCK, //0x0, // num
+        0x0, //headset hook
+        0x0, //focus
+        KeyInput.KEY_ADD,
+        KeyInput.KEY_LMETA, //menu
+        0x0,//notification
+        0x0,//search
+        0x0,//media play/pause
+        0x0,//media stop
+        0x0,//media next
+        0x0,//media previous
+        0x0,//media rewind
+        0x0,//media fastforward
+        0x0,//mute
+    };
+
+    public AndroidInput(Context ctx, AttributeSet attribs) {
+        super(ctx, attribs);
+        detector = new GestureDetector(null, this, null, false);
+        scaledetector = new ScaleGestureDetector(ctx, this);
+
+    }
+
+    public AndroidInput(Context ctx) {
+        super(ctx);
+        detector = new GestureDetector(null, this, null, false);
+        scaledetector = new ScaleGestureDetector(ctx, this);
+    }
+
+    private TouchEvent getNextFreeTouchEvent() {
+        return getNextFreeTouchEvent(false);
+    }
+
+    /**
+     * Fetches a touch event from the reuse pool
+     * @param wait if true waits for a reusable event to get available/released by an other thread, if false returns a new one if needed
+     * @return a usable TouchEvent
+     */
+    private TouchEvent getNextFreeTouchEvent(boolean wait) {
+        TouchEvent evt = null;
+        synchronized (eventPoolUnConsumed) {
+            int size = eventPoolUnConsumed.size();
+            while (size > 0) {
+                evt = eventPoolUnConsumed.pop();
+                if (!evt.isConsumed()) {
+                    eventPoolUnConsumed.push(evt);
+                    evt = null;
+                } else {
+                    break;
+                }
+                size--;
+            }
+        }
+
+
+        if (evt == null) {
+            if (eventPool.isEmpty() && wait) {
+                logger.warning("eventPool buffer underrun");
+                boolean isEmpty;
+                do {
+                    synchronized (eventPool) {
+                        isEmpty = eventPool.isEmpty();
+                    }
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {
+                    }
+                } while (isEmpty);
+                synchronized (eventPool) {
+                    evt = eventPool.pop();
+                }
+            } else if (eventPool.isEmpty()) {
+                evt = new TouchEvent();
+                logger.warning("eventPool buffer underrun");
+            } else {
+                synchronized (eventPool) {
+                    evt = eventPool.pop();
+                }
+            }
+        }
+        return evt;
+    }
+
+    /**
+     * onTouchEvent gets called from android thread on touchpad events
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean bWasHandled = false;
+        TouchEvent touch;
+        //    System.out.println("native : " + event.getAction());
+        int action = event.getAction() & MotionEvent.ACTION_MASK;
+        int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+        int pointerId = event.getPointerId(pointerIndex);
+
+        // final int historySize = event.getHistorySize();
+        //final int pointerCount = event.getPointerCount();
+
+
+        switch (action) {
+
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_DOWN:
+
+
+                touch = getNextFreeTouchEvent();
+                touch.set(Type.DOWN, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0);
+                touch.setPointerId(pointerId);
+                touch.setTime(event.getEventTime());
+                touch.setPressure(event.getPressure(pointerIndex));
+                processEvent(touch);
+
+                bWasHandled = true;
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+
+                touch = getNextFreeTouchEvent();
+                touch.set(Type.UP, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0);
+                touch.setPointerId(pointerId);
+                touch.setTime(event.getEventTime());
+                touch.setPressure(event.getPressure(pointerIndex));
+                processEvent(touch);
+
+
+                bWasHandled = true;
+                break;
+            case MotionEvent.ACTION_MOVE:
+
+
+                // Convert all pointers into events
+                for (int p = 0; p < event.getPointerCount(); p++) {
+                    Vector2f lastPos = lastPositions.get(pointerIndex);
+                    if (lastPos == null) {
+                        lastPos = new Vector2f(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex));
+                        lastPositions.put(pointerId, lastPos);
+                    }
+                    touch = getNextFreeTouchEvent();
+                    touch.set(Type.MOVE, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), event.getX(pointerIndex) - lastPos.x, this.getHeight() - event.getY(pointerIndex) - lastPos.y);
+                    touch.setPointerId(pointerId);
+                    touch.setTime(event.getEventTime());
+                    touch.setPressure(event.getPressure(pointerIndex));
+                    processEvent(touch);
+                    lastPos.set(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex));
+                }
+                bWasHandled = true;
+                break;
+
+
+            case MotionEvent.ACTION_OUTSIDE:
+                break;
+
+        }
+
+        // Try to detect gestures        
+        this.detector.onTouchEvent(event);
+        this.scaledetector.onTouchEvent(event);
+
+        return bWasHandled;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        TouchEvent evt;
+        evt = getNextFreeTouchEvent();
+        evt.set(TouchEvent.Type.KEY_DOWN);
+        evt.setKeyCode(keyCode);
+        evt.setCharacters(event.getCharacters());
+        evt.setTime(event.getEventTime());
+
+        // Send the event
+        processEvent(evt);
+
+        // Handle all keys ourself except Volume Up/Down
+        if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
+            return false;
+        } else {
+            return true;
+        }
+
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        TouchEvent evt;
+        evt = getNextFreeTouchEvent();
+        evt.set(TouchEvent.Type.KEY_UP);
+        evt.setKeyCode(keyCode);
+        evt.setCharacters(event.getCharacters());
+        evt.setTime(event.getEventTime());
+
+        // Send the event
+        processEvent(evt);
+
+        // Handle all keys ourself except Volume Up/Down
+        if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    // -----------------------------------------
+    // JME3 Input interface
+    @Override
+    public void initialize() {
+        TouchEvent item;
+        for (int i = 0; i < MAX_EVENTS; i++) {
+            item = new TouchEvent();
+            eventPool.push(item);
+        }
+        isInitialized = true;
+    }
+
+    @Override
+    public void destroy() {
+        isInitialized = false;
+
+        // Clean up queues
+        while (!eventPool.isEmpty()) {
+            eventPool.pop();
+        }
+        while (!eventQueue.isEmpty()) {
+            eventQueue.pop();
+        }
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return isInitialized;
+    }
+
+    @Override
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+    // -----------------------------------------
+
+    private void processEvent(TouchEvent event) {
+        synchronized (eventQueue) {
+            eventQueue.push(event);
+        }
+    }
+
+    //  ---------------  INSIDE GLThread  --------------- 
+    @Override
+    public void update() {
+        generateEvents();
+    }
+
+    private void generateEvents() {
+        if (listener != null) {
+            TouchEvent event;
+            MouseButtonEvent btn;
+            int newX;
+            int newY;
+
+            while (!eventQueue.isEmpty()) {
+                synchronized (eventQueue) {
+                    event = eventQueue.pop();
+                }
+                if (event != null) {
+                    listener.onTouchEvent(event);
+
+                    if (mouseEventsEnabled) {
+                        if (mouseEventsInvertX) {
+                            newX = this.getWidth() - (int) event.getX();
+                        } else {
+                            newX = (int) event.getX();
+                        }
+
+                        if (mouseEventsInvertY) {
+                            newY = this.getHeight() - (int) event.getY();
+                        } else {
+                            newY = (int) event.getY();
+                        }
+
+                        switch (event.getType()) {
+                            case DOWN:
+                                // Handle mouse down event 
+                                btn = new MouseButtonEvent(0, true, newX, newY);
+                                btn.setTime(event.getTime());
+                                listener.onMouseButtonEvent(btn);
+                                // Store current pos
+                                lastX = -1;
+                                lastY = -1;
+                                break;
+
+                            case UP:
+                                // Handle mouse up event 
+                                btn = new MouseButtonEvent(0, false, newX, newY);
+                                btn.setTime(event.getTime());
+                                listener.onMouseButtonEvent(btn);
+                                // Store current pos
+                                lastX = -1;
+                                lastY = -1;
+                                break;
+
+                            case MOVE:
+                                int dx;
+                                int dy;
+                                if (lastX != -1) {
+                                    dx = newX - lastX;
+                                    dy = newY - lastY;
+                                } else {
+                                    dx = 0;
+                                    dy = 0;
+                                }
+                                MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0);
+                                mot.setTime(event.getTime());
+                                listener.onMouseMotionEvent(mot);
+                                lastX = newX;
+                                lastY = newY;
+                                break;
+                        }
+
+
+                    }
+                }
+
+                if (event.isConsumed() == false) {
+                    synchronized (eventPoolUnConsumed) {
+                        eventPoolUnConsumed.push(event);
+                    }
+
+                } else {
+                    synchronized (eventPool) {
+                        eventPool.push(event);
+                    }
+                }
+            }
+
+        }
+    }
+    //  --------------- ENDOF INSIDE GLThread  --------------- 
+
+    // --------------- Gesture detected callback events  --------------- 
+    public boolean onDown(MotionEvent event) {
+        return false;
+    }
+
+    public void onLongPress(MotionEvent event) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.LONGPRESSED, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(event.getEventTime());
+        processEvent(touch);
+    }
+
+    public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.FLING, event.getX(), this.getHeight() - event.getY(), vx, vy);
+        touch.setPointerId(0);
+        touch.setTime(event.getEventTime());
+        processEvent(touch);
+
+        return true;
+    }
+
+    public boolean onSingleTapConfirmed(MotionEvent event) {
+        //Nothing to do here the tap has already been detected.
+        return false;
+    }
+
+    public boolean onDoubleTap(MotionEvent event) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.DOUBLETAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(event.getEventTime());
+        processEvent(touch);
+        return true;
+    }
+
+    public boolean onDoubleTapEvent(MotionEvent event) {
+        return false;
+    }
+
+    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(scaleGestureDetector.getEventTime());
+        touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+        touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
+        processEvent(touch);
+        //    System.out.println("scaleBegin");
+
+        return true;
+    }
+
+    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(scaleGestureDetector.getEventTime());
+        touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+        touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
+        processEvent(touch);
+        //   System.out.println("scale");
+
+        return false;
+    }
+
+    public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(scaleGestureDetector.getEventTime());
+        touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+        touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
+        processEvent(touch);
+    }
+
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.SCROLL, e1.getX(), this.getHeight() - e1.getY(), distanceX, distanceY * (-1));
+        touch.setPointerId(0);
+        touch.setTime(e1.getEventTime());
+        processEvent(touch);
+        //System.out.println("scroll " + e1.getPointerCount());
+        return false;
+    }
+
+    public void onShowPress(MotionEvent event) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.SHOWPRESS, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(event.getEventTime());
+        processEvent(touch);
+    }
+
+    public boolean onSingleTapUp(MotionEvent event) {
+        TouchEvent touch = getNextFreeTouchEvent();
+        touch.set(Type.TAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
+        touch.setPointerId(0);
+        touch.setTime(event.getEventTime());
+        processEvent(touch);
+        return true;
+    }
+
+    @Override
+    public void setSimulateMouse(boolean simulate) {
+        mouseEventsEnabled = simulate;
+    }
+
+    @Override
+    public void setSimulateKeyboard(boolean simulate) {
+        keyboardEventsEnabled = simulate;
+    }
+
+    @Override
+    public void setOmitHistoricEvents(boolean dontSendHistory) {
+        this.dontSendHistory = dontSendHistory;
+    }
+
+    // TODO: move to TouchInput
+    public boolean isMouseEventsEnabled() {
+        return mouseEventsEnabled;
+    }
+
+    public void setMouseEventsEnabled(boolean mouseEventsEnabled) {
+        this.mouseEventsEnabled = mouseEventsEnabled;
+    }
+
+    public boolean isMouseEventsInvertY() {
+        return mouseEventsInvertY;
+    }
+
+    public void setMouseEventsInvertY(boolean mouseEventsInvertY) {
+        this.mouseEventsInvertY = mouseEventsInvertY;
+    }
+
+    public boolean isMouseEventsInvertX() {
+        return mouseEventsInvertX;
+    }
+
+    public void setMouseEventsInvertX(boolean mouseEventsInvertX) {
+        this.mouseEventsInvertX = mouseEventsInvertX;
+    }
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java
new file mode 100644
index 0000000..34e4592
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java
@@ -0,0 +1,19 @@
+package com.jme3.input.android;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.TouchEvent;
+
+/**
+ * AndroidTouchInputListener is an inputlistener interface which defines callbacks/events for android touch screens
+ * For use with class AndroidInput
+ * @author larynx
+ *
+ */
+public interface AndroidTouchInputListener extends RawInputListener
+{
+    public void onTouchEvent(TouchEvent evt);
+    public void onMotionEvent(MotionEvent evt);
+    public void onAndroidKeyEvent(KeyEvent evt);
+}
diff --git a/engine/src/android/com/jme3/renderer/android/Android22Workaround.java b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java
new file mode 100644
index 0000000..9c5bf58
--- /dev/null
+++ b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java
@@ -0,0 +1,14 @@
+package com.jme3.renderer.android;
+
+import android.opengl.GLES20;
+
+public class Android22Workaround {
+    public static void glVertexAttribPointer(int location, int components, int format, boolean normalize, int stride, int offset){
+        GLES20.glVertexAttribPointer(location,
+                                     components,
+                                     format,
+                                     normalize,
+                                     stride,
+                                     offset);
+    }
+}
diff --git a/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java
new file mode 100644
index 0000000..b5a8c14
--- /dev/null
+++ b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java
@@ -0,0 +1,2951 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer.android;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES10;
+import android.opengl.GLES11;
+import android.opengl.GLES20;
+import android.os.Build;
+import com.jme3.asset.AndroidImageInfo;
+import com.jme3.light.LightList;
+import com.jme3.material.RenderState;
+import com.jme3.math.*;
+import com.jme3.renderer.*;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.shader.Attribute;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.shader.Shader.ShaderType;
+import com.jme3.shader.Uniform;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.FrameBuffer.RenderBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapAxis;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.ListMap;
+import com.jme3.util.NativeObjectManager;
+import com.jme3.util.SafeArrayList;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.microedition.khronos.opengles.GL10;
+
+public class OGLESShaderRenderer implements Renderer {
+
+    private static final Logger logger = Logger.getLogger(OGLESShaderRenderer.class.getName());
+    private static final boolean VALIDATE_SHADER = false;
+    private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250);
+    private final StringBuilder stringBuf = new StringBuilder(250);
+    private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1);
+    private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16);
+    private final RenderContext context = new RenderContext();
+    private final NativeObjectManager objManager = new NativeObjectManager();
+    private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
+    // current state
+    private Shader boundShader;
+    private int initialDrawBuf, initialReadBuf;
+    private int glslVer;
+    private int vertexTextureUnits;
+    private int fragTextureUnits;
+    private int vertexUniforms;
+    private int fragUniforms;
+    private int vertexAttribs;
+    private int maxFBOSamples;
+    private int maxFBOAttachs;
+    private int maxMRTFBOAttachs;
+    private int maxRBSize;
+    private int maxTexSize;
+    private int maxCubeTexSize;
+    private int maxVertCount;
+    private int maxTriCount;
+    private boolean tdc;
+    private FrameBuffer lastFb = null;
+    private final Statistics statistics = new Statistics();
+    private int vpX, vpY, vpW, vpH;
+    private int clipX, clipY, clipW, clipH;
+    //private final GL10 gl;
+    private boolean powerVr = false;
+    private boolean powerOf2 = false;
+    private boolean verboseLogging = false;
+    private boolean useVBO = false;
+    private boolean checkErrors = true;
+
+    public OGLESShaderRenderer() {
+    }
+
+    public void setUseVA(boolean value) {
+        logger.log(Level.INFO, "use_VBO [{0}] -> [{1}]", new Object[]{useVBO, !value});
+        useVBO = !value;
+    }
+
+    public void setVerboseLogging(boolean value) {
+        logger.log(Level.INFO, "verboseLogging [{0}] -> [{1}]", new Object[]{verboseLogging, value});
+        verboseLogging = value;
+    }
+
+    protected void updateNameBuffer() {
+        int len = stringBuf.length();
+
+        nameBuf.position(0);
+        nameBuf.limit(len);
+        for (int i = 0; i < len; i++) {
+            nameBuf.put((byte) stringBuf.charAt(i));
+        }
+
+        nameBuf.rewind();
+    }
+    
+    private void checkGLError() {
+        if (!checkErrors) return;
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            throw new RendererException("OpenGL Error " + error);
+        }
+    }
+
+    private boolean log(String message) {
+        logger.info(message);
+        return true;
+    }
+
+    public Statistics getStatistics() {
+        return statistics;
+    }
+
+    public EnumSet<Caps> getCaps() {
+        return caps;
+    }
+
+    public void initialize() {
+
+        logger.log(Level.INFO, "Vendor: {0}", GLES20.glGetString(GLES20.GL_VENDOR));
+        logger.log(Level.INFO, "Renderer: {0}", GLES20.glGetString(GLES20.GL_RENDERER));
+        logger.log(Level.INFO, "Version: {0}", GLES20.glGetString(GLES20.GL_VERSION));
+
+        powerVr = GLES20.glGetString(GLES20.GL_RENDERER).contains("PowerVR");
+        
+        String versionStr = GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION);
+        logger.log(Level.INFO, "GLES20.Shading Language Version: {0}", versionStr);
+        if (versionStr == null || versionStr.equals("")) {
+            glslVer = -1;
+            throw new UnsupportedOperationException("GLSL and OpenGL2 is "
+                    + "required for the OpenGL ES "
+                    + "renderer!");
+        }
+
+        // Fix issue in TestRenderToMemory when GL_FRONT is the main
+        // buffer being used.
+
+//        initialDrawBuf = GLES20.glGetIntegeri(GLES20.GL_DRAW_BUFFER);
+//        initialReadBuf = GLES20.glGetIntegeri(GLES20.GL_READ_BUFFER);
+
+        String openGlEsStr = "OpenGL ES GLSL ES ";
+        int spaceIdx = versionStr.indexOf(" ", openGlEsStr.length());
+        if (spaceIdx >= 1) {
+            versionStr = versionStr.substring(openGlEsStr.length(), spaceIdx).trim();
+        }else{
+            versionStr = versionStr.substring(openGlEsStr.length()).trim();
+        }
+
+        float version = Float.parseFloat(versionStr);
+        glslVer = (int) (version * 100);
+
+        switch (glslVer) {
+            // TODO: When new versions of OpenGL ES shader language come out, 
+            // update this.
+            default:
+                caps.add(Caps.GLSL100);
+                break;
+        }
+
+        if (!caps.contains(Caps.GLSL100)) {
+            logger.info("Force-adding GLSL100 support, since OpenGL2 is supported.");
+            caps.add(Caps.GLSL100);
+        }
+
+        GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16);
+        vertexTextureUnits = intBuf16.get(0);
+        logger.log(Level.INFO, "VTF Units: {0}", vertexTextureUnits);
+        if (vertexTextureUnits > 0) {
+            caps.add(Caps.VertexTextureFetch);
+        }
+
+        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16);
+        fragTextureUnits = intBuf16.get(0);
+        logger.log(Level.INFO, "Texture Units: {0}", fragTextureUnits);
+        /*
+        GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);
+        vertexUniforms = intBuf16.get(0);
+        logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);
+        
+        GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16);
+        fragUniforms = intBuf16.get(0);
+        logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms);
+         */
+
+        GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, intBuf16);
+        vertexAttribs = intBuf16.get(0);
+        logger.log(Level.INFO, "Vertex Attributes: {0}", vertexAttribs);
+
+        /*
+        GLES20.glGetIntegerv(GLES20.GL_MAX_VARYING_FLOATS, intBuf16);
+        int varyingFloats = intBuf16.get(0);
+        logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats);
+         */
+
+        GLES20.glGetIntegerv(GLES20.GL_SUBPIXEL_BITS, intBuf16);
+        int subpixelBits = intBuf16.get(0);
+        logger.log(Level.INFO, "Subpixel Bits: {0}", subpixelBits);
+        /*
+        GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_VERTICES, intBuf16);
+        maxVertCount = intBuf16.get(0);
+        logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount);
+        
+        GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_INDICES, intBuf16);
+        maxTriCount = intBuf16.get(0);
+        logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount);
+         */
+        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, intBuf16);
+        maxTexSize = intBuf16.get(0);
+        logger.log(Level.INFO, "Maximum Texture Resolution: {0}", maxTexSize);
+
+        GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16);
+        maxCubeTexSize = intBuf16.get(0);
+        logger.log(Level.INFO, "Maximum CubeMap Resolution: {0}", maxCubeTexSize);
+
+
+        /*
+        if (ctxCaps.GL_ARB_color_buffer_float){
+        // XXX: Require both 16 and 32 bit float support for FloatColorBuffer.
+        if (ctxCaps.GL_ARB_half_float_pixel){
+        caps.add(Caps.FloatColorBuffer);
+        }
+        }
+        
+        if (ctxCaps.GL_ARB_depth_buffer_float){
+        caps.add(Caps.FloatDepthBuffer);
+        }
+        
+        if (ctxCaps.GL_ARB_draw_instanced)
+        caps.add(Caps.MeshInstancing);
+        
+        if (ctxCaps.GL_ARB_fragment_program)
+        caps.add(Caps.ARBprogram);
+        
+        if (ctxCaps.GL_ARB_texture_buffer_object)
+        caps.add(Caps.TextureBuffer);
+        
+        if (ctxCaps.GL_ARB_texture_float){
+        if (ctxCaps.GL_ARB_half_float_pixel){
+        caps.add(Caps.FloatTexture);
+        }
+        }
+        
+        if (ctxCaps.GL_ARB_vertex_array_object)
+        caps.add(Caps.VertexBufferArray);
+        
+        boolean latc = ctxCaps.GL_EXT_texture_compression_latc;
+        boolean atdc = ctxCaps.GL_ATI_texture_compression_3dc;
+        if (latc || atdc){
+        caps.add(Caps.TextureCompressionLATC);
+        if (atdc && !latc){
+        tdc = true;
+        }
+        }
+        
+        if (ctxCaps.GL_EXT_packed_float){
+        caps.add(Caps.PackedFloatColorBuffer);
+        if (ctxCaps.GL_ARB_half_float_pixel){
+        // because textures are usually uploaded as RGB16F
+        // need half-float pixel
+        caps.add(Caps.PackedFloatTexture);
+        }
+        }
+        
+        if (ctxCaps.GL_EXT_texture_array)
+        caps.add(Caps.TextureArray);
+        
+        if (ctxCaps.GL_EXT_texture_shared_exponent)
+        caps.add(Caps.SharedExponentTexture);
+        
+        if (ctxCaps.GL_EXT_framebuffer_object){
+        caps.add(Caps.FrameBuffer);
+        
+        glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16);
+        maxRBSize = intBuf16.get(0);
+        logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize);
+        
+        glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16);
+        maxFBOAttachs = intBuf16.get(0);
+        logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs);
+        
+        if (ctxCaps.GL_EXT_framebuffer_multisample){
+        caps.add(Caps.FrameBufferMultisample);
+        
+        glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16);
+        maxFBOSamples = intBuf16.get(0);
+        logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples);
+        }
+        
+        if (ctxCaps.GL_ARB_draw_buffers){
+        caps.add(Caps.FrameBufferMRT);
+        glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16);
+        maxMRTFBOAttachs = intBuf16.get(0);
+        logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs);
+        }
+        }
+        
+        if (ctxCaps.GL_ARB_multisample){
+        glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16);
+        boolean available = intBuf16.get(0) != 0;
+        glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16);
+        int samples = intBuf16.get(0);
+        logger.log(Level.FINER, "Samples: {0}", samples);
+        boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB);
+        if (samples > 0 && available && !enabled){
+        glEnable(ARBMultisample.GL_MULTISAMPLE_ARB);
+        }
+        }
+         */
+
+        String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
+        logger.log(Level.INFO, "GL_EXTENSIONS: {0}", extensions);
+
+        GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, intBuf16);
+        for (int i = 0; i < intBuf16.limit(); i++) {
+            logger.log(Level.INFO, "Compressed Texture Formats: {0}", intBuf16.get(i));
+        }
+
+        if (extensions.contains("GL_OES_texture_npot")) {
+            powerOf2 = true;
+        }
+
+        applyRenderState(RenderState.DEFAULT);
+//        GLES20.glClearDepthf(1.0f);
+
+        if (verboseLogging) {
+            logger.info("GLES20.glDisable(GL10.GL_DITHER)");
+        }
+
+        GLES20.glDisable(GL10.GL_DITHER);
+
+        checkGLError();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST)");
+        }
+         
+        //It seems that GL10.GL_PERSPECTIVE_CORRECTION_HINT gives invalid_enum error on android.        
+//        GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
+
+//	checkGLError();
+
+        useVBO = false;
+        
+        // NOTE: SDK_INT is only available since 1.6, 
+        // but for jME3 it doesn't matter since android versions 1.5 and below
+        // are not supported.
+        if (Build.VERSION.SDK_INT >= 9){
+            useVBO = true;
+        }
+        
+        logger.log(Level.INFO, "Caps: {0}", caps);        
+    }
+
+    /**
+     * <code>resetGLObjects</code> should be called when die GLView gets recreated to reset all GPU objects
+     */
+    public void resetGLObjects() {
+        objManager.resetObjects();
+        statistics.clearMemory();
+        boundShader = null;
+        lastFb = null;
+        context.reset();
+    }
+
+    public void cleanup() {
+        objManager.deleteAllObjects(this);
+        statistics.clearMemory();
+    }
+
+    private void checkCap(Caps cap) {
+        if (!caps.contains(cap)) {
+            throw new UnsupportedOperationException("Required capability missing: " + cap.name());
+        }
+    }
+
+    /*********************************************************************\
+    |* Render State                                                      *|
+    \*********************************************************************/
+    public void setDepthRange(float start, float end) {
+
+        if (verboseLogging) {
+            logger.log(Level.INFO, "GLES20.glDepthRangef({0}, {1})", new Object[]{start, end});
+        }
+        GLES20.glDepthRangef(start, end);
+        checkGLError();
+    }
+
+    public void clearBuffers(boolean color, boolean depth, boolean stencil) {
+        int bits = 0;
+        if (color) {
+            bits = GLES20.GL_COLOR_BUFFER_BIT;
+        }
+        if (depth) {
+            bits |= GLES20.GL_DEPTH_BUFFER_BIT;
+        }
+        if (stencil) {
+            bits |= GLES20.GL_STENCIL_BUFFER_BIT;
+        }
+        if (bits != 0) {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glClear(color={0}, depth={1}, stencil={2})", new Object[]{color, depth, stencil});
+            }
+            GLES20.glClear(bits);
+            checkGLError();
+        }
+    }
+
+    public void setBackgroundColor(ColorRGBA color) {
+        if (verboseLogging) {
+            logger.log(Level.INFO, "GLES20.glClearColor({0}, {1}, {2}, {3})", new Object[]{color.r, color.g, color.b, color.a});
+        }
+        GLES20.glClearColor(color.r, color.g, color.b, color.a);
+        checkGLError();
+    }
+
+    public void applyRenderState(RenderState state) {
+        /*
+        if (state.isWireframe() && !context.wireframe){
+        GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_LINE);
+        context.wireframe = true;
+        }else if (!state.isWireframe() && context.wireframe){
+        GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_FILL);
+        context.wireframe = false;
+        }
+         */
+        if (state.isDepthTest() && !context.depthTestEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glEnable(GLES20.GL_DEPTH_TEST)");
+            }
+            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
+            checkGLError();
+            if (verboseLogging) {
+                logger.info("GLES20.glDepthFunc(GLES20.GL_LEQUAL)");
+            }
+            GLES20.glDepthFunc(GLES20.GL_LEQUAL);
+            checkGLError();
+            context.depthTestEnabled = true;
+        } else if (!state.isDepthTest() && context.depthTestEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glDisable(GLES20.GL_DEPTH_TEST)");
+            }
+            GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+            checkGLError();
+            context.depthTestEnabled = false;
+        }
+        if (state.isAlphaTest() && !context.alphaTestEnabled) {
+//            GLES20.glEnable(GLES20.GL_ALPHA_TEST);
+//           GLES20.glAlphaFunc(GLES20.GL_GREATER, state.getAlphaFallOff());
+            context.alphaTestEnabled = true;
+        } else if (!state.isAlphaTest() && context.alphaTestEnabled) {
+//            GLES20.glDisable(GLES20.GL_ALPHA_TEST);
+            context.alphaTestEnabled = false;
+        }
+        if (state.isDepthWrite() && !context.depthWriteEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glDepthMask(true)");
+            }
+            GLES20.glDepthMask(true);
+            checkGLError();
+            context.depthWriteEnabled = true;
+        } else if (!state.isDepthWrite() && context.depthWriteEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glDepthMask(false)");
+            }
+            GLES20.glDepthMask(false);
+            checkGLError();
+            context.depthWriteEnabled = false;
+        }
+        if (state.isColorWrite() && !context.colorWriteEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glColorMask(true, true, true, true)");
+            }
+            GLES20.glColorMask(true, true, true, true);
+            checkGLError();
+            context.colorWriteEnabled = true;
+        } else if (!state.isColorWrite() && context.colorWriteEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glColorMask(false, false, false, false)");
+            }
+            GLES20.glColorMask(false, false, false, false);
+            checkGLError();
+            context.colorWriteEnabled = false;
+        }
+        if (state.isPointSprite() && !context.pointSprite) {
+//            GLES20.glEnable(GLES20.GL_POINT_SPRITE);
+//            GLES20.glTexEnvi(GLES20.GL_POINT_SPRITE, GLES20.GL_COORD_REPLACE, GLES20.GL_TRUE);
+//            GLES20.glEnable(GLES20.GL_VERTEX_PROGRAM_POINT_SIZE);
+//            GLES20.glPointParameterf(GLES20.GL_POINT_SIZE_MIN, 1.0f);
+        } else if (!state.isPointSprite() && context.pointSprite) {
+//            GLES20.glDisable(GLES20.GL_POINT_SPRITE);
+        }
+
+        if (state.isPolyOffset()) {
+            if (!context.polyOffsetEnabled) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL)");
+                }
+                GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL);
+                checkGLError();
+                if (verboseLogging) {
+                    logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()});
+                }
+                GLES20.glPolygonOffset(state.getPolyOffsetFactor(),
+                        state.getPolyOffsetUnits());
+                checkGLError();
+                context.polyOffsetEnabled = true;
+                context.polyOffsetFactor = state.getPolyOffsetFactor();
+                context.polyOffsetUnits = state.getPolyOffsetUnits();
+            } else {
+                if (state.getPolyOffsetFactor() != context.polyOffsetFactor
+                        || state.getPolyOffsetUnits() != context.polyOffsetUnits) {
+                    if (verboseLogging) {
+                        logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()});
+                    }
+                    GLES20.glPolygonOffset(state.getPolyOffsetFactor(),
+                            state.getPolyOffsetUnits());
+                    checkGLError();
+                    context.polyOffsetFactor = state.getPolyOffsetFactor();
+                    context.polyOffsetUnits = state.getPolyOffsetUnits();
+                }
+            }
+        } else {
+            if (context.polyOffsetEnabled) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL)");
+                }
+                GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL);
+                checkGLError();
+                context.polyOffsetEnabled = false;
+                context.polyOffsetFactor = 0;
+                context.polyOffsetUnits = 0;
+            }
+        }
+        if (state.getFaceCullMode() != context.cullMode) {
+            if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glDisable(GLES20.GL_CULL_FACE)");
+                }
+                GLES20.glDisable(GLES20.GL_CULL_FACE);
+            } else {
+                if (verboseLogging) {
+                    logger.info("GLES20.glEnable(GLES20.GL_CULL_FACE)");
+                }
+                GLES20.glEnable(GLES20.GL_CULL_FACE);
+            }
+
+            checkGLError();
+
+            switch (state.getFaceCullMode()) {
+                case Off:
+                    break;
+                case Back:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glCullFace(GLES20.GL_BACK)");
+                    }
+                    GLES20.glCullFace(GLES20.GL_BACK);
+                    break;
+                case Front:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glCullFace(GLES20.GL_FRONT)");
+                    }
+                    GLES20.glCullFace(GLES20.GL_FRONT);
+                    break;
+                case FrontAndBack:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK)");
+                    }
+                    GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unrecognized face cull mode: "
+                            + state.getFaceCullMode());
+            }
+
+            checkGLError();
+
+            context.cullMode = state.getFaceCullMode();
+        }
+
+        if (state.getBlendMode() != context.blendMode) {
+            if (state.getBlendMode() == RenderState.BlendMode.Off) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glDisable(GLES20.GL_BLEND)");
+                }
+                GLES20.glDisable(GLES20.GL_BLEND);
+            } else {
+                if (verboseLogging) {
+                    logger.info("GLES20.glEnable(GLES20.GL_BLEND)");
+                }
+                GLES20.glEnable(GLES20.GL_BLEND);
+                switch (state.getBlendMode()) {
+                    case Off:
+                        break;
+                    case Additive:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);
+                        break;
+                    case AlphaAdditive:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE);
+                        break;
+                    case Color:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR);
+                        break;
+                    case Alpha:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+                        break;
+                    case PremultAlpha:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+                        break;
+                    case Modulate:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO);
+                        break;
+                    case ModulateX2:
+                        if (verboseLogging) {
+                            logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR)");
+                        }
+                        GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR);
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unrecognized blend mode: "
+                                + state.getBlendMode());
+                }
+            }
+
+            checkGLError();
+
+            context.blendMode = state.getBlendMode();
+        }
+    }
+
+    /*********************************************************************\
+    |* Camera and World transforms                                       *|
+    \*********************************************************************/
+    public void setViewPort(int x, int y, int w, int h) {
+        if (x != vpX || vpY != y || vpW != w || vpH != h) {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glViewport({0}, {1}, {2}, {3})", new Object[]{x, y, w, h});
+            }
+            GLES20.glViewport(x, y, w, h);
+            checkGLError();
+            vpX = x;
+            vpY = y;
+            vpW = w;
+            vpH = h;
+        }
+    }
+
+    public void setClipRect(int x, int y, int width, int height) {
+        if (!context.clipRectEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glEnable(GLES20.GL_SCISSOR_TEST)");
+            }
+            GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+            checkGLError();
+            context.clipRectEnabled = true;
+        }
+        if (clipX != x || clipY != y || clipW != width || clipH != height) {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glScissor({0}, {1}, {2}, {3})", new Object[]{x, y, width, height});
+            }
+            GLES20.glScissor(x, y, width, height);
+            clipX = x;
+            clipY = y;
+            clipW = width;
+            clipH = height;
+            checkGLError();
+        }
+    }
+
+    public void clearClipRect() {
+        if (context.clipRectEnabled) {
+            if (verboseLogging) {
+                logger.info("GLES20.glDisable(GLES20.GL_SCISSOR_TEST)");
+            }
+            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+            checkGLError();
+            context.clipRectEnabled = false;
+
+            clipX = 0;
+            clipY = 0;
+            clipW = 0;
+            clipH = 0;
+        }
+    }
+
+    public void onFrame() {
+        if (!checkErrors){
+            int error = GLES20.glGetError();
+            if (error != GLES20.GL_NO_ERROR){
+                throw new RendererException("OpenGL Error " + error + ". Enable error checking for more info.");
+            }
+        }
+        objManager.deleteUnused(this);
+//      statistics.clearFrame();
+    }
+
+    public void setWorldMatrix(Matrix4f worldMatrix) {
+    }
+
+    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
+    }
+
+    /*********************************************************************\
+    |* Shaders                                                           *|
+    \*********************************************************************/
+    protected void updateUniformLocation(Shader shader, Uniform uniform) {
+        stringBuf.setLength(0);
+        stringBuf.append(uniform.getName()).append('\0');
+        updateNameBuffer();
+        if (verboseLogging) {
+            logger.log(Level.INFO, "GLES20.glGetUniformLocation({0}, {1})", new Object[]{shader.getId(), uniform.getName()});
+        }
+        int loc = GLES20.glGetUniformLocation(shader.getId(), uniform.getName());
+        checkGLError();
+        if (loc < 0) {
+            uniform.setLocation(-1);
+            // uniform is not declared in shader
+            if (verboseLogging) {
+                logger.log(Level.WARNING, "Uniform [{0}] is not declared in shader.", uniform.getName());
+            }
+        } else {
+            uniform.setLocation(loc);
+        }
+    }
+
+    protected void updateUniform(Shader shader, Uniform uniform) {
+        int shaderId = shader.getId();
+
+        assert uniform.getName() != null;
+        assert shader.getId() > 0;
+
+        if (context.boundShaderProgram != shaderId) {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glUseProgram({0})", shaderId);
+            }
+            GLES20.glUseProgram(shaderId);
+            checkGLError();
+            statistics.onShaderUse(shader, true);
+            boundShader = shader;
+            context.boundShaderProgram = shaderId;
+        } else {
+            statistics.onShaderUse(shader, false);
+        }
+
+        int loc = uniform.getLocation();
+        if (loc == -1) {
+            if (verboseLogging) {
+                logger.log(Level.WARNING, "no location for uniform [{0}]", uniform.getName());
+            }
+            return;
+        }
+
+        if (loc == -2) {
+            // get uniform location
+            updateUniformLocation(shader, uniform);
+            if (uniform.getLocation() == -1) {
+                // not declared, ignore
+
+                if (verboseLogging) {
+                    logger.log(Level.WARNING, "not declared uniform: [{0}]", uniform.getName());
+                }
+
+                uniform.clearUpdateNeeded();
+                return;
+            }
+            loc = uniform.getLocation();
+        }
+
+        if (uniform.getVarType() == null) {
+            logger.warning("value is not set yet.");
+            return; // value not set yet..
+        }
+
+        statistics.onUniformSet();
+
+        uniform.clearUpdateNeeded();
+        FloatBuffer fb;
+        switch (uniform.getVarType()) {
+            case Float:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform1f set Float. " + uniform.getName());
+                }
+                Float f = (Float) uniform.getValue();
+                GLES20.glUniform1f(loc, f.floatValue());
+                break;
+            case Vector2:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform2f set Vector2. " + uniform.getName());
+                }
+                Vector2f v2 = (Vector2f) uniform.getValue();
+                GLES20.glUniform2f(loc, v2.getX(), v2.getY());
+                break;
+            case Vector3:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform3f set Vector3. " + uniform.getName());
+                }
+                Vector3f v3 = (Vector3f) uniform.getValue();
+                GLES20.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ());
+                break;
+            case Vector4:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform4f set Vector4." + uniform.getName());
+                }
+                Object val = uniform.getValue();
+                if (val instanceof ColorRGBA) {
+                    ColorRGBA c = (ColorRGBA) val;
+                    GLES20.glUniform4f(loc, c.r, c.g, c.b, c.a);
+                } else {
+                    Quaternion c = (Quaternion) uniform.getValue();
+                    GLES20.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW());
+                }
+                break;
+            case Boolean:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform1i set Boolean." + uniform.getName());
+                }
+                Boolean b = (Boolean) uniform.getValue();
+                GLES20.glUniform1i(loc, b.booleanValue() ? GLES20.GL_TRUE : GLES20.GL_FALSE);
+                break;
+            case Matrix3:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniformMatrix3fv set Matrix3." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                assert fb.remaining() == 9;
+                GLES20.glUniformMatrix3fv(loc, 1, false, fb);
+                break;
+            case Matrix4:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniformMatrix4fv set Matrix4." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                assert fb.remaining() == 16;
+                GLES20.glUniformMatrix4fv(loc, 1, false, fb);
+                break;
+            case FloatArray:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform1fv set FloatArray." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                GLES20.glUniform1fv(loc, fb.capacity(), fb);
+                break;
+            case Vector2Array:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform2fv set Vector2Array." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                GLES20.glUniform2fv(loc, fb.capacity() / 2, fb);
+                break;
+            case Vector3Array:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform3fv set Vector3Array." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                GLES20.glUniform3fv(loc, fb.capacity() / 3, fb);
+                break;
+            case Vector4Array:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform4fv set Vector4Array." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                GLES20.glUniform4fv(loc, fb.capacity() / 4, fb);
+                break;
+            case Matrix4Array:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform4fv set Matrix4Array." + uniform.getName());
+                }
+                fb = (FloatBuffer) uniform.getValue();
+                GLES20.glUniformMatrix4fv(loc, fb.capacity() / 16, false, fb);
+                break;
+            case Int:
+                if (verboseLogging) {
+                    logger.info("GLES20.glUniform1i set Int." + uniform.getName());
+                }
+                Integer i = (Integer) uniform.getValue();
+                GLES20.glUniform1i(loc, i.intValue());
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType());
+        }
+        checkGLError();
+    }
+
+    protected void updateShaderUniforms(Shader shader) {
+        ListMap<String, Uniform> uniforms = shader.getUniformMap();
+//        for (Uniform uniform : shader.getUniforms()){
+        for (int i = 0; i < uniforms.size(); i++) {
+            Uniform uniform = uniforms.getValue(i);
+            if (uniform.isUpdateNeeded()) {
+                updateUniform(shader, uniform);
+            }
+        }
+    }
+
+    protected void resetUniformLocations(Shader shader) {
+        ListMap<String, Uniform> uniforms = shader.getUniformMap();
+//        for (Uniform uniform : shader.getUniforms()){
+        for (int i = 0; i < uniforms.size(); i++) {
+            Uniform uniform = uniforms.getValue(i);
+            uniform.reset(); // e.g check location again
+        }
+    }
+
+    /*
+     * (Non-javadoc)
+     * Only used for fixed-function. Ignored.
+     */
+    public void setLighting(LightList list) {
+    }
+
+    public int convertShaderType(ShaderType type) {
+        switch (type) {
+            case Fragment:
+                return GLES20.GL_FRAGMENT_SHADER;
+            case Vertex:
+                return GLES20.GL_VERTEX_SHADER;
+//            case Geometry:
+//                return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB;
+            default:
+                throw new RuntimeException("Unrecognized shader type.");
+        }
+    }
+
+    public void updateShaderSourceData(ShaderSource source, String language) {
+        int id = source.getId();
+        if (id == -1) {
+            // create id
+            if (verboseLogging) {
+                logger.info("GLES20.glCreateShader(" + source.getType() + ")");
+            }
+            id = GLES20.glCreateShader(convertShaderType(source.getType()));
+            checkGLError();
+            if (id <= 0) {
+                throw new RendererException("Invalid ID received when trying to create shader.");
+            }
+
+            source.setId(id);
+        }
+
+        // upload shader source
+        // merge the defines and source code
+        byte[] versionData = new byte[]{};//"#version 140\n".getBytes();
+//        versionData = "#define INSTANCING 1\n".getBytes();
+        byte[] definesCodeData = source.getDefines().getBytes();
+        byte[] sourceCodeData = source.getSource().getBytes();
+        ByteBuffer codeBuf = BufferUtils.createByteBuffer(versionData.length
+                + definesCodeData.length
+                + sourceCodeData.length);
+        codeBuf.put(versionData);
+        codeBuf.put(definesCodeData);
+        codeBuf.put(sourceCodeData);
+        codeBuf.flip();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glShaderSource(" + id + ")");
+        }
+
+        if (powerVr && source.getType() == ShaderType.Vertex) {
+            // XXX: This is to fix a bug in old PowerVR, remove
+            // when no longer applicable.
+            GLES20.glShaderSource(
+                    id, source.getDefines()
+                    + source.getSource());
+        } else {
+            GLES20.glShaderSource(
+                    id,
+                    "precision mediump float;\n"
+                    + source.getDefines()
+                    + source.getSource());
+        }
+
+        checkGLError();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glCompileShader(" + id + ")");
+        }
+
+        GLES20.glCompileShader(id);
+
+        checkGLError();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glGetShaderiv(" + id + ", GLES20.GL_COMPILE_STATUS)");
+        }
+
+        GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, intBuf1);
+
+        checkGLError();
+
+        boolean compiledOK = intBuf1.get(0) == GLES20.GL_TRUE;
+        String infoLog = null;
+
+        if (VALIDATE_SHADER || !compiledOK) {
+            // even if compile succeeded, check
+            // log for warnings
+            if (verboseLogging) {
+                logger.info("GLES20.glGetShaderiv()");
+            }
+            GLES20.glGetShaderiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1);
+            checkGLError();
+            if (verboseLogging) {
+                logger.info("GLES20.glGetShaderInfoLog(" + id + ")");
+            }
+            infoLog = GLES20.glGetShaderInfoLog(id);
+            logger.severe("Errooooooooooot(" + id + ")");
+        }
+
+        if (compiledOK) {
+            if (infoLog != null) {
+                logger.log(Level.INFO, "compile success: " + source.getName() + ", " + infoLog);
+            } else {
+                logger.log(Level.FINE, "compile success: " + source.getName());
+            }
+        } else {
+           logger.log(Level.WARNING, "Bad compile of:\n{0}{1}",
+                    new Object[]{source.getDefines(), source.getSource()});
+            if (infoLog != null) {
+                throw new RendererException("compile error in:" + source + " error:" + infoLog);
+            } else {
+                throw new RendererException("compile error in:" + source + " error: <not provided>");
+            }
+        }
+
+        source.clearUpdateNeeded();
+        // only usable if compiled
+        source.setUsable(compiledOK);
+        if (!compiledOK) {
+            // make sure to dispose id cause all program's
+            // shaders will be cleared later.
+            if (verboseLogging) {
+                logger.info("GLES20.glDeleteShader(" + id + ")");
+            }
+            GLES20.glDeleteShader(id);
+            checkGLError();
+        } else {
+            // register for cleanup since the ID is usable
+            objManager.registerForCleanup(source);
+        }
+    }
+
+    public void updateShaderData(Shader shader) {
+        int id = shader.getId();
+        boolean needRegister = false;
+        if (id == -1) {
+            // create program
+
+            if (verboseLogging) {
+                logger.info("GLES20.glCreateProgram()");
+            }
+
+            id = GLES20.glCreateProgram();
+
+            if (id <= 0) {
+                throw new RendererException("Invalid ID received when trying to create shader program.");
+            }
+
+            shader.setId(id);
+            needRegister = true;
+        }
+
+        for (ShaderSource source : shader.getSources()) {
+            if (source.isUpdateNeeded()) {
+                updateShaderSourceData(source, shader.getLanguage());
+                // shader has been compiled here
+            }
+
+            if (!source.isUsable()) {
+                // it's useless.. just forget about everything..
+                shader.setUsable(false);
+                shader.clearUpdateNeeded();
+                return;
+            }
+            if (verboseLogging) {
+                logger.info("GLES20.glAttachShader(" + id + ", " + source.getId() + ")");
+            }
+
+            GLES20.glAttachShader(id, source.getId());
+        }
+
+        // link shaders to program
+        if (verboseLogging) {
+            logger.info("GLES20.glLinkProgram(" + id + ")");
+        }
+
+        GLES20.glLinkProgram(id);
+
+
+        if (verboseLogging) {
+            logger.info("GLES20.glGetProgramiv(" + id + ")");
+        }
+
+        GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, intBuf1);
+
+        boolean linkOK = intBuf1.get(0) == GLES20.GL_TRUE;
+        String infoLog = null;
+
+        if (VALIDATE_SHADER || !linkOK) {
+            if (verboseLogging) {
+                logger.info("GLES20.glGetProgramiv(" + id + ", GLES20.GL_INFO_LOG_LENGTH, buffer)");
+            }
+
+            GLES20.glGetProgramiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1);
+
+            int length = intBuf1.get(0);
+            if (length > 3) {
+                // get infos
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glGetProgramInfoLog(" + id + ")");
+                }
+
+                infoLog = GLES20.glGetProgramInfoLog(id);
+            }
+        }
+
+        if (linkOK) {
+            if (infoLog != null) {
+                logger.log(Level.INFO, "shader link success. \n{0}", infoLog);
+            } else {
+                logger.fine("shader link success");
+            }
+        } else {
+            if (infoLog != null) {
+                throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog);
+            } else {
+                throw new RendererException("Shader link failure, shader:" + shader + " info: <not provided>");
+            }
+        }
+
+        shader.clearUpdateNeeded();
+        if (!linkOK) {
+            // failure.. forget about everything
+            shader.resetSources();
+            shader.setUsable(false);
+            deleteShader(shader);
+        } else {
+            shader.setUsable(true);
+            if (needRegister) {
+                objManager.registerForCleanup(shader);
+                statistics.onNewShader();
+            } else {
+                // OpenGL spec: uniform locations may change after re-link
+                resetUniformLocations(shader);
+            }
+        }
+    }
+
+    public void setShader(Shader shader) {
+        if (verboseLogging) {
+            logger.info("setShader(" + shader + ")");
+        }
+
+        if (shader == null) {
+            if (context.boundShaderProgram > 0) {
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glUseProgram(0)");
+                }
+
+                GLES20.glUseProgram(0);
+
+                statistics.onShaderUse(null, true);
+                context.boundShaderProgram = 0;
+                boundShader = null;
+            }
+        } else {
+            if (shader.isUpdateNeeded()) {
+                updateShaderData(shader);
+            }
+
+            // NOTE: might want to check if any of the 
+            // sources need an update?
+
+            if (!shader.isUsable()) {
+                logger.warning("shader is not usable.");
+                return;
+            }
+
+            assert shader.getId() > 0;
+
+            updateShaderUniforms(shader);
+            if (context.boundShaderProgram != shader.getId()) {
+                if (VALIDATE_SHADER) {
+                    // check if shader can be used
+                    // with current state
+                    if (verboseLogging) {
+                        logger.info("GLES20.glValidateProgram(" + shader.getId() + ")");
+                    }
+
+                    GLES20.glValidateProgram(shader.getId());
+
+                    if (verboseLogging) {
+                        logger.info("GLES20.glGetProgramiv(" + shader.getId() + ", GLES20.GL_VALIDATE_STATUS, buffer)");
+                    }
+
+                    GLES20.glGetProgramiv(shader.getId(), GLES20.GL_VALIDATE_STATUS, intBuf1);
+
+                    boolean validateOK = intBuf1.get(0) == GLES20.GL_TRUE;
+
+                    if (validateOK) {
+                        logger.fine("shader validate success");
+                    } else {
+                        logger.warning("shader validate failure");
+                    }
+                }
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glUseProgram(" + shader.getId() + ")");
+                }
+
+                GLES20.glUseProgram(shader.getId());
+
+                statistics.onShaderUse(shader, true);
+                context.boundShaderProgram = shader.getId();
+                boundShader = shader;
+            } else {
+                statistics.onShaderUse(shader, false);
+            }
+        }
+    }
+
+    public void deleteShaderSource(ShaderSource source) {
+        if (source.getId() < 0) {
+            logger.warning("Shader source is not uploaded to GPU, cannot delete.");
+            return;
+        }
+        source.setUsable(false);
+        source.clearUpdateNeeded();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glDeleteShader(" + source.getId() + ")");
+        }
+
+        GLES20.glDeleteShader(source.getId());
+        source.resetObject();
+    }
+
+    public void deleteShader(Shader shader) {
+        if (shader.getId() == -1) {
+            logger.warning("Shader is not uploaded to GPU, cannot delete.");
+            return;
+        }
+        for (ShaderSource source : shader.getSources()) {
+            if (source.getId() != -1) {
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glDetachShader(" + shader.getId() + ", " + source.getId() + ")");
+                }
+
+                GLES20.glDetachShader(shader.getId(), source.getId());
+                // the next part is done by the GLObjectManager automatically
+//                glDeleteShader(source.getId());
+            }
+        }
+        // kill all references so sources can be collected
+        // if needed.
+        shader.resetSources();
+
+        if (verboseLogging) {
+            logger.info("GLES20.glDeleteProgram(" + shader.getId() + ")");
+        }
+
+        GLES20.glDeleteProgram(shader.getId());
+
+        statistics.onDeleteShader();
+    }
+
+    /*********************************************************************\
+    |* Framebuffers                                                      *|
+    \*********************************************************************/
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
+        logger.warning("copyFrameBuffer is not supported.");
+    }
+
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
+        logger.warning("copyFrameBuffer is not supported.");
+    }
+    /*
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst){
+    if (GLContext.getCapabilities().GL_EXT_framebuffer_blit){
+    int srcW = 0;
+    int srcH = 0;
+    int dstW = 0;
+    int dstH = 0;
+    int prevFBO = context.boundFBO;
+    
+    if (src != null && src.isUpdateNeeded())
+    updateFrameBuffer(src);
+    
+    if (dst != null && dst.isUpdateNeeded())
+    updateFrameBuffer(dst);
+    
+    if (src == null){
+    glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
+    //                srcW = viewWidth;
+    //                srcH = viewHeight;
+    }else{  
+    glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId());
+    srcW = src.getWidth();
+    srcH = src.getHeight();
+    }
+    if (dst == null){
+    glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
+    //                dstW = viewWidth;
+    //                dstH = viewHeight;
+    }else{
+    glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
+    dstW = dst.getWidth();
+    dstH = dst.getHeight();
+    }
+    glBlitFramebufferEXT(0, 0, srcW, srcH,
+    0, 0, dstW, dstH,
+    GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
+    GL_NEAREST);
+    
+    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO);
+    try {
+    checkFrameBufferError();
+    } catch (IllegalStateException ex){
+    logger.log(Level.SEVERE, "Source FBO:\n{0}", src);
+    logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst);
+    throw ex;
+    }
+    }else{
+    throw new UnsupportedOperationException("EXT_framebuffer_blit required.");
+    // TODO: support non-blit copies?
+    }
+    }
+     */
+
+    private void checkFrameBufferError() {
+        logger.warning("checkFrameBufferError is not supported.");
+    }
+    /*
+    private void checkFrameBufferError() {
+    int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+    switch (status) {
+    case GL_FRAMEBUFFER_COMPLETE_EXT:
+    break;
+    case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
+    //Choose different formats
+    throw new IllegalStateException("Framebuffer object format is " +
+    "unsupported by the video hardware.");
+    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
+    throw new IllegalStateException("Framebuffer has erronous attachment.");
+    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
+    throw new IllegalStateException("Framebuffer is missing required attachment.");
+    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
+    throw new IllegalStateException("Framebuffer attachments must have same dimensions.");
+    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
+    throw new IllegalStateException("Framebuffer attachments must have same formats.");
+    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
+    throw new IllegalStateException("Incomplete draw buffer.");
+    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
+    throw new IllegalStateException("Incomplete read buffer.");
+    case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
+    throw new IllegalStateException("Incomplete multisample buffer.");
+    default:
+    //Programming error; will fail on all hardware
+    throw new IllegalStateException("Some video driver error " +
+    "or programming error occured. " +
+    "Framebuffer object status is invalid. ");
+    }
+    }
+     */
+
+    private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
+        logger.warning("updateRenderBuffer is not supported.");
+    }
+    /*
+    private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb){
+    int id = rb.getId();
+    if (id == -1){
+    glGenRenderbuffersEXT(intBuf1);
+    id = intBuf1.get(0);
+    rb.setId(id);
+    }
+    
+    if (context.boundRB != id){
+    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id);
+    context.boundRB = id;
+    }
+    
+    if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize)
+    throw new UnsupportedOperationException("Resolution "+fb.getWidth()+
+    ":"+fb.getHeight()+" is not supported.");
+    
+    if (fb.getSamples() > 0 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample){
+    int samples = fb.getSamples();
+    if (maxFBOSamples < samples){
+    samples = maxFBOSamples;
+    }
+    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT,
+    samples,
+    TextureUtil.convertTextureFormat(rb.getFormat()),
+    fb.getWidth(),
+    fb.getHeight());
+    }else{
+    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
+    TextureUtil.convertTextureFormat(rb.getFormat()),
+    fb.getWidth(),
+    fb.getHeight());
+    }
+    }
+     */
+
+    private int convertAttachmentSlot(int attachmentSlot) {
+        logger.warning("convertAttachmentSlot is not supported.");
+        return -1;
+    }
+    /*
+    private int convertAttachmentSlot(int attachmentSlot){
+    // can also add support for stencil here
+    if (attachmentSlot == -100){
+    return GL_DEPTH_ATTACHMENT_EXT;
+    }else if (attachmentSlot < 0 || attachmentSlot >= 16){
+    throw new UnsupportedOperationException("Invalid FBO attachment slot: "+attachmentSlot);
+    }
+    
+    return GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
+    }
+     */
+
+    public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) {
+        logger.warning("updateRenderTexture is not supported.");
+    }
+    /*
+    public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb){
+    Texture tex = rb.getTexture();
+    Image image = tex.getImage();
+    if (image.isUpdateNeeded())
+    updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels());
+    
+    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
+    convertAttachmentSlot(rb.getSlot()),
+    convertTextureType(tex.getType()),
+    image.getId(),
+    0);
+    }
+     */
+
+    public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) {
+        logger.warning("updateFrameBufferAttachment is not supported.");
+    }
+    /*
+    public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb){
+    boolean needAttach;
+    if (rb.getTexture() == null){
+    // if it hasn't been created yet, then attach is required.
+    needAttach = rb.getId() == -1;
+    updateRenderBuffer(fb, rb);
+    }else{
+    needAttach = false;
+    updateRenderTexture(fb, rb);
+    }
+    if (needAttach){
+    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
+    convertAttachmentSlot(rb.getSlot()),
+    GL_RENDERBUFFER_EXT,
+    rb.getId());
+    }
+    }
+     */
+
+    public void updateFrameBuffer(FrameBuffer fb) {
+        logger.warning("updateFrameBuffer is not supported.");
+    }
+    /*
+    public void updateFrameBuffer(FrameBuffer fb) {
+    int id = fb.getId();
+    if (id == -1){
+    // create FBO
+    glGenFramebuffersEXT(intBuf1);
+    id = intBuf1.get(0);
+    fb.setId(id);
+    objManager.registerForCleanup(fb);
+    
+    statistics.onNewFrameBuffer();
+    }
+    
+    if (context.boundFBO != id){
+    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
+    // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0
+    context.boundDrawBuf = 0;
+    context.boundFBO = id;
+    }
+    
+    FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
+    if (depthBuf != null){
+    updateFrameBufferAttachment(fb, depthBuf);
+    }
+    
+    for (int i = 0; i < fb.getNumColorBuffers(); i++){
+    FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
+    updateFrameBufferAttachment(fb, colorBuf);
+    }
+    
+    fb.clearUpdateNeeded();
+    }
+     */
+
+    public void setMainFrameBufferOverride(FrameBuffer fb){
+    }
+    
+    public void setFrameBuffer(FrameBuffer fb) {
+        if (verboseLogging) {
+            logger.warning("setFrameBuffer is not supported.");
+        }
+    }
+    /*
+    public void setFrameBuffer(FrameBuffer fb) {
+    if (lastFb == fb)
+    return;
+    
+    // generate mipmaps for last FB if needed
+    if (lastFb != null){
+    for (int i = 0; i < lastFb.getNumColorBuffers(); i++){
+    RenderBuffer rb = lastFb.getColorBuffer(i);
+    Texture tex = rb.getTexture();
+    if (tex != null
+    && tex.getMinFilter().usesMipMapLevels()){
+    setTexture(0, rb.getTexture());
+    glGenerateMipmapEXT(convertTextureType(tex.getType()));
+    }
+    }
+    }
+    
+    
+    if (fb == null){
+    // unbind any fbos
+    if (context.boundFBO != 0){
+    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+    statistics.onFrameBufferUse(null, true);
+    
+    context.boundFBO = 0;
+    }
+    // select back buffer
+    if (context.boundDrawBuf != -1){
+    glDrawBuffer(initialDrawBuf);
+    context.boundDrawBuf = -1;
+    }
+    if (context.boundReadBuf != -1){
+    glReadBuffer(initialReadBuf);
+    context.boundReadBuf = -1;
+    }
+    
+    lastFb = null;
+    }else{
+    if (fb.isUpdateNeeded())
+    updateFrameBuffer(fb);
+    
+    if (context.boundFBO != fb.getId()){
+    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId());
+    statistics.onFrameBufferUse(fb, true);
+    
+    // update viewport to reflect framebuffer's resolution
+    setViewPort(0, 0, fb.getWidth(), fb.getHeight());
+    
+    context.boundFBO = fb.getId();
+    }else{
+    statistics.onFrameBufferUse(fb, false);
+    }
+    if (fb.getNumColorBuffers() == 0){
+    // make sure to select NONE as draw buf
+    // no color buffer attached. select NONE
+    if (context.boundDrawBuf != -2){
+    glDrawBuffer(GL_NONE);
+    context.boundDrawBuf = -2;
+    }
+    if (context.boundReadBuf != -2){
+    glReadBuffer(GL_NONE);
+    context.boundReadBuf = -2;
+    }
+    }else{
+    if (fb.isMultiTarget()){
+    if (fb.getNumColorBuffers() > maxMRTFBOAttachs)
+    throw new UnsupportedOperationException("Framebuffer has more"
+    + " targets than are supported"
+    + " on the system!");
+    
+    if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()){
+    intBuf16.clear();
+    for (int i = 0; i < fb.getNumColorBuffers(); i++)
+    intBuf16.put( GL_COLOR_ATTACHMENT0_EXT + i );
+    
+    intBuf16.flip();
+    glDrawBuffers(intBuf16);
+    context.boundDrawBuf = 100 + fb.getNumColorBuffers();
+    }
+    }else{
+    RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex());
+    // select this draw buffer
+    if (context.boundDrawBuf != rb.getSlot()){
+    glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
+    context.boundDrawBuf = rb.getSlot();
+    }
+    }
+    }
+    
+    assert fb.getId() >= 0;
+    assert context.boundFBO == fb.getId();
+    lastFb = fb;
+    }
+    
+    try {
+    checkFrameBufferError();
+    } catch (IllegalStateException ex){
+    logger.log(Level.SEVERE, "Problem FBO:\n{0}", fb);
+    throw ex;
+    }
+    }
+     */
+
+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
+        logger.warning("readFrameBuffer is not supported.");
+    }
+    /*
+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf){
+    if (fb != null){
+    RenderBuffer rb = fb.getColorBuffer();
+    if (rb == null)
+    throw new IllegalArgumentException("Specified framebuffer" +
+    " does not have a colorbuffer");
+    
+    setFrameBuffer(fb);
+    if (context.boundReadBuf != rb.getSlot()){
+    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
+    context.boundReadBuf = rb.getSlot();
+    }
+    }else{
+    setFrameBuffer(null);
+    }
+    
+    glReadPixels(vpX, vpY, vpW, vpH, GL_RGBA GL_BGRA, GL_UNSIGNED_BYTE, byteBuf);
+    }
+     */
+
+    private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
+        logger.warning("deleteRenderBuffer is not supported.");
+    }
+    /*
+    private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb){
+    intBuf1.put(0, rb.getId());
+    glDeleteRenderbuffersEXT(intBuf1);
+    }
+     */
+
+    public void deleteFrameBuffer(FrameBuffer fb) {
+        logger.warning("deleteFrameBuffer is not supported.");
+    }
+    /*
+    public void deleteFrameBuffer(FrameBuffer fb) {
+    if (fb.getId() != -1){
+    if (context.boundFBO == fb.getId()){
+    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+    context.boundFBO = 0;
+    }
+    
+    if (fb.getDepthBuffer() != null){
+    deleteRenderBuffer(fb, fb.getDepthBuffer());
+    }
+    if (fb.getColorBuffer() != null){
+    deleteRenderBuffer(fb, fb.getColorBuffer());
+    }
+    
+    intBuf1.put(0, fb.getId());
+    glDeleteFramebuffersEXT(intBuf1);
+    fb.resetObject();
+    
+    statistics.onDeleteFrameBuffer();
+    }
+    }
+     */
+
+    /*********************************************************************\
+    |* Textures                                                          *|
+    \*********************************************************************/
+    private int convertTextureType(Texture.Type type) {
+        switch (type) {
+            case TwoDimensional:
+                return GLES20.GL_TEXTURE_2D;
+            //        case TwoDimensionalArray:
+            //            return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT;
+//            case ThreeDimensional:
+            //               return GLES20.GL_TEXTURE_3D;
+            case CubeMap:
+                return GLES20.GL_TEXTURE_CUBE_MAP;
+            default:
+                throw new UnsupportedOperationException("Unknown texture type: " + type);
+        }
+    }
+
+    private int convertMagFilter(Texture.MagFilter filter) {
+        switch (filter) {
+            case Bilinear:
+                return GLES20.GL_LINEAR;
+            case Nearest:
+                return GLES20.GL_NEAREST;
+            default:
+                throw new UnsupportedOperationException("Unknown mag filter: " + filter);
+        }
+    }
+
+    private int convertMinFilter(Texture.MinFilter filter) {
+        switch (filter) {
+            case Trilinear:
+                return GLES20.GL_LINEAR_MIPMAP_LINEAR;
+            case BilinearNearestMipMap:
+                return GLES20.GL_LINEAR_MIPMAP_NEAREST;
+            case NearestLinearMipMap:
+                return GLES20.GL_NEAREST_MIPMAP_LINEAR;
+            case NearestNearestMipMap:
+                return GLES20.GL_NEAREST_MIPMAP_NEAREST;
+            case BilinearNoMipMaps:
+                return GLES20.GL_LINEAR;
+            case NearestNoMipMaps:
+                return GLES20.GL_NEAREST;
+            default:
+                throw new UnsupportedOperationException("Unknown min filter: " + filter);
+        }
+    }
+
+    private int convertWrapMode(Texture.WrapMode mode) {
+        switch (mode) {
+//            case BorderClamp:
+//                return GLES20.GL_CLAMP_TO_BORDER;
+//            case Clamp:
+//                return GLES20.GL_CLAMP;
+            case EdgeClamp:
+                return GLES20.GL_CLAMP_TO_EDGE;
+            case Repeat:
+                return GLES20.GL_REPEAT;
+            case MirroredRepeat:
+                return GLES20.GL_MIRRORED_REPEAT;
+            default:
+                throw new UnsupportedOperationException("Unknown wrap mode: " + mode);
+        }
+    }
+
+    /**
+     * <code>setupTextureParams</code> sets the OpenGL context texture parameters
+     * @param tex the Texture to set the texture parameters from
+     */
+    private void setupTextureParams(Texture tex) {
+        int target = convertTextureType(tex.getType());
+
+        // filter things
+        int minFilter = convertMinFilter(tex.getMinFilter());
+        int magFilter = convertMagFilter(tex.getMagFilter());
+
+        if (verboseLogging) {
+            logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MIN_FILTER, " + minFilter + ")");
+        }
+
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, minFilter);
+
+        if (verboseLogging) {
+            logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MAG_FILTER, " + magFilter + ")");
+        }
+
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, magFilter);
+
+        /*        
+        if (tex.getAnisotropicFilter() > 1){
+        
+        if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){
+        glTexParameterf(target,
+        EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT,
+        tex.getAnisotropicFilter());
+        }
+        
+        }
+         */
+        // repeat modes
+
+        switch (tex.getType()) {
+            case ThreeDimensional:
+            case CubeMap: // cubemaps use 3D coords
+            // GL_TEXTURE_WRAP_R is not available in api 8
+            //GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R)));
+            case TwoDimensional:
+            case TwoDimensionalArray:
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_T, " + convertWrapMode(tex.getWrap(WrapAxis.T)));
+                }
+
+                GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T)));
+
+                // fall down here is intentional..
+//          case OneDimensional:
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_S, " + convertWrapMode(tex.getWrap(WrapAxis.S)));
+                }
+
+                GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S)));
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown texture type: " + tex.getType());
+        }
+
+        // R to Texture compare mode
+/*
+        if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off){
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_MODE, GLES20.GL_COMPARE_R_TO_TEXTURE);
+        GLES20.glTexParameteri(target, GLES20.GL_DEPTH_TEXTURE_MODE, GLES20.GL_INTENSITY);
+        if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual){
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_GEQUAL);
+        }else{
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_LEQUAL);
+        }
+        }
+         */
+    }
+
+    /**
+     * <code>updateTexImageData</code> activates and binds the texture
+     * @param img
+     * @param type
+     * @param mips
+     */
+    public void updateTexImageData(Image img, Texture.Type type, boolean mips) {
+        int texId = img.getId();
+        if (texId == -1) {
+            // create texture
+            if (verboseLogging) {
+                logger.info("GLES20.glGenTexture(1, buffer)");
+            }
+
+            GLES20.glGenTextures(1, intBuf1);
+            texId = intBuf1.get(0);
+            img.setId(texId);
+            objManager.registerForCleanup(img);
+
+            statistics.onNewTexture();
+        }
+
+        // bind texture
+        int target = convertTextureType(type);
+        if (context.boundTextures[0] != img) {
+            if (context.boundTextureUnit != 0) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0)");
+                }
+
+                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+                context.boundTextureUnit = 0;
+            }
+
+            if (verboseLogging) {
+                logger.info("GLES20.glBindTexture(" + target + ", " + texId + ")");
+            }
+
+            GLES20.glBindTexture(target, texId);
+            context.boundTextures[0] = img;
+        }
+
+
+        if (target == GLES20.GL_TEXTURE_CUBE_MAP) {
+            // Upload a cube map / sky box
+            @SuppressWarnings("unchecked")
+            List<AndroidImageInfo> bmps = (List<AndroidImageInfo>) img.getEfficentData();
+            if (bmps != null) {
+                // Native android bitmap                                       
+                if (bmps.size() != 6) {
+                    throw new UnsupportedOperationException("Invalid texture: " + img
+                            + "Cubemap textures must contain 6 data units.");
+                }
+                for (int i = 0; i < 6; i++) {
+                    TextureUtil.uploadTextureBitmap(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), false, powerOf2);
+                }
+            } else {
+                // Standard jme3 image data
+                List<ByteBuffer> data = img.getData();
+                if (data.size() != 6) {
+                    logger.log(Level.WARNING, "Invalid texture: {0}\n"
+                            + "Cubemap textures must contain 6 data units.", img);
+                    return;
+                }
+                for (int i = 0; i < 6; i++) {
+                    TextureUtil.uploadTexture(img, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc, false, powerOf2);
+                }
+            }
+        } else {
+            TextureUtil.uploadTexture(img, target, 0, 0, tdc, false, powerOf2);
+
+            if (verboseLogging) {
+                logger.info("GLES20.glTexParameteri(" + target + "GLES11.GL_GENERATE_MIMAP, GLES20.GL_TRUE)");
+            }
+
+            if (!img.hasMipmaps() && mips) {
+                // No pregenerated mips available,
+                // generate from base level if required
+                if (verboseLogging) {
+                    logger.info("GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D)");
+                }
+                GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
+            }
+        }
+
+        img.clearUpdateNeeded();
+    }
+
+    public void setTexture(int unit, Texture tex) {
+        Image image = tex.getImage();
+        if (image.isUpdateNeeded()) {
+            /*
+            Bitmap bmp = (Bitmap)image.getEfficentData();
+            if (bmp != null)
+            {
+            // Check if the bitmap got recycled, can happen after wakeup/restart
+            if ( bmp.isRecycled() )
+            {
+            // We need to reload the bitmap
+            Texture textureReloaded = JmeSystem.newAssetManager().loadTexture((TextureKey)tex.getKey());
+            image.setEfficentData( textureReloaded.getImage().getEfficentData());
+            }
+            }
+             */
+            updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels());
+        }
+
+        int texId = image.getId();
+        assert texId != -1;
+
+        if (texId == -1) {
+            logger.warning("error: texture image has -1 id");
+        }
+
+        Image[] textures = context.boundTextures;
+
+        int type = convertTextureType(tex.getType());
+        if (!context.textureIndexList.moveToNew(unit)) {
+//             if (context.boundTextureUnit != unit){
+//                glActiveTexture(GL_TEXTURE0 + unit);
+//                context.boundTextureUnit = unit;
+//             }
+//             glEnable(type);
+        }
+
+        if (textures[unit] != image) {
+            if (context.boundTextureUnit != unit) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + " + unit + ")");
+                }
+                GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
+                context.boundTextureUnit = unit;
+            }
+
+            if (verboseLogging) {
+                logger.info("GLES20.glBindTexture(" + type + ", " + texId + ")");
+            }
+
+            GLES20.glBindTexture(type, texId);
+            textures[unit] = image;
+
+            statistics.onTextureUse(tex.getImage(), true);
+        } else {
+            statistics.onTextureUse(tex.getImage(), false);
+        }
+
+        setupTextureParams(tex);
+    }
+
+    public void clearTextureUnits() {
+        IDList textureList = context.textureIndexList;
+        Image[] textures = context.boundTextures;
+        for (int i = 0; i < textureList.oldLen; i++) {
+            int idx = textureList.oldList[i];
+//            if (context.boundTextureUnit != idx){
+//                glActiveTexture(GL_TEXTURE0 + idx);
+//                context.boundTextureUnit = idx;
+//            }
+//            glDisable(convertTextureType(textures[idx].getType()));
+            textures[idx] = null;
+        }
+        context.textureIndexList.copyNewToOld();
+    }
+
+    public void deleteImage(Image image) {
+        int texId = image.getId();
+        if (texId != -1) {
+            intBuf1.put(0, texId);
+            intBuf1.position(0).limit(1);
+
+            if (verboseLogging) {
+                logger.info("GLES20.glDeleteTexture(1, buffer)");
+            }
+
+            GLES20.glDeleteTextures(1, intBuf1);
+            image.resetObject();
+
+            statistics.onDeleteTexture();
+        }
+    }
+
+    /*********************************************************************\
+    |* Vertex Buffers and Attributes                                     *|
+    \*********************************************************************/
+    private int convertUsage(Usage usage) {
+        switch (usage) {
+            case Static:
+                return GLES20.GL_STATIC_DRAW;
+            case Dynamic:
+                return GLES20.GL_DYNAMIC_DRAW;
+            case Stream:
+                return GLES20.GL_STREAM_DRAW;
+            default:
+                throw new RuntimeException("Unknown usage type.");
+        }
+    }
+
+    private int convertFormat(Format format) {
+        switch (format) {
+            case Byte:
+                return GLES20.GL_BYTE;
+            case UnsignedByte:
+                return GLES20.GL_UNSIGNED_BYTE;
+            case Short:
+                return GLES20.GL_SHORT;
+            case UnsignedShort:
+                return GLES20.GL_UNSIGNED_SHORT;
+            case Int:
+                return GLES20.GL_INT;
+            case UnsignedInt:
+                return GLES20.GL_UNSIGNED_INT;
+            /*
+            case Half:
+            return NVHalfFloat.GL_HALF_FLOAT_NV;
+            //                return ARBHalfFloatVertex.GL_HALF_FLOAT;
+             */
+            case Float:
+                return GLES20.GL_FLOAT;
+//            case Double:
+//                return GLES20.GL_DOUBLE;
+            default:
+                throw new RuntimeException("Unknown buffer format.");
+
+        }
+    }
+
+    public void updateBufferData(VertexBuffer vb) {
+
+        if (verboseLogging) {
+            logger.info("updateBufferData(" + vb + ")");
+        }
+
+        int bufId = vb.getId();
+        boolean created = false;
+        if (bufId == -1) {
+            // create buffer
+
+            if (verboseLogging) {
+                logger.info("GLES20.glGenBuffers(" + 1 + ", buffer)");
+            }
+
+            GLES20.glGenBuffers(1, intBuf1);
+            bufId = intBuf1.get(0);
+            vb.setId(bufId);
+            objManager.registerForCleanup(vb);
+
+            created = true;
+        }
+
+        // bind buffer
+        int target;
+        if (vb.getBufferType() == VertexBuffer.Type.Index) {
+            target = GLES20.GL_ELEMENT_ARRAY_BUFFER;
+
+            if (verboseLogging) {
+                logger.info("vb.getBufferType() == VertexBuffer.Type.Index");
+            }
+
+            if (context.boundElementArrayVBO != bufId) {
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")");
+                }
+
+                GLES20.glBindBuffer(target, bufId);
+                context.boundElementArrayVBO = bufId;
+            }
+        } else {
+            if (verboseLogging) {
+                logger.info("vb.getBufferType() != VertexBuffer.Type.Index");
+            }
+
+            target = GLES20.GL_ARRAY_BUFFER;
+
+            if (context.boundArrayVBO != bufId) {
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")");
+                }
+
+                GLES20.glBindBuffer(target, bufId);
+                context.boundArrayVBO = bufId;
+            }
+        }
+
+        int usage = convertUsage(vb.getUsage());
+        vb.getData().clear();
+
+        if (created || vb.hasDataSizeChanged()) {
+            // upload data based on format
+            int size = vb.getData().capacity() * vb.getFormat().getComponentSize();
+
+            switch (vb.getFormat()) {
+                case Byte:
+                case UnsignedByte:
+
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")");
+                    }
+
+                    GLES20.glBufferData(target, size, (ByteBuffer) vb.getData(), usage);
+                    break;
+                //            case Half:
+                case Short:
+                case UnsignedShort:
+
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")");
+                    }
+
+                    GLES20.glBufferData(target, size, (ShortBuffer) vb.getData(), usage);
+                    break;
+                case Int:
+                case UnsignedInt:
+
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")");
+                    }
+
+                    GLES20.glBufferData(target, size, (IntBuffer) vb.getData(), usage);
+                    break;
+                case Float:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")");
+                    }
+
+                    GLES20.glBufferData(target, size, (FloatBuffer) vb.getData(), usage);
+                    break;
+                case Double:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")");
+                    }
+
+                    GLES20.glBufferData(target, size, (DoubleBuffer) vb.getData(), usage);
+                    break;
+                default:
+                    throw new RuntimeException("Unknown buffer format.");
+            }
+        } else {
+            int size = vb.getData().capacity() * vb.getFormat().getComponentSize();
+
+            switch (vb.getFormat()) {
+                case Byte:
+                case UnsignedByte:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))");
+                    }
+
+                    GLES20.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData());
+                    break;
+                case Short:
+                case UnsignedShort:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))");
+                    }
+
+                    GLES20.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData());
+                    break;
+                case Int:
+                case UnsignedInt:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))");
+                    }
+
+                    GLES20.glBufferSubData(target, 0, size, (IntBuffer) vb.getData());
+                    break;
+                case Float:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))");
+                    }
+
+                    GLES20.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData());
+                    break;
+                case Double:
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))");
+                    }
+
+                    GLES20.glBufferSubData(target, 0, size, (DoubleBuffer) vb.getData());
+                    break;
+                default:
+                    throw new RuntimeException("Unknown buffer format.");
+            }
+        }
+//        }else{
+//            if (created || vb.hasDataSizeChanged()){
+//                glBufferData(target, vb.getData().capacity() * vb.getFormat().getComponentSize(), usage);
+//            }
+//
+//            ByteBuffer buf = glMapBuffer(target,
+//                                         GL_WRITE_ONLY,
+//                                         vb.getMappedData());
+//
+//            if (buf != vb.getMappedData()){
+//                buf = buf.order(ByteOrder.nativeOrder());
+//                vb.setMappedData(buf);
+//            }
+//
+//            buf.clear();
+//
+//            switch (vb.getFormat()){
+//                case Byte:
+//                case UnsignedByte:
+//                    buf.put( (ByteBuffer) vb.getData() );
+//                    break;
+//                case Short:
+//                case UnsignedShort:
+//                    buf.asShortBuffer().put( (ShortBuffer) vb.getData() );
+//                    break;
+//                case Int:
+//                case UnsignedInt:
+//                    buf.asIntBuffer().put( (IntBuffer) vb.getData() );
+//                    break;
+//                case Float:
+//                    buf.asFloatBuffer().put( (FloatBuffer) vb.getData() );
+//                    break;
+//                case Double:
+//                    break;
+//                default:
+//                    throw new RuntimeException("Unknown buffer format.");
+//            }
+//
+//            glUnmapBuffer(target);
+//        }
+
+        vb.clearUpdateNeeded();
+    }
+
+    public void deleteBuffer(VertexBuffer vb) {
+        int bufId = vb.getId();
+        if (bufId != -1) {
+            // delete buffer
+            intBuf1.put(0, bufId);
+            intBuf1.position(0).limit(1);
+            if (verboseLogging) {
+                logger.info("GLES20.glDeleteBuffers(1, buffer)");
+            }
+
+            GLES20.glDeleteBuffers(1, intBuf1);
+            vb.resetObject();
+        }
+    }
+
+    public void clearVertexAttribs() {
+        IDList attribList = context.attribIndexList;
+        for (int i = 0; i < attribList.oldLen; i++) {
+            int idx = attribList.oldList[i];
+
+            if (verboseLogging) {
+                logger.info("GLES20.glDisableVertexAttribArray(" + idx + ")");
+            }
+
+            GLES20.glDisableVertexAttribArray(idx);
+            context.boundAttribs[idx] = null;
+        }
+        context.attribIndexList.copyNewToOld();
+    }
+
+    public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
+        if (verboseLogging) {
+            logger.info("setVertexAttrib(" + vb + ", " + idb + ")");
+        }
+
+        if (vb.getBufferType() == VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
+        }
+
+        if (vb.isUpdateNeeded() && idb == null) {
+            updateBufferData(vb);
+        }
+
+        int programId = context.boundShaderProgram;
+        if (programId > 0) {
+            Attribute attrib = boundShader.getAttribute(vb.getBufferType());
+            int loc = attrib.getLocation();
+            if (loc == -1) {
+
+                if (verboseLogging) {
+                    logger.warning("location is invalid for attrib: [" + vb.getBufferType().name() + "]");
+                }
+
+                return; // not defined
+            }
+
+            if (loc == -2) {
+//                stringBuf.setLength(0);
+//                stringBuf.append("in").append(vb.getBufferType().name()).append('\0');
+//                updateNameBuffer();
+
+                String attributeName = "in" + vb.getBufferType().name();
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glGetAttribLocation(" + programId + ", " + attributeName + ")");
+                }
+
+                loc = GLES20.glGetAttribLocation(programId, attributeName);
+
+                // not really the name of it in the shader (inPosition\0) but
+                // the internal name of the enum (Position).
+                if (loc < 0) {
+                    attrib.setLocation(-1);
+
+                    if (verboseLogging) {
+                        logger.warning("attribute is invalid in shader: [" + vb.getBufferType().name() + "]");
+                    }
+
+                    return; // not available in shader.
+                } else {
+                    attrib.setLocation(loc);
+                }
+            }
+
+            VertexBuffer[] attribs = context.boundAttribs;
+            if (!context.attribIndexList.moveToNew(loc)) {
+                if (verboseLogging) {
+                    logger.info("GLES20.glEnableVertexAttribArray(" + loc + ")");
+                }
+
+                GLES20.glEnableVertexAttribArray(loc);
+                //System.out.println("Enabled ATTRIB IDX: "+loc);
+            }
+            if (attribs[loc] != vb) {
+                // NOTE: Use id from interleaved buffer if specified
+                int bufId = idb != null ? idb.getId() : vb.getId();
+                assert bufId != -1;
+
+                if (bufId == -1) {
+                    logger.warning("invalid buffer id");
+                }
+
+                if (context.boundArrayVBO != bufId) {
+                    if (verboseLogging) {
+                        logger.info("GLES20.glBindBuffer(" + GLES20.GL_ARRAY_BUFFER + ", " + bufId + ")");
+                    }
+                    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufId);
+                    context.boundArrayVBO = bufId;
+                }
+
+                vb.getData().clear();
+
+                if (verboseLogging) {
+                    logger.info("GLES20.glVertexAttribPointer("
+                            + "location=" + loc + ", "
+                            + "numComponents=" + vb.getNumComponents() + ", "
+                            + "format=" + vb.getFormat() + ", "
+                            + "isNormalized=" + vb.isNormalized() + ", "
+                            + "stride=" + vb.getStride() + ", "
+                            + "data.capacity=" + vb.getData().capacity() + ")");
+                }
+
+                Android22Workaround.glVertexAttribPointer(loc,
+                                    vb.getNumComponents(),
+                                    convertFormat(vb.getFormat()),
+                                    vb.isNormalized(),
+                                    vb.getStride(),
+                                    0);
+
+                attribs[loc] = vb;
+            }
+        } else {
+            throw new IllegalStateException("Cannot render mesh without shader bound");
+        }
+    }
+
+    public void setVertexAttrib(VertexBuffer vb) {
+        setVertexAttrib(vb, null);
+    }
+
+    public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
+        /*        if (count > 1){
+        ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0,
+        vertCount, count);
+        }else{*/
+        if (verboseLogging) {
+            logger.info("GLES20.glDrawArrays(" + vertCount + ")");
+        }
+
+        GLES20.glDrawArrays(convertElementMode(mode), 0, vertCount);
+        /*
+        }*/
+    }
+
+    public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) {
+
+        if (verboseLogging) {
+            logger.info("drawTriangleList(" + count + ")");
+        }
+
+        if (indexBuf.getBufferType() != VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Only index buffers are allowed as triangle lists.");
+        }
+
+        if (indexBuf.isUpdateNeeded()) {
+            if (verboseLogging) {
+                logger.info("updateBufferData for indexBuf.");
+            }
+            updateBufferData(indexBuf);
+        }
+
+        int bufId = indexBuf.getId();
+        assert bufId != -1;
+
+        if (bufId == -1) {
+            logger.info("invalid buffer id!");
+        }
+
+        if (context.boundElementArrayVBO != bufId) {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, {0})", bufId);
+            }
+
+            GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufId);
+            context.boundElementArrayVBO = bufId;
+        }
+
+        int vertCount = mesh.getVertexCount();
+        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
+
+        Buffer indexData = indexBuf.getData();
+
+        if (mesh.getMode() == Mode.Hybrid) {
+            int[] modeStart = mesh.getModeStart();
+            int[] elementLengths = mesh.getElementLengths();
+
+            int elMode = convertElementMode(Mode.Triangles);
+            int fmt = convertFormat(indexBuf.getFormat());
+            int elSize = indexBuf.getFormat().getComponentSize();
+            int listStart = modeStart[0];
+            int stripStart = modeStart[1];
+            int fanStart = modeStart[2];
+            int curOffset = 0;
+            for (int i = 0; i < elementLengths.length; i++) {
+                if (i == stripStart) {
+                    elMode = convertElementMode(Mode.TriangleStrip);
+                } else if (i == fanStart) {
+                    elMode = convertElementMode(Mode.TriangleStrip);
+                }
+                int elementLength = elementLengths[i];
+
+                if (useInstancing) {
+                    //ARBDrawInstanced.
+                    throw new IllegalArgumentException("instancing is not supported.");
+                    /*
+                    GLES20.glDrawElementsInstancedARB(elMode,
+                    elementLength,
+                    fmt,
+                    curOffset,
+                    count);
+                     */
+                } else {
+                    indexBuf.getData().position(curOffset);
+                    if (verboseLogging) {
+                        logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset});
+                    }
+
+                    GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData());
+                    /*
+                    glDrawRangeElements(elMode,
+                    0,
+                    vertCount,
+                    elementLength,
+                    fmt,
+                    curOffset);
+                     */
+                }
+
+                curOffset += elementLength * elSize;
+            }
+        } else {
+            if (useInstancing) {
+                throw new IllegalArgumentException("instancing is not supported.");
+                //ARBDrawInstanced.
+/*
+                GLES20.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
+                indexBuf.getData().capacity(),
+                convertFormat(indexBuf.getFormat()),
+                0,
+                count);
+                 */
+            } else {
+                indexData.clear();
+
+                if (verboseLogging) {
+                    logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount});
+                }
+
+                GLES11.glDrawElements(
+                        convertElementMode(mesh.getMode()),
+                        indexBuf.getData().capacity(),
+                        convertFormat(indexBuf.getFormat()),
+                        0);
+            }
+        }
+    }
+
+    /*********************************************************************\
+    |* Render Calls                                                      *|
+    \*********************************************************************/
+    public int convertElementMode(Mesh.Mode mode) {
+        switch (mode) {
+            case Points:
+                return GLES20.GL_POINTS;
+            case Lines:
+                return GLES20.GL_LINES;
+            case LineLoop:
+                return GLES20.GL_LINE_LOOP;
+            case LineStrip:
+                return GLES20.GL_LINE_STRIP;
+            case Triangles:
+                return GLES20.GL_TRIANGLES;
+            case TriangleFan:
+                return GLES20.GL_TRIANGLE_FAN;
+            case TriangleStrip:
+                return GLES20.GL_TRIANGLE_STRIP;
+            default:
+                throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode);
+        }
+    }
+
+    public void updateVertexArray(Mesh mesh) {
+        logger.log(Level.INFO, "updateVertexArray({0})", mesh);
+        int id = mesh.getId();
+        /*
+        if (id == -1){
+        IntBuffer temp = intBuf1;
+        //      ARBVertexArrayObject.glGenVertexArrays(temp);
+        GLES20.glGenVertexArrays(temp);
+        id = temp.get(0);
+        mesh.setId(id);
+        }
+        
+        if (context.boundVertexArray != id){
+        //     ARBVertexArrayObject.glBindVertexArray(id);
+        GLES20.glBindVertexArray(id);
+        context.boundVertexArray = id;
+        }
+         */
+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
+        if (interleavedData != null && interleavedData.isUpdateNeeded()) {
+            updateBufferData(interleavedData);
+        }
+
+      
+        for (VertexBuffer vb : mesh.getBufferList().getArray()){         
+      
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index) {
+                continue;
+            }
+
+            if (vb.getStride() == 0) {
+                // not interleaved
+                setVertexAttrib(vb);
+            } else {
+                // interleaved
+                setVertexAttrib(vb, interleavedData);
+            }
+        }
+    }
+
+    /**
+     * renderMeshVertexArray renders a mesh using vertex arrays
+     * @param mesh
+     * @param lod
+     * @param count
+     */
+    private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
+        if (verboseLogging) {
+            logger.info("renderMeshVertexArray");
+        }
+
+      //  IntMap<VertexBuffer> buffers = mesh.getBuffers();
+         for (VertexBuffer vb : mesh.getBufferList().getArray()){         
+
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index) {
+                continue;
+            }
+
+            if (vb.getStride() == 0) {
+                // not interleaved
+                setVertexAttrib_Array(vb);
+            } else {
+                // interleaved
+                VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
+                setVertexAttrib_Array(vb, interleavedData);
+            }
+        }
+
+        VertexBuffer indices = null;
+        if (mesh.getNumLodLevels() > 0) {
+            indices = mesh.getLodLevel(lod);
+        } else {
+            indices = mesh.getBuffer(Type.Index);//buffers.get(Type.Index.ordinal());
+        }
+        if (indices != null) {
+            drawTriangleList_Array(indices, mesh, count);
+        } else {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glDrawArrays({0}, {1}, {2})", 
+                        new Object[]{mesh.getMode(), 0, mesh.getVertexCount()});
+            }
+
+            GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount());
+        }
+        clearVertexAttribs();
+        clearTextureUnits();
+    }
+
+    private void renderMeshDefault(Mesh mesh, int lod, int count) {
+        if (verboseLogging) {
+            logger.log(Level.INFO, "renderMeshDefault({0}, {1}, {2})", 
+                    new Object[]{mesh, lod, count});
+        }
+        VertexBuffer indices = null;
+
+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
+        if (interleavedData != null && interleavedData.isUpdateNeeded()) {
+            updateBufferData(interleavedData);
+        }
+
+        //IntMap<VertexBuffer> buffers = mesh.getBuffers();     ;
+        if (mesh.getNumLodLevels() > 0) {
+            indices = mesh.getLodLevel(lod);
+        } else {
+            indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal());
+        }
+        for (VertexBuffer vb : mesh.getBufferList().getArray()){         
+         
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index) {
+                continue;
+            }
+
+            if (vb.getStride() == 0) {
+                // not interleaved
+                setVertexAttrib(vb);
+            } else {
+                // interleaved
+                setVertexAttrib(vb, interleavedData);
+            }
+        }
+        if (indices != null) {
+            drawTriangleList(indices, mesh, count);
+        } else {
+//            throw new UnsupportedOperationException("Cannot render without index buffer");
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glDrawArrays({0}, 0, {1})", 
+                        new Object[]{convertElementMode(mesh.getMode()), mesh.getVertexCount()});
+            }
+
+            GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount());
+        }
+        clearVertexAttribs();
+        clearTextureUnits();
+    }
+
+    public void renderMesh(Mesh mesh, int lod, int count) {
+        if (context.pointSize != mesh.getPointSize()) {
+
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES10.glPointSize({0})", mesh.getPointSize());
+            }
+
+            GLES10.glPointSize(mesh.getPointSize());
+            context.pointSize = mesh.getPointSize();
+        }
+        if (context.lineWidth != mesh.getLineWidth()) {
+
+            if (verboseLogging) {
+                logger.log(Level.INFO, "GLES20.glLineWidth({0})", mesh.getLineWidth());
+            }
+
+            GLES20.glLineWidth(mesh.getLineWidth());
+            context.lineWidth = mesh.getLineWidth();
+        }
+
+        statistics.onMeshDrawn(mesh, lod);
+//        if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
+//            renderMeshVertexArray(mesh, lod, count);
+//        }else{
+
+        if (useVBO) {
+            if (verboseLogging) {
+                logger.info("RENDERING A MESH USING VertexBufferObject");
+            }
+
+            renderMeshDefault(mesh, lod, count);
+        } else {
+            if (verboseLogging) {
+                logger.info("RENDERING A MESH USING VertexArray");
+            }
+
+            renderMeshVertexArray(mesh, lod, count);
+        }
+
+//        }
+    }
+
+    /**
+     * drawTriangleList_Array uses Vertex Array
+     * @param indexBuf
+     * @param mesh
+     * @param count
+     */
+    public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) {
+        if (verboseLogging) {
+            logger.log(Level.INFO, "drawTriangleList_Array(Count = {0})", count);
+        }
+
+        if (indexBuf.getBufferType() != VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Only index buffers are allowed as triangle lists.");
+        }
+
+        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
+        if (useInstancing) {
+            throw new IllegalArgumentException("Caps.MeshInstancing is not supported.");
+        }
+
+        int vertCount = mesh.getVertexCount();
+        Buffer indexData = indexBuf.getData();
+        indexData.clear();
+
+        if (mesh.getMode() == Mode.Hybrid) {
+            int[] modeStart = mesh.getModeStart();
+            int[] elementLengths = mesh.getElementLengths();
+
+            int elMode = convertElementMode(Mode.Triangles);
+            int fmt = convertFormat(indexBuf.getFormat());
+            int elSize = indexBuf.getFormat().getComponentSize();
+            int listStart = modeStart[0];
+            int stripStart = modeStart[1];
+            int fanStart = modeStart[2];
+            int curOffset = 0;
+            for (int i = 0; i < elementLengths.length; i++) {
+                if (i == stripStart) {
+                    elMode = convertElementMode(Mode.TriangleStrip);
+                } else if (i == fanStart) {
+                    elMode = convertElementMode(Mode.TriangleStrip);
+                }
+                int elementLength = elementLengths[i];
+
+                indexBuf.getData().position(curOffset);
+                if (verboseLogging) {
+                    logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset});
+                }
+
+                GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData());
+
+                curOffset += elementLength * elSize;
+            }
+        } else {
+            if (verboseLogging) {
+                logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount});
+            }
+
+            GLES20.glDrawElements(
+                    convertElementMode(mesh.getMode()),
+                    indexBuf.getData().capacity(),
+                    convertFormat(indexBuf.getFormat()),
+                    indexBuf.getData());
+        }
+    }
+
+    /**
+     * setVertexAttrib_Array uses Vertex Array
+     * @param vb
+     * @param idb
+     */
+    public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer idb) {
+        if (verboseLogging) {
+            logger.log(Level.INFO, "setVertexAttrib_Array({0}, {1})", new Object[]{vb, idb});
+        }
+
+        if (vb.getBufferType() == VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
+        }
+
+        // Get shader
+        int programId = context.boundShaderProgram;
+        if (programId > 0) {
+            VertexBuffer[] attribs = context.boundAttribs;
+
+            Attribute attrib = boundShader.getAttribute(vb.getBufferType());
+            int loc = attrib.getLocation();
+            if (loc == -1) {
+                //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]");
+                if (verboseLogging) {
+                    logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name());
+                }
+                return;
+            } else if (loc == -2) {
+                String attributeName = "in" + vb.getBufferType().name();
+
+                if (verboseLogging) {
+                    logger.log(Level.INFO, "GLES20.glGetAttribLocation({0}, {1})", new Object[]{programId, attributeName});
+                }
+
+                loc = GLES20.glGetAttribLocation(programId, attributeName);
+                if (loc < 0) {
+                    attrib.setLocation(-1);
+                    if (verboseLogging) {
+                        logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name());
+                    }
+                    return; // not available in shader.
+                } else {
+                    attrib.setLocation(loc);
+                }
+
+            }  // if (loc == -2)
+
+            if ((attribs[loc] != vb) || vb.isUpdateNeeded()) {
+                // NOTE: Use data from interleaved buffer if specified
+                VertexBuffer avb = idb != null ? idb : vb;
+                avb.getData().clear();
+                avb.getData().position(vb.getOffset());
+
+                if (verboseLogging) {
+                    logger.log(Level.INFO,
+                            "GLES20.glVertexAttribPointer(" + 
+                            "location={0}, " +
+                            "numComponents={1}, " +
+                            "format={2}, " + 
+                            "isNormalized={3}, " + 
+                            "stride={4}, " + 
+                            "data.capacity={5})", 
+                            new Object[]{loc, vb.getNumComponents(), 
+                                         vb.getFormat(), 
+                                         vb.isNormalized(), 
+                                         vb.getStride(), 
+                                         avb.getData().capacity()});
+                }
+
+
+                // Upload attribute data
+                GLES20.glVertexAttribPointer(loc,
+                        vb.getNumComponents(),
+                        convertFormat(vb.getFormat()),
+                        vb.isNormalized(),
+                        vb.getStride(),
+                        avb.getData());
+                checkGLError();
+
+                GLES20.glEnableVertexAttribArray(loc);
+
+                attribs[loc] = vb;
+            } // if (attribs[loc] != vb)
+        } else {
+            throw new IllegalStateException("Cannot render mesh without shader bound");
+        }
+    }
+
+    /**
+     * setVertexAttrib_Array uses Vertex Array
+     * @param vb
+     */
+    public void setVertexAttrib_Array(VertexBuffer vb) {
+        setVertexAttrib_Array(vb, null);
+    }
+
+    public void setAlphaToCoverage(boolean value) {
+        if (value) {
+            GLES20.glEnable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE);
+        } else {
+            GLES20.glDisable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE);
+        }
+    }
+
+    @Override
+    public void invalidateState() {
+        context.reset();
+        boundShader = null;
+        lastFb = null;
+    }
+}
diff --git a/engine/src/android/com/jme3/renderer/android/TextureUtil.java b/engine/src/android/com/jme3/renderer/android/TextureUtil.java
new file mode 100644
index 0000000..53b96b4
--- /dev/null
+++ b/engine/src/android/com/jme3/renderer/android/TextureUtil.java
@@ -0,0 +1,297 @@
+package com.jme3.renderer.android;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import com.jme3.asset.AndroidImageInfo;
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.nio.ByteBuffer;
+import javax.microedition.khronos.opengles.GL10;
+
+public class TextureUtil {
+
+    public static int convertTextureFormat(Format fmt){
+        switch (fmt){
+            case Alpha16:
+            case Alpha8:
+                return GL10.GL_ALPHA;
+            case Luminance8Alpha8:
+            case Luminance16Alpha16:
+                return GL10.GL_LUMINANCE_ALPHA;
+            case Luminance8:
+            case Luminance16:
+                return GL10.GL_LUMINANCE;
+            case RGB10:
+            case RGB16:
+            case BGR8:
+            case RGB8:
+            case RGB565:
+                return GL10.GL_RGB;
+            case RGB5A1:
+            case RGBA16:
+            case RGBA8:
+                return GL10.GL_RGBA;
+                
+            case Depth:
+                return GLES20.GL_DEPTH_COMPONENT;
+            case Depth16:
+                return GLES20.GL_DEPTH_COMPONENT16;
+            case Depth24:
+            case Depth32:
+            case Depth32F:
+                throw new UnsupportedOperationException("Unsupported depth format: " + fmt);   
+                
+            case DXT1A:
+                throw new UnsupportedOperationException("Unsupported format: " + fmt);
+            default:
+                throw new UnsupportedOperationException("Unrecognized format: " + fmt);
+        }
+    }
+
+    private static void buildMipmap(Bitmap bitmap) {
+        int level = 0;
+        int height = bitmap.getHeight();
+        int width = bitmap.getWidth();
+
+        while (height >= 1 || width >= 1) {
+            //First of all, generate the texture from our bitmap and set it to the according level
+            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0);
+
+            if (height == 1 || width == 1) {
+                break;
+            }
+
+            //Increase the mipmap level
+            level++;
+
+            height /= 2;
+            width /= 2;
+            Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);
+
+            bitmap.recycle();
+            bitmap = bitmap2;
+        }
+    }
+
+    /**
+     * <code>uploadTextureBitmap</code> uploads a native android bitmap
+     * @param target
+     * @param bitmap
+     * @param generateMips
+     * @param powerOf2
+     */
+    public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean generateMips, boolean powerOf2)
+    {
+        if (!powerOf2)
+        {
+            int width = bitmap.getWidth();
+            int height = bitmap.getHeight();
+            if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height))
+            {
+                // scale to power of two
+                width = FastMath.nearestPowerOfTwo(width);
+                height = FastMath.nearestPowerOfTwo(height);
+                Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);
+                bitmap.recycle();
+                bitmap = bitmap2;
+            }
+        }
+
+        if (generateMips)
+        {
+            buildMipmap(bitmap);
+        }
+        else
+        {
+            GLUtils.texImage2D(target, 0, bitmap, 0);
+            //bitmap.recycle();
+        }
+    }
+
+    public static void uploadTexture(
+                                     Image img,
+                                     int target,
+                                     int index,
+                                     int border,
+                                     boolean tdc,
+                                     boolean generateMips,
+                                     boolean powerOf2){
+
+        if (img.getEfficentData() instanceof AndroidImageInfo){
+            // If image was loaded from asset manager, use fast path
+            AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
+            uploadTextureBitmap(target, imageInfo.getBitmap(), generateMips, powerOf2);
+            return;
+        }
+
+        // Otherwise upload image directly. 
+        // Prefer to only use power of 2 textures here to avoid errors.
+        
+        Image.Format fmt = img.getFormat();
+        ByteBuffer data;
+        if (index >= 0 || img.getData() != null && img.getData().size() > 0){
+            data = img.getData(index);
+        }else{
+            data = null;
+        }
+
+        int width = img.getWidth();
+        int height = img.getHeight();
+        int depth = img.getDepth();
+
+        boolean compress = false;
+        int internalFormat = -1;
+        int format = -1;
+        int dataType = -1;
+
+        switch (fmt){
+            case Alpha16:
+            case Alpha8:
+                format = GLES20.GL_ALPHA;
+                dataType = GLES20.GL_UNSIGNED_BYTE;                
+                break;
+            case Luminance8:
+                format = GLES20.GL_LUMINANCE;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case Luminance8Alpha8:
+                format = GLES20.GL_LUMINANCE_ALPHA;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case Luminance16Alpha16:
+                format = GLES20.GL_LUMINANCE_ALPHA;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case Luminance16:
+                format = GLES20.GL_LUMINANCE;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case RGB565:
+                format = GLES20.GL_RGB;
+                internalFormat = GLES20.GL_RGB565;
+                dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5;
+                break;
+            case ARGB4444:
+                format = GLES20.GL_RGBA;
+                dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4;
+                break;
+            case RGB10:
+                format = GLES20.GL_RGB;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case RGB16:
+                format = GLES20.GL_RGB;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case RGB5A1:
+                format = GLES20.GL_RGBA;
+                internalFormat = GLES20.GL_RGB5_A1;
+                dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1;
+                break;
+            case RGB8:
+                format = GLES20.GL_RGB;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case BGR8:
+                format = GLES20.GL_RGB;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case RGBA16:
+                format = GLES20.GL_RGBA;
+                internalFormat = GLES20.GL_RGBA4;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case RGBA8:
+                format = GLES20.GL_RGBA;                
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case DXT1A:
+                format = GLES20.GL_COMPRESSED_TEXTURE_FORMATS;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+            case Depth:
+                format = GLES20.GL_DEPTH_COMPONENT;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case Depth16:
+                format = GLES20.GL_DEPTH_COMPONENT;
+                internalFormat = GLES20.GL_DEPTH_COMPONENT16;
+                dataType = GLES20.GL_UNSIGNED_BYTE;
+                break;
+            case Depth24:
+            case Depth32:
+            case Depth32F:
+                throw new UnsupportedOperationException("Unsupported depth format: " + fmt);                
+            default:
+                throw new UnsupportedOperationException("Unrecognized format: " + fmt);
+        }
+        
+        if (internalFormat == -1)
+        {
+            internalFormat = format;
+        }
+
+        if (data != null)
+            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
+
+        int[] mipSizes = img.getMipMapSizes();
+        int pos = 0;
+        if (mipSizes == null){
+            if (data != null)
+                mipSizes = new int[]{ data.capacity() };
+            else
+                mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
+        }
+
+        // XXX: might want to change that when support
+        // of more than paletted compressions is added..
+        if (compress){
+            data.clear();
+            GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D,
+                                      1 - mipSizes.length,
+                                      format,
+                                      width,
+                                      height,
+                                      0,
+                                      data.capacity(),
+                                      data);
+            return;
+        }
+
+        for (int i = 0; i < mipSizes.length; i++){
+            int mipWidth =  Math.max(1, width  >> i);
+            int mipHeight = Math.max(1, height >> i);
+            int mipDepth =  Math.max(1, depth  >> i);
+
+            if (data != null){
+                data.position(pos);
+                data.limit(pos + mipSizes[i]);
+            }
+
+            if (compress && data != null){
+                GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D,
+                                          i,
+                                          format,
+                                          mipWidth,
+                                          mipHeight,
+                                          0,
+                                          data.remaining(),
+                                          data);
+            }else{
+                GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,
+                                i,
+                                internalFormat,
+                                mipWidth,
+                                mipHeight,
+                                0,
+                                format,
+                                dataType,
+                                data);
+            }
+
+            pos += mipSizes[i];
+        }
+    }
+
+}
diff --git a/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java b/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java
new file mode 100644
index 0000000..e55fa55
--- /dev/null
+++ b/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java
@@ -0,0 +1,362 @@
+package com.jme3.system.android;
+
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView.EGLConfigChooser;
+import java.util.logging.Logger;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/**
+ * AndroidConfigChooser is used to determine the best suited EGL Config
+ * @author larynx
+ *
+ */
+public class AndroidConfigChooser implements EGLConfigChooser 
+{
+    private static final Logger logger = Logger.getLogger(AndroidConfigChooser.class.getName());
+    
+    protected int clientOpenGLESVersion = 0;
+    protected EGLConfig bestConfig = null;
+    protected EGLConfig fastestConfig = null;
+    protected EGLConfig choosenConfig = null;
+    protected ConfigType type;
+    protected int pixelFormat;
+    
+    protected boolean verbose = false;
+    
+    private final static int EGL_OPENGL_ES2_BIT = 4;
+
+    public enum ConfigType 
+    {
+        /**
+         * RGB565, 0 alpha, 16 depth, 0 stencil
+         */
+        FASTEST,
+        /**
+         * RGB???, 0 alpha, >=16 depth, 0 stencil
+         */
+        BEST,
+        /**
+         * Turn off config chooser and use hardcoded
+         * setEGLContextClientVersion(2);
+         * setEGLConfigChooser(5, 6, 5, 0, 16, 0);
+         */
+        LEGACY
+    }
+    
+    public AndroidConfigChooser(ConfigType type, boolean verbose)
+    {
+        this.type = type;
+        this.verbose = verbose;
+    }
+        
+    /**
+     * Gets called by the GLSurfaceView class to return the best config
+     */    
+    @Override
+    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display)
+    {
+        logger.info("GLSurfaceView asks for egl config, returning: ");
+        logEGLConfig(choosenConfig, display, egl);
+        return choosenConfig;
+    }
+    
+    /**
+     * findConfig is used to locate the best config and init the chooser with
+     * @param egl
+     * @param display
+     * @return true if successfull, false if no config was found
+     */
+    public boolean findConfig(EGL10 egl, EGLDisplay display)
+    {           
+        
+        if (type == ConfigType.BEST)
+        {        	
+        	ComponentSizeChooser compChooser = new ComponentSizeChooser(8, 8, 8, 8, 32, 0);
+        	choosenConfig = compChooser.chooseConfig(egl, display);
+
+        	if (choosenConfig == null)
+        	{
+                compChooser = new ComponentSizeChooser(8, 8, 8, 0, 32, 0);
+                choosenConfig = compChooser.chooseConfig(egl, display);
+                if (choosenConfig == null)
+                {
+                    compChooser = new ComponentSizeChooser(8, 8, 8, 8, 16, 0);
+                    choosenConfig = compChooser.chooseConfig(egl, display);
+                    if (choosenConfig == null)
+                    {
+                        compChooser = new ComponentSizeChooser(8, 8, 8, 0, 16, 0);
+                        choosenConfig = compChooser.chooseConfig(egl, display);
+                    }
+                }
+        	}
+        	
+            logger.info("JME3 using best EGL configuration available here: ");
+        }
+        else
+        {
+        	ComponentSizeChooser compChooser = new ComponentSizeChooser(5, 6, 5, 0, 16, 0);
+        	choosenConfig = compChooser.chooseConfig(egl, display);
+            logger.info("JME3 using fastest EGL configuration available here: ");
+        }
+        
+        if (choosenConfig != null)
+        {
+            logger.info("JME3 using choosen config: "); 
+            logEGLConfig(choosenConfig, display, egl);               
+            pixelFormat = getPixelFormat(choosenConfig, display, egl);
+            clientOpenGLESVersion = getOpenGLVersion(choosenConfig, display, egl);
+            return true;
+        }
+        else
+        {
+            logger.severe("###ERROR### Unable to get a valid OpenGL ES 2.0 config, nether Fastest nor Best found! Bug. Please report this.");
+            clientOpenGLESVersion = 1;
+            pixelFormat = PixelFormat.UNKNOWN;
+            return false;
+        }        
+    }
+    
+    
+    private int getPixelFormat(EGLConfig conf, EGLDisplay display, EGL10 egl)
+    {
+        int[] value = new int[1];
+        int result = PixelFormat.RGB_565;                
+
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value);
+        if (value[0] == 8)
+        {
+            result = PixelFormat.RGBA_8888;
+            /*
+            egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value);
+            if (value[0] == 8)
+            {
+                result = PixelFormat.RGBA_8888;
+            }
+            else
+            {
+                result = PixelFormat.RGB_888;
+            }*/
+        }
+        
+        if (verbose)
+        {
+            logger.info("Using PixelFormat " + result);                            
+        }
+    
+        //return result; TODO Test pixelformat
+        return PixelFormat.TRANSPARENT;
+    }
+    
+    private int getOpenGLVersion(EGLConfig conf, EGLDisplay display, EGL10 egl)
+    {
+        int[] value = new int[1];
+        int result = 1;
+                        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value);
+        // Check if conf is OpenGL ES 2.0
+        if ((value[0] & EGL_OPENGL_ES2_BIT) != 0)
+        {
+            result = 2;
+        }
+
+        return result;                    
+    }
+    
+    /**
+     * log output with egl config details
+     * @param conf
+     * @param display
+     * @param egl
+     */
+    public void logEGLConfig(EGLConfig conf, EGLDisplay display, EGL10 egl)
+    {
+        int[] value = new int[1];
+
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value);
+        logger.info(String.format("EGL_RED_SIZE  = %d", value[0] ) );
+        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_GREEN_SIZE, value);
+        logger.info(String.format("EGL_GREEN_SIZE  = %d", value[0] ) );
+        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_BLUE_SIZE, value);
+        logger.info(String.format("EGL_BLUE_SIZE  = %d", value[0] ) );
+        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value);
+        logger.info(String.format("EGL_ALPHA_SIZE  = %d", value[0] ) );
+        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_DEPTH_SIZE, value);
+        logger.info(String.format("EGL_DEPTH_SIZE  = %d", value[0] ) );
+                
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_STENCIL_SIZE, value);
+        logger.info(String.format("EGL_STENCIL_SIZE  = %d", value[0] ) );
+
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value);
+        logger.info(String.format("EGL_RENDERABLE_TYPE  = %d", value[0] ) );
+        
+        egl.eglGetConfigAttrib(display, conf, EGL10.EGL_SURFACE_TYPE, value);
+        logger.info(String.format("EGL_SURFACE_TYPE  = %d", value[0] ) );               
+    }
+        
+    public int getClientOpenGLESVersion() 
+    {
+        return clientOpenGLESVersion;
+    }
+
+    public void setClientOpenGLESVersion(int clientOpenGLESVersion) 
+    {
+        this.clientOpenGLESVersion = clientOpenGLESVersion;
+    }
+    
+    public int getPixelFormat() 
+    {
+        return pixelFormat;
+    }
+    
+    
+    
+    private abstract class BaseConfigChooser implements EGLConfigChooser 
+    {
+        private boolean bClientOpenGLESVersionSet;
+        
+		public BaseConfigChooser(int[] configSpec) 
+		{
+		    bClientOpenGLESVersionSet = false;
+		    mConfigSpec = filterConfigSpec(configSpec);
+		}
+		
+		public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) 
+		{
+		    int[] num_config = new int[1];
+		    if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
+		            num_config)) {
+		        throw new IllegalArgumentException("eglChooseConfig failed");
+		    }
+		
+		    int numConfigs = num_config[0];
+		
+		    if (numConfigs <= 0) 
+		    {
+		        //throw new IllegalArgumentException("No configs match configSpec");
+		        
+		        return null;
+		    }
+		
+		    EGLConfig[] configs = new EGLConfig[numConfigs];
+		    if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+		            num_config)) {
+		        throw new IllegalArgumentException("eglChooseConfig#2 failed");
+		    }
+		    EGLConfig config = chooseConfig(egl, display, configs);
+		    //if (config == null) {
+		    //    throw new IllegalArgumentException("No config chosen");
+		    //}
+		    return config;
+		}
+		
+		abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+		        EGLConfig[] configs);
+		
+		protected int[] mConfigSpec;
+		
+		private int[] filterConfigSpec(int[] configSpec) 
+		{
+		    if (bClientOpenGLESVersionSet == true) {
+		        return configSpec;
+		    }
+		    /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
+		     * And we know the configSpec is well formed.
+		     */
+		    int len = configSpec.length;
+		    int[] newConfigSpec = new int[len + 2];
+		    System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
+		    newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
+		    newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
+		    newConfigSpec[len+1] = EGL10.EGL_NONE;
+		    
+		    bClientOpenGLESVersionSet = true;
+		    
+		    return newConfigSpec;
+		}
+    }
+    
+    /**
+     * Choose a configuration with exactly the specified r,g,b,a sizes,
+     * and at least the specified depth and stencil sizes.
+     */
+    private class ComponentSizeChooser extends BaseConfigChooser 
+    {
+        public ComponentSizeChooser(int redSize, int greenSize, int blueSize,
+                int alphaSize, int depthSize, int stencilSize) 
+        {
+            super(new int[] {
+                    EGL10.EGL_RED_SIZE, redSize,
+                    EGL10.EGL_GREEN_SIZE, greenSize,
+                    EGL10.EGL_BLUE_SIZE, blueSize,
+                    EGL10.EGL_ALPHA_SIZE, alphaSize,
+                    EGL10.EGL_DEPTH_SIZE, depthSize,
+                    EGL10.EGL_STENCIL_SIZE, stencilSize,
+                    EGL10.EGL_NONE});
+            mValue = new int[1];
+            mRedSize = redSize;
+            mGreenSize = greenSize;
+            mBlueSize = blueSize;
+            mAlphaSize = alphaSize;
+            mDepthSize = depthSize;
+            mStencilSize = stencilSize;
+       }
+
+        @Override
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) 
+        {
+            for (EGLConfig config : configs) 
+            {
+                int d = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_DEPTH_SIZE, 0);
+                int s = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_STENCIL_SIZE, 0);
+                if ((d >= mDepthSize) && (s >= mStencilSize)) 
+                {
+                    int r = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config,
+                             EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config,
+                              EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_ALPHA_SIZE, 0);
+                    if ((r == mRedSize) && (g == mGreenSize)
+                            && (b == mBlueSize) && (a == mAlphaSize)) {
+                        return config;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+                EGLConfig config, int attribute, int defaultValue) 
+        {
+
+            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) 
+            {
+                return mValue[0];
+            }
+            return defaultValue;
+        }
+
+        private int[] mValue;
+        // Subclasses can adjust these values:
+        protected int mRedSize;
+        protected int mGreenSize;
+        protected int mBlueSize;
+        protected int mAlphaSize;
+        protected int mDepthSize;
+        protected int mStencilSize;
+    }
+    
+    
+    
+    
+}
diff --git a/engine/src/android/com/jme3/system/android/AndroidTimer.java b/engine/src/android/com/jme3/system/android/AndroidTimer.java
new file mode 100644
index 0000000..4cabc33
--- /dev/null
+++ b/engine/src/android/com/jme3/system/android/AndroidTimer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2003-2009 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
+ *   may be used to endorse or promote products derived from this software 
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.android;
+
+import com.jme3.system.Timer;
+
+/**
+ * <code>AndroidTimer</code> is a System.nanoTime implementation of <code>Timer</code>.
+ */
+public class AndroidTimer extends Timer {
+    
+    //private static final long TIMER_RESOLUTION = 1000L;
+    //private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L;
+    private static final long TIMER_RESOLUTION = 1000000000L;
+    private static final float INVERSE_TIMER_RESOLUTION = 1f/1000000000L;
+    
+    private long startTime;
+    private long previousTime;
+    private float tpf;
+    private float fps;
+    
+    public AndroidTimer() {
+        //startTime = System.currentTimeMillis();
+        startTime = System.nanoTime();
+    }
+
+    /**
+     * Returns the time in seconds. The timer starts
+     * at 0.0 seconds.
+     *
+     * @return the current time in seconds
+     */
+    @Override
+    public float getTimeInSeconds() {
+        return getTime() * INVERSE_TIMER_RESOLUTION;
+    }
+
+    public long getTime() {
+        //return System.currentTimeMillis() - startTime;
+        return System.nanoTime() - startTime;
+    }
+
+    public long getResolution() {
+        return TIMER_RESOLUTION;
+    }
+
+    public float getFrameRate() {
+        return fps;
+    }
+
+    public float getTimePerFrame() {
+        return tpf;
+    }
+
+    public void update() {
+        tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION);
+        fps = 1.0f / tpf;
+        previousTime = getTime();
+    }
+    
+    public void reset() {
+        //startTime = System.currentTimeMillis();
+        startTime = System.nanoTime();
+        previousTime = getTime();
+    }
+}
diff --git a/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java
new file mode 100644
index 0000000..6550101
--- /dev/null
+++ b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java
@@ -0,0 +1,134 @@
+package com.jme3.system.android;

+

+import android.app.Activity;

+import android.content.res.Resources;

+import android.os.Environment;

+import com.jme3.asset.AndroidAssetManager;

+import com.jme3.asset.AssetManager;

+import com.jme3.audio.AudioRenderer;

+import com.jme3.audio.android.AndroidAudioRenderer;

+import com.jme3.system.AppSettings;

+import com.jme3.system.JmeContext;

+import com.jme3.system.JmeContext.Type;

+import com.jme3.system.JmeSystemDelegate;

+import com.jme3.system.Platform;

+import com.jme3.util.AndroidLogHandler;

+import com.jme3.util.JmeFormatter;

+import java.io.File;

+import java.net.URL;

+import java.util.logging.Handler;

+import java.util.logging.Level;

+

+public class JmeAndroidSystem extends JmeSystemDelegate {

+

+    private static Resources res;

+    private static Activity activity;

+

+    @Override

+    public AssetManager newAssetManager(URL configFile) {

+        logger.log(Level.INFO, "newAssetManager({0})", configFile);

+        return new AndroidAssetManager(configFile);

+    }

+

+    @Override

+    public AssetManager newAssetManager() {

+        logger.log(Level.INFO, "newAssetManager()");

+        return new AndroidAssetManager(null);

+    }

+

+    @Override

+    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {

+        return true;

+    }

+

+    @Override

+    public JmeContext newContext(AppSettings settings, Type contextType) {

+        initialize(settings);

+        return new OGLESContext();

+    }

+

+    @Override

+    public AudioRenderer newAudioRenderer(AppSettings settings) {

+        return new AndroidAudioRenderer(activity);

+    }

+

+    @Override

+    public void initialize(AppSettings settings) {

+        if (initialized) {

+            return;

+        }

+

+        initialized = true;

+        try {

+            JmeFormatter formatter = new JmeFormatter();

+

+            Handler consoleHandler = new AndroidLogHandler();

+            consoleHandler.setFormatter(formatter);

+        } catch (SecurityException ex) {

+            logger.log(Level.SEVERE, "Security error in creating log file", ex);

+        }

+        logger.log(Level.INFO, "Running on {0}", getFullName());

+    }

+

+    @Override

+    public Platform getPlatform() {

+        String arch = System.getProperty("os.arch").toLowerCase();

+        if (arch.contains("arm")) {

+            if (arch.contains("v5")) {

+                return Platform.Android_ARM5;

+            } else if (arch.contains("v6")) {

+                return Platform.Android_ARM6;

+            } else if (arch.contains("v7")) {

+                return Platform.Android_ARM7;

+            } else {

+                return Platform.Android_ARM5; // unknown ARM

+            }

+        } else {

+            throw new UnsupportedOperationException("Unsupported Android Platform");

+        }

+    }

+

+    @Override

+    public synchronized File getStorageFolder() {

+        //http://developer.android.com/reference/android/content/Context.html#getExternalFilesDir

+        //http://developer.android.com/guide/topics/data/data-storage.html

+

+        boolean mExternalStorageWriteable = false;

+        String state = Environment.getExternalStorageState();

+        if (Environment.MEDIA_MOUNTED.equals(state)) {

+            mExternalStorageWriteable = true;

+        } else {

+            mExternalStorageWriteable = false;

+        }

+

+        if (mExternalStorageWriteable) {

+            //getExternalFilesDir automatically creates the directory if necessary.

+            //directory structure should be: /mnt/sdcard/Android/data/<packagename>/files

+            //when created this way, the directory is automatically removed by the Android

+            //  system when the app is uninstalled

+            storageFolder = activity.getApplicationContext().getExternalFilesDir(null);

+            logger.log(Level.INFO, "Storage Folder Path: {0}", storageFolder.getAbsolutePath());

+

+            return storageFolder;

+        } else {

+            return null;

+        }

+

+    }

+

+    public static void setResources(Resources res) {

+        JmeAndroidSystem.res = res;

+    }

+

+    public static Resources getResources() {

+        return res;

+    }

+

+    public static void setActivity(Activity activity) {

+        JmeAndroidSystem.activity = activity;

+    }

+

+    public static Activity getActivity() {

+        return activity;

+    }

+}

diff --git a/engine/src/android/com/jme3/system/android/OGLESContext.java b/engine/src/android/com/jme3/system/android/OGLESContext.java
new file mode 100644
index 0000000..668b68d
--- /dev/null
+++ b/engine/src/android/com/jme3/system/android/OGLESContext.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) 2003-2009 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
+ *   may be used to endorse or promote products derived from this software 
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.view.SurfaceHolder;
+import com.jme3.app.AndroidHarness;
+import com.jme3.app.Application;
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.android.AndroidInput;
+import com.jme3.input.controls.TouchTrigger;
+import com.jme3.input.dummy.DummyKeyInput;
+import com.jme3.input.dummy.DummyMouseInput;
+import com.jme3.renderer.android.OGLESShaderRenderer;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+import com.jme3.system.SystemListener;
+import com.jme3.system.Timer;
+import com.jme3.system.android.AndroidConfigChooser.ConfigType;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+public class OGLESContext implements JmeContext, GLSurfaceView.Renderer {
+
+    private static final Logger logger = Logger.getLogger(OGLESContext.class.getName());
+    protected final AtomicBoolean created = new AtomicBoolean(false);
+    protected final AtomicBoolean renderable = new AtomicBoolean(false);
+    protected final AtomicBoolean needClose = new AtomicBoolean(false);
+    protected final AppSettings settings = new AppSettings(true);
+
+    /*
+     * >= OpenGL ES 2.0 (Android 2.2+)
+     */
+    protected OGLESShaderRenderer renderer;
+    protected Timer timer;
+    protected SystemListener listener;
+    protected boolean autoFlush = true;
+    protected AndroidInput view;
+    private boolean firstDrawFrame = true;
+
+    //protected int minFrameDuration = 1000 / frameRate;  // Set a max FPS of 33
+    protected int minFrameDuration = 0;                   // No FPS cap
+    /**
+     * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 |
+     * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0
+     */
+    protected int clientOpenGLESVersion = 1;
+    protected boolean verboseLogging = false;
+    final private String ESCAPE_EVENT = "TouchEscape";
+
+    public OGLESContext() {
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Display;
+    }
+
+    /**
+     * <code>createView</code>
+     *
+     * @param activity The Android activity which is parent for the
+     * GLSurfaceView
+     * @return GLSurfaceView The newly created view
+     */
+    public GLSurfaceView createView(Activity activity) {
+        return createView(new AndroidInput(activity));
+    }
+
+    /**
+     * <code>createView</code>
+     *
+     * @param view The Android input which will be used as the GLSurfaceView for
+     * this context
+     * @return GLSurfaceView The newly created view
+     */
+    public GLSurfaceView createView(AndroidInput view) {
+        return createView(view, ConfigType.FASTEST, false);
+    }
+
+    /**
+     * <code>createView</code> initializes the GLSurfaceView
+     *
+     * @param view The Android input which will be used as the GLSurfaceView for
+     * this context
+     * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY |
+     * ConfigType.BEST
+     * @param eglConfigVerboseLogging if true show all found configs
+     * @return GLSurfaceView The newly created view
+     */
+    public GLSurfaceView createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging) {
+        // Start to set up the view
+        this.view = view;
+        verboseLogging = eglConfigVerboseLogging;
+
+        if (configType == ConfigType.LEGACY) {
+            // Hardcoded egl setup
+            clientOpenGLESVersion = 2;
+            view.setEGLContextClientVersion(2);
+            //RGB565, Depth16
+            view.setEGLConfigChooser(5, 6, 5, 0, 16, 0);
+            logger.info("ConfigType.LEGACY using RGB565");
+        } else {
+            EGL10 egl = (EGL10) EGLContext.getEGL();
+            EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+            int[] version = new int[2];
+            if (egl.eglInitialize(display, version) == true) {
+                logger.info("Display EGL Version: " + version[0] + "." + version[1]);
+            }
+
+            try {
+                // Create a config chooser
+                AndroidConfigChooser configChooser = new AndroidConfigChooser(configType, eglConfigVerboseLogging);
+                // Init chooser
+                if (!configChooser.findConfig(egl, display)) {
+                    listener.handleError("Unable to find suitable EGL config", null);
+                    return null;
+                }
+
+                clientOpenGLESVersion = configChooser.getClientOpenGLESVersion();
+                if (clientOpenGLESVersion < 2) {
+                    listener.handleError("OpenGL ES 2.0 is not supported on this device", null);
+                    return null;
+                }
+                
+                // Requesting client version from GLSurfaceView which is extended by
+                // AndroidInput.
+                view.setEGLContextClientVersion(clientOpenGLESVersion);
+                view.setEGLConfigChooser(configChooser);
+                view.getHolder().setFormat(configChooser.getPixelFormat());
+            } finally {
+                if (display != null) {
+                    egl.eglTerminate(display);
+                }
+            }
+        }
+
+        view.setFocusableInTouchMode(true);
+        view.setFocusable(true);
+        view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
+        view.setRenderer(this);
+
+        return view;
+    }
+
+    // renderer:initialize
+    @Override
+    public void onSurfaceCreated(GL10 gl, EGLConfig cfg) {
+
+        if (created.get() && renderer != null) {
+            renderer.resetGLObjects();
+        } else {
+            if (!created.get()) {
+                logger.info("GL Surface created, doing JME3 init");
+                initInThread();
+            } else {
+                logger.warning("GL Surface already created");
+            }
+        }
+    }
+
+    protected void initInThread() {
+        created.set(true);
+
+        logger.info("OGLESContext create");
+        logger.info("Running on thread: " + Thread.currentThread().getName());
+
+        final Context ctx = this.view.getContext();
+
+        // Setup unhandled Exception Handler
+        if (ctx instanceof AndroidHarness) {
+            Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                public void uncaughtException(Thread thread, Throwable thrown) {
+                    ((AndroidHarness) ctx).handleError("Exception thrown in " + thread.toString(), thrown);
+                }
+            });
+        } else {
+            Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                public void uncaughtException(Thread thread, Throwable thrown) {
+                    listener.handleError("Exception thrown in " + thread.toString(), thrown);
+                }
+            });
+        }
+
+        if (clientOpenGLESVersion < 2) {
+            throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device");
+        }
+
+        timer = new AndroidTimer();
+        renderer = new OGLESShaderRenderer();
+
+        renderer.setUseVA(true);
+        renderer.setVerboseLogging(verboseLogging);
+
+        renderer.initialize();
+        listener.initialize();
+
+        // Setup exit hook
+        if (ctx instanceof AndroidHarness) {
+            Application app = ((AndroidHarness) ctx).getJmeApplication();
+            if (app.getInputManager() != null) {
+                app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK));
+                app.getInputManager().addListener((AndroidHarness) ctx, new String[]{ESCAPE_EVENT});
+            }
+        }
+
+        needClose.set(false);
+        renderable.set(true);
+    }
+
+    /**
+     * De-initialize in the OpenGL thread.
+     */
+    protected void deinitInThread() {
+        if (renderable.get()) {
+            created.set(false);
+            if (renderer != null) {
+                renderer.cleanup();
+            }
+
+            listener.destroy();
+
+            listener = null;
+            renderer = null;
+            timer = null;
+
+            // do android specific cleaning here
+            logger.info("Display destroyed.");
+
+            renderable.set(false);
+        }
+    }
+
+    protected void applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings) {
+        logger.warning("setSettings.USE_VA: [" + settings.getBoolean("USE_VA") + "]");
+        logger.warning("setSettings.VERBOSE_LOGGING: [" + settings.getBoolean("VERBOSE_LOGGING") + "]");
+        renderer.setUseVA(settings.getBoolean("USE_VA"));
+        renderer.setVerboseLogging(settings.getBoolean("VERBOSE_LOGGING"));
+    }
+
+    protected void applySettings(AppSettings settings) {
+        setSettings(settings);
+        if (renderer != null) {
+            applySettingsToRenderer(renderer, this.settings);
+        }
+    }
+
+    @Override
+    public void setSettings(AppSettings settings) {
+        this.settings.copyFrom(settings);
+    }
+
+    @Override
+    public void setSystemListener(SystemListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public AppSettings getSettings() {
+        return settings;
+    }
+
+    @Override
+    public com.jme3.renderer.Renderer getRenderer() {
+        return renderer;
+    }
+
+    @Override
+    public MouseInput getMouseInput() {
+        return new DummyMouseInput();
+    }
+
+    @Override
+    public KeyInput getKeyInput() {
+        return new DummyKeyInput();
+    }
+
+    @Override
+    public JoyInput getJoyInput() {
+        return null;
+    }
+
+    @Override
+    public TouchInput getTouchInput() {
+        return view;
+    }
+
+    @Override
+    public Timer getTimer() {
+        return timer;
+    }
+
+    @Override
+    public void setTitle(String title) {
+    }
+
+    @Override
+    public boolean isCreated() {
+        return created.get();
+    }
+
+    @Override
+    public void setAutoFlushFrames(boolean enabled) {
+        this.autoFlush = enabled;
+    }
+
+    // SystemListener:reshape
+    @Override
+    public void onSurfaceChanged(GL10 gl, int width, int height) {
+        logger.info("GL Surface changed, width: " + width + " height: " + height);
+        settings.setResolution(width, height);
+        listener.reshape(width, height);
+    }
+
+    // SystemListener:update
+    @Override
+    public void onDrawFrame(GL10 gl) {
+        if (needClose.get()) {
+            deinitInThread();
+            return;
+        }
+
+        if (renderable.get()) {
+            if (!created.get()) {
+                throw new IllegalStateException("onDrawFrame without create");
+            }
+
+            long milliStart = System.currentTimeMillis();
+
+            listener.update();
+
+            // call to AndroidHarness to remove the splash screen, if present.
+            // call after listener.update() to make sure no gap between
+            //   splash screen going away and app display being shown.
+            if (firstDrawFrame) {
+                final Context ctx = this.view.getContext();
+                if (ctx instanceof AndroidHarness) {
+                    ((AndroidHarness) ctx).removeSplashScreen();
+                }
+                firstDrawFrame = false;
+            }
+
+            if (autoFlush) {
+                renderer.onFrame();
+            }
+
+            long milliDelta = System.currentTimeMillis() - milliStart;
+
+            // Enforce a FPS cap
+            if (milliDelta < minFrameDuration) {
+                //logger.log(Level.INFO, "Time per frame {0}", milliDelta);
+                try {
+                    Thread.sleep(minFrameDuration - milliDelta);
+                } catch (InterruptedException e) {
+                }
+            }
+
+        }
+    }
+
+    @Override
+    public boolean isRenderable() {
+        return renderable.get();
+    }
+
+    @Override
+    public void create(boolean waitFor) {
+        if (waitFor) {
+            waitFor(true);
+        }
+    }
+
+    public void create() {
+        create(false);
+    }
+
+    @Override
+    public void restart() {
+    }
+
+    @Override
+    public void destroy(boolean waitFor) {
+        needClose.set(true);
+        if (waitFor) {
+            waitFor(false);
+        }
+    }
+
+    public void destroy() {
+        destroy(true);
+    }
+
+    protected void waitFor(boolean createdVal) {
+        while (renderable.get() != createdVal) {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException ex) {
+            }
+        }
+    }
+
+    public int getClientOpenGLESVersion() {
+        return clientOpenGLESVersion;
+    }
+}
diff --git a/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java b/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java
new file mode 100644
index 0000000..17f850e
--- /dev/null
+++ b/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java
@@ -0,0 +1,20 @@
+package com.jme3.texture.plugins;
+
+import android.graphics.Bitmap;
+import com.jme3.asset.AndroidImageInfo;
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.texture.Image;
+import java.io.IOException;
+
+public class AndroidImageLoader implements AssetLoader {
+
+    public Object load(AssetInfo info) throws IOException {
+        AndroidImageInfo imageInfo = new AndroidImageInfo(info);
+        Bitmap bitmap = imageInfo.getBitmap();
+        
+        Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);
+        image.setEfficentData(imageInfo);
+        return image;
+    }
+}
diff --git a/engine/src/android/com/jme3/util/AndroidLogHandler.java b/engine/src/android/com/jme3/util/AndroidLogHandler.java
new file mode 100644
index 0000000..8fb21c2
--- /dev/null
+++ b/engine/src/android/com/jme3/util/AndroidLogHandler.java
@@ -0,0 +1,37 @@
+package com.jme3.util;
+
+import android.util.Log;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+public class AndroidLogHandler extends Handler {
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    @Override
+    public void publish(LogRecord record) {
+        Level level = record.getLevel();
+        String clsName = record.getSourceClassName();
+        String msg = record.getMessage();
+        Throwable t = record.getThrown();
+        if (level == Level.INFO){
+            Log.i(clsName, msg, t);
+        }else if (level == Level.SEVERE){
+            Log.e(clsName, msg, t);
+        }else if (level == Level.WARNING){
+            Log.w(clsName, msg, t);
+        }else if (level == Level.CONFIG){
+            Log.d(clsName, msg, t);
+        }else if (level == Level.FINE || level == Level.FINER || level == Level.FINEST){
+            Log.v(clsName, msg, t);
+        }
+    }
+
+}
diff --git a/engine/src/android/com/jme3/util/FastInteger.java b/engine/src/android/com/jme3/util/FastInteger.java
new file mode 100644
index 0000000..49294b4
--- /dev/null
+++ b/engine/src/android/com/jme3/util/FastInteger.java
@@ -0,0 +1,359 @@
+package com.jme3.util;
+
+
+/**
+ * The wrapper for the primitive type {@code int}.
+ * <p>
+ * As with the specification, this implementation relies on code laid out in <a
+ * href="http://www.hackersdelight.org/">Henry S. Warren, Jr.'s Hacker's
+ * Delight, (Addison Wesley, 2002)</a> as well as <a
+ * href="http://aggregate.org/MAGIC/">The Aggregate's Magic Algorithms</a>.
+ *
+ * @see java.lang.Number
+ * @since 1.1
+ */
+public final class FastInteger {
+
+    /**
+     * Constant for the maximum {@code int} value, 2<sup>31</sup>-1.
+     */
+    public static final int MAX_VALUE = 0x7FFFFFFF;
+
+    /**
+     * Constant for the minimum {@code int} value, -2<sup>31</sup>.
+     */
+    public static final int MIN_VALUE = 0x80000000;
+
+    /**
+     * Constant for the number of bits needed to represent an {@code int} in
+     * two's complement form.
+     *
+     * @since 1.5
+     */
+    public static final int SIZE = 32;
+    
+    /*
+     * Progressively smaller decimal order of magnitude that can be represented
+     * by an instance of Integer. Used to help compute the String
+     * representation.
+     */
+    private static final int[] decimalScale = new int[] { 1000000000, 100000000,
+            10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
+    
+    /**
+     * Converts the specified integer into its decimal string representation.
+     * The returned string is a concatenation of a minus sign if the number is
+     * negative and characters from '0' to '9'.
+     * 
+     * @param value
+     *            the integer to convert.
+     * @return the decimal string representation of {@code value}.
+     */
+    public static boolean toCharArray(int value, char[] output) {
+        if (value == 0) 
+        {
+            output[0] = '0';
+            output[1] = 0;
+            return true;
+        }
+
+        // Faster algorithm for smaller Integers
+        if (value < 1000 && value > -1000) {
+
+            int positive_value = value < 0 ? -value : value;
+            int first_digit = 0;
+            if (value < 0) {
+                output[0] = '-';
+                first_digit++;
+            }
+            int last_digit = first_digit;
+            int quot = positive_value;
+            do {
+                int res = quot / 10;
+                int digit_value = quot - ((res << 3) + (res << 1));
+                digit_value += '0';
+                output[last_digit++] = (char) digit_value;
+                quot = res;
+            } while (quot != 0);
+
+            int count = last_digit--;
+            do {
+                char tmp = output[last_digit];
+                output[last_digit--] = output[first_digit];
+                output[first_digit++] = tmp;
+            } while (first_digit < last_digit);
+            output[count] = 0;
+            return true;
+        }
+        if (value == MIN_VALUE) {
+            System.arraycopy("-2147483648".toCharArray(), 0, output, 0, 12);
+            output[12] = 0;
+            return true;
+        }
+
+
+        int positive_value = value < 0 ? -value : value;
+        byte first_digit = 0;
+        if (value < 0) {
+            output[0] = '-';
+            first_digit++;
+        }
+        byte last_digit = first_digit;
+        byte count;
+        int number;
+        boolean start = false;
+        for (int i = 0; i < 9; i++) {
+            count = 0;
+            if (positive_value < (number = decimalScale[i])) {
+                if (start) {
+                    output[last_digit++] = '0';
+                }
+                continue;
+            }
+
+            if (i > 0) {
+                number = (decimalScale[i] << 3);
+                if (positive_value >= number) {
+                    positive_value -= number;
+                    count += 8;
+                }
+                number = (decimalScale[i] << 2);
+                if (positive_value >= number) {
+                    positive_value -= number;
+                    count += 4;
+                }
+            }
+            number = (decimalScale[i] << 1);
+            if (positive_value >= number) {
+                positive_value -= number;
+                count += 2;
+            }
+            if (positive_value >= decimalScale[i]) {
+                positive_value -= decimalScale[i];
+                count++;
+            }
+            if (count > 0 && !start) {
+                start = true;
+            }
+            if (start) {
+                output[last_digit++] = (char) (count + '0');
+            }
+        }
+
+        output[last_digit++] = (char) (positive_value + '0');
+        output[last_digit] = 0;
+        count = last_digit--;
+        return true;
+    }
+
+
+    /**
+     * Determines the highest (leftmost) bit of the specified integer that is 1
+     * and returns the bit mask value for that bit. This is also referred to as
+     * the Most Significant 1 Bit. Returns zero if the specified integer is
+     * zero.
+     * 
+     * @param i
+     *            the integer to examine.
+     * @return the bit mask indicating the highest 1 bit in {@code i}.
+     * @since 1.5
+     */
+    public static int highestOneBit(int i) {
+        i |= (i >> 1);
+        i |= (i >> 2);
+        i |= (i >> 4);
+        i |= (i >> 8);
+        i |= (i >> 16);
+        return (i & ~(i >>> 1));
+    }
+
+    /**
+     * Determines the lowest (rightmost) bit of the specified integer that is 1
+     * and returns the bit mask value for that bit. This is also referred
+     * to as the Least Significant 1 Bit. Returns zero if the specified integer
+     * is zero.
+     * 
+     * @param i
+     *            the integer to examine.
+     * @return the bit mask indicating the lowest 1 bit in {@code i}.
+     * @since 1.5
+     */
+    public static int lowestOneBit(int i) {
+        return (i & (-i));
+    }
+
+    /**
+     * Determines the number of leading zeros in the specified integer prior to
+     * the {@link #highestOneBit(int) highest one bit}.
+     *
+     * @param i
+     *            the integer to examine.
+     * @return the number of leading zeros in {@code i}.
+     * @since 1.5
+     */
+    public static int numberOfLeadingZeros(int i) {
+        i |= i >> 1;
+        i |= i >> 2;
+        i |= i >> 4;
+        i |= i >> 8;
+        i |= i >> 16;
+        return bitCount(~i);
+    }
+
+    /**
+     * Determines the number of trailing zeros in the specified integer after
+     * the {@link #lowestOneBit(int) lowest one bit}.
+     *
+     * @param i
+     *            the integer to examine.
+     * @return the number of trailing zeros in {@code i}.
+     * @since 1.5
+     */
+    public static int numberOfTrailingZeros(int i) {
+        return bitCount((i & -i) - 1);
+    }
+
+    /**
+     * Counts the number of 1 bits in the specified integer; this is also
+     * referred to as population count.
+     *
+     * @param i
+     *            the integer to examine.
+     * @return the number of 1 bits in {@code i}.
+     * @since 1.5
+     */
+    public static int bitCount(int i) {
+        i -= ((i >> 1) & 0x55555555);
+        i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
+        i = (((i >> 4) + i) & 0x0F0F0F0F);
+        i += (i >> 8);
+        i += (i >> 16);
+        return (i & 0x0000003F);
+    }
+
+    /**
+     * Rotates the bits of the specified integer to the left by the specified
+     * number of bits.
+     *
+     * @param i
+     *            the integer value to rotate left.
+     * @param distance
+     *            the number of bits to rotate.
+     * @return the rotated value.
+     * @since 1.5
+     */
+    public static int rotateLeft(int i, int distance) {
+        if (distance == 0) {
+            return i;
+        }
+        /*
+         * According to JLS3, 15.19, the right operand of a shift is always
+         * implicitly masked with 0x1F, which the negation of 'distance' is
+         * taking advantage of.
+         */
+        return ((i << distance) | (i >>> (-distance)));
+    }
+
+    /**
+     * Rotates the bits of the specified integer to the right by the specified
+     * number of bits.
+     *
+     * @param i
+     *            the integer value to rotate right.
+     * @param distance
+     *            the number of bits to rotate.
+     * @return the rotated value.
+     * @since 1.5
+     */
+    public static int rotateRight(int i, int distance) {
+        if (distance == 0) {
+            return i;
+        }
+        /*
+         * According to JLS3, 15.19, the right operand of a shift is always
+         * implicitly masked with 0x1F, which the negation of 'distance' is
+         * taking advantage of.
+         */
+        return ((i >>> distance) | (i << (-distance)));
+    }
+
+    /**
+     * Reverses the order of the bytes of the specified integer.
+     * 
+     * @param i
+     *            the integer value for which to reverse the byte order.
+     * @return the reversed value.
+     * @since 1.5
+     */
+    public static int reverseBytes(int i) {
+        int b3 = i >>> 24;
+        int b2 = (i >>> 8) & 0xFF00;
+        int b1 = (i & 0xFF00) << 8;
+        int b0 = i << 24;
+        return (b0 | b1 | b2 | b3);
+    }
+
+    /**
+     * Reverses the order of the bits of the specified integer.
+     * 
+     * @param i
+     *            the integer value for which to reverse the bit order.
+     * @return the reversed value.
+     * @since 1.5
+     */
+    public static int reverse(int i) {
+        // From Hacker's Delight, 7-1, Figure 7-1
+        i = (i & 0x55555555) << 1 | (i >> 1) & 0x55555555;
+        i = (i & 0x33333333) << 2 | (i >> 2) & 0x33333333;
+        i = (i & 0x0F0F0F0F) << 4 | (i >> 4) & 0x0F0F0F0F;
+        return reverseBytes(i);
+    }
+
+    /**
+     * Returns the value of the {@code signum} function for the specified
+     * integer.
+     * 
+     * @param i
+     *            the integer value to check.
+     * @return -1 if {@code i} is negative, 1 if {@code i} is positive, 0 if
+     *         {@code i} is zero.
+     * @since 1.5
+     */
+    public static int signum(int i) {
+        return (i == 0 ? 0 : (i < 0 ? -1 : 1));
+    }
+
+    /**
+     * Returns a {@code Integer} instance for the specified integer value.
+     * <p>
+     * If it is not necessary to get a new {@code Integer} instance, it is
+     * recommended to use this method instead of the constructor, since it
+     * maintains a cache of instances which may result in better performance.
+     *
+     * @param i
+     *            the integer value to store in the instance.
+     * @return a {@code Integer} instance containing {@code i}.
+     * @since 1.5
+     */
+    public static Integer valueOf(int i) {
+        if (i < -128 || i > 127) {
+            return new Integer(i);
+        }
+        return valueOfCache.CACHE [i+128];
+
+    }
+
+   static class valueOfCache {
+        /**
+         * <p>
+         * A cache of instances used by {@link Integer#valueOf(int)} and auto-boxing.
+         */
+        static final Integer[] CACHE = new Integer[256];
+
+        static {
+            for(int i=-128; i<=127; i++) {
+                CACHE[i+128] = new Integer(i);
+            }
+        }
+    }
+}
diff --git a/engine/src/android/com/jme3/util/RingBuffer.java b/engine/src/android/com/jme3/util/RingBuffer.java
new file mode 100644
index 0000000..786417b
--- /dev/null
+++ b/engine/src/android/com/jme3/util/RingBuffer.java
@@ -0,0 +1,75 @@
+package com.jme3.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Ring buffer (fixed size queue) implementation using a circular array (array with wrap-around).
+ */
+// suppress unchecked warnings in Java 1.5.0_6 and later
+@SuppressWarnings("unchecked")
+public class RingBuffer<Item> implements Iterable<Item> {
+
+    private Item[] buffer;          // queue elements
+    private int count = 0;          // number of elements on queue
+    private int indexOut = 0;       // index of first element of queue
+    private int indexIn = 0;       // index of next available slot
+
+    // cast needed since no generic array creation in Java
+    public RingBuffer(int capacity) {
+        buffer = (Item[]) new Object[capacity];
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+
+    public int size() {
+        return count;
+    }
+
+    public void push(Item item) {
+        if (count == buffer.length) {
+            throw new RuntimeException("Ring buffer overflow");
+        }
+        buffer[indexIn] = item;
+        indexIn = (indexIn + 1) % buffer.length;     // wrap-around
+        count++;
+    }
+
+    public Item pop() {
+        if (isEmpty()) {
+            throw new RuntimeException("Ring buffer underflow");
+        }
+        Item item = buffer[indexOut];
+        buffer[indexOut] = null;                  // to help with garbage collection
+        count--;
+        indexOut = (indexOut + 1) % buffer.length; // wrap-around
+        return item;
+    }
+
+    public Iterator<Item> iterator() {
+        return new RingBufferIterator();
+    }
+
+    // an iterator, doesn't implement remove() since it's optional
+    private class RingBufferIterator implements Iterator<Item> {
+
+        private int i = 0;
+
+        public boolean hasNext() {
+            return i < count;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        public Item next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            return buffer[i++];
+        }
+    }
+}
diff --git a/engine/src/android/jme3test/android/AndroidManifest.xml b/engine/src/android/jme3test/android/AndroidManifest.xml
new file mode 100644
index 0000000..a1f8f3f
--- /dev/null
+++ b/engine/src/android/jme3test/android/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.jme3.androiddemo"
+      android:versionCode="6"
+      android:versionName="1.2.2">
+      
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />      
+    
+    <!-- Tell the system that you need ES 2.0. -->
+    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+    <!-- Tell the system that you need distinct touches (for the zoom gesture). -->
+    <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="true" />
+
+    <application android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:name=".DemoMainActivity"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity android:name=".DemoAndroidHarness"
+                  android:label="@string/app_name">
+        </activity>
+        
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/engine/src/android/jme3test/android/DemoAndroidHarness.java b/engine/src/android/jme3test/android/DemoAndroidHarness.java
new file mode 100644
index 0000000..0a431cf
--- /dev/null
+++ b/engine/src/android/jme3test/android/DemoAndroidHarness.java
@@ -0,0 +1,54 @@
+package jme3test.android;
+
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import com.jme3.app.AndroidHarness;
+import com.jme3.system.android.AndroidConfigChooser.ConfigType;
+
+public class DemoAndroidHarness extends AndroidHarness
+{
+    @Override
+    public void onCreate(Bundle savedInstanceState) 
+    {        
+        // Set the application class to run
+        // First Extract the bundle from intent
+        Bundle bundle = getIntent().getExtras();
+
+        //Next extract the values using the key as
+        appClass = bundle.getString("APPCLASSNAME");                
+        
+        
+        String eglConfig = bundle.getString("EGLCONFIG");
+        if (eglConfig.equals("Best"))
+        {
+            eglConfigType = ConfigType.BEST;
+        }
+        else if (eglConfig.equals("Legacy"))
+        {
+            eglConfigType = ConfigType.LEGACY;
+        }
+        else
+        {
+            eglConfigType = ConfigType.FASTEST;    
+        }
+        
+        
+        if (bundle.getBoolean("VERBOSE"))
+        {
+            eglConfigVerboseLogging = true;
+        }
+        else
+        {
+            eglConfigVerboseLogging = false;
+        }
+        
+        
+        exitDialogTitle = "Close Demo?";
+        exitDialogMessage = "Press Yes";
+                        
+        screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+        
+        super.onCreate(savedInstanceState);                
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/DemoLaunchAdapter.java b/engine/src/android/jme3test/android/DemoLaunchAdapter.java
new file mode 100644
index 0000000..9d4c9de
--- /dev/null
+++ b/engine/src/android/jme3test/android/DemoLaunchAdapter.java
@@ -0,0 +1,72 @@
+package jme3test.android;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+import java.util.List;
+
+/**
+ * The view adapter which gets a list of LaunchEntries and displaqs them
+ * @author larynx
+ *
+ */
+public class DemoLaunchAdapter extends BaseAdapter implements OnClickListener 
+{
+    
+    private Context context;
+
+    private List<DemoLaunchEntry> listDemos;
+
+    public DemoLaunchAdapter(Context context, List<DemoLaunchEntry> listDemos) {
+        this.context = context;
+        this.listDemos = listDemos;
+    }
+
+    public int getCount() {
+        return listDemos.size();
+    }
+
+    public Object getItem(int position) {
+        return listDemos.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup viewGroup) {
+        DemoLaunchEntry entry = listDemos.get(position);
+        if (convertView == null) {
+            LayoutInflater inflater = (LayoutInflater) context
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            convertView = inflater.inflate(R.layout.demo_row, null);
+        }
+        TextView tvDemoName = (TextView) convertView.findViewById(R.id.tvDemoName);
+        tvDemoName.setText(entry.getName());
+
+        TextView tvDescription = (TextView) convertView.findViewById(R.id.tvDescription);
+        tvDescription.setText(entry.getDescription());
+        
+        return convertView;
+    }
+
+    @Override
+    public void onClick(View view) {
+        DemoLaunchEntry entry = (DemoLaunchEntry) view.getTag();
+        
+        
+        
+
+    }
+
+    private void showDialog(DemoLaunchEntry entry) {
+        // Create and show your dialog
+        // Depending on the Dialogs button clicks delete it or do nothing
+    }
+
+}
+
diff --git a/engine/src/android/jme3test/android/DemoLaunchEntry.java b/engine/src/android/jme3test/android/DemoLaunchEntry.java
new file mode 100644
index 0000000..e23d5eb
--- /dev/null
+++ b/engine/src/android/jme3test/android/DemoLaunchEntry.java
@@ -0,0 +1,38 @@
+package jme3test.android;
+
+/**
+ * Name (=appClass) and Description of one demo launch inside the main apk
+ * @author larynx
+ *
+ */
+public class DemoLaunchEntry 
+{
+    private String name;
+    private String description;
+            
+    /**
+     * @param name
+     * @param description
+     */
+    public DemoLaunchEntry(String name, String description) {
+        super();
+        this.name = name;
+        this.description = description;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public String getDescription() {
+        return description;
+    }
+    public void setDescription(String description) {
+        this.description = description;
+    }
+    
+    
+
+}
diff --git a/engine/src/android/jme3test/android/DemoMainActivity.java b/engine/src/android/jme3test/android/DemoMainActivity.java
new file mode 100644
index 0000000..dfc88aa
--- /dev/null
+++ b/engine/src/android/jme3test/android/DemoMainActivity.java
@@ -0,0 +1,131 @@
+package jme3test.android;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DemoMainActivity extends Activity {
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);       
+        
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                      
+        final Intent myIntent = new Intent(DemoMainActivity.this, DemoAndroidHarness.class);
+        
+        //Next create the bundle and initialize it
+        final Bundle bundle = new Bundle();
+
+
+        final Spinner spinnerConfig = (Spinner) findViewById(R.id.spinnerConfig);
+        ArrayAdapter<CharSequence> adapterDropDownConfig = ArrayAdapter.createFromResource(
+                this, R.array.eglconfig_array, android.R.layout.simple_spinner_item);
+        adapterDropDownConfig.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinnerConfig.setAdapter(adapterDropDownConfig);
+
+        
+        spinnerConfig.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+            @Override           
+            public void onItemSelected(AdapterView<?> parent,
+                    View view, int pos, long id) {
+                  Toast.makeText(parent.getContext(), "Set EGLConfig " +
+                      parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
+                  //Add the parameters to bundle as
+                  bundle.putString("EGLCONFIG", parent.getItemAtPosition(pos).toString()); 
+            }
+
+            public void onNothingSelected(AdapterView parent) {
+                  // Do nothing.
+            }
+        });
+        
+        
+        final Spinner spinnerLogging = (Spinner) findViewById(R.id.spinnerLogging);
+        ArrayAdapter<CharSequence> adapterDropDownLogging = ArrayAdapter.createFromResource(
+                this, R.array.logging_array, android.R.layout.simple_spinner_item);
+        adapterDropDownLogging.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinnerLogging.setAdapter(adapterDropDownLogging);
+
+        
+        spinnerLogging.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+            @Override           
+            public void onItemSelected(AdapterView<?> parent,
+                    View view, int pos, long id) {
+                  Toast.makeText(parent.getContext(), "Set Logging " +
+                      parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
+                                    
+                  //Add the parameters to bundle as
+                  bundle.putBoolean("VERBOSE", parent.getItemAtPosition(pos).toString().equals("Verbose"));
+            }
+
+            public void onNothingSelected(AdapterView parent) {
+                  // Do nothing.
+            }
+        });
+        
+        
+        ListView list = (ListView) findViewById(R.id.ListView01);
+        list.setClickable(true);
+ 
+        final List<DemoLaunchEntry> listDemos = new ArrayList<DemoLaunchEntry>();
+        
+        listDemos.add(new DemoLaunchEntry("jme3test.android.SimpleTexturedTest", "An field of textured boxes rotating"));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestSkyLoadingLagoon", "Sky box demonstration with jpg"));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestSkyLoadingPrimitives", "Sky box demonstration with png"));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestBumpModel", "Shows a bump mapped well with a moving light"));        
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestNormalMapping", "Shows a normal mapped sphere"));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestUnshadedModel", "Shows an unshaded model of the sphere"));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestMovingParticle", "Demonstrates particle effects"));        
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestAmbient", "Positional sound - You sit in a dark cave under a waterfall"));
+        
+        //listDemos.add(new DemoLaunchEntry("jme3test.effect.TestParticleEmitter", ""));
+        //listDemos.add(new DemoLaunchEntry("jme3test.effect.TestPointSprite", ""));
+        //listDemos.add(new DemoLaunchEntry("jme3test.light.TestLightRadius", ""));
+        listDemos.add(new DemoLaunchEntry("jme3test.android.TestMotionPath", "Shows cinematics - see a teapot on its journey - model loading needs a long time - just let it load, looks like freezed"));
+        //listDemos.add(new DemoLaunchEntry("com.jme3.androiddemo.TestSimpleWater", "Post processors - not working correctly due to missing framebuffer support, looks interresting :)"));
+        //listDemos.add(new DemoLaunchEntry("jme3test.model.TestHoverTank", ""));
+        //listDemos.add(new DemoLaunchEntry("jme3test.niftygui.TestNiftyGui", ""));
+        //listDemos.add(new DemoLaunchEntry("com.jme3.androiddemo.TestNiftyGui", ""));
+
+        
+        DemoLaunchAdapter adapterList = new DemoLaunchAdapter(this, listDemos);
+
+        list.setOnItemClickListener(new OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> arg0, View view, int position, long index) {
+                System.out.println("onItemClick");                               
+                showToast(listDemos.get(position).getName());
+                 
+
+                //Add the parameters to bundle as
+                bundle.putString("APPCLASSNAME", listDemos.get(position).getName());
+
+                //Add this bundle to the intent
+                myIntent.putExtras(bundle);
+
+                //Start the JME3 app harness activity                
+                DemoMainActivity.this.startActivity(myIntent);
+
+            }
+        });
+
+        list.setAdapter(adapterList);
+    }
+
+    private void showToast(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+    }
+}
+
diff --git a/engine/src/android/jme3test/android/R.java b/engine/src/android/jme3test/android/R.java
new file mode 100644
index 0000000..ffb1f37
--- /dev/null
+++ b/engine/src/android/jme3test/android/R.java
@@ -0,0 +1,46 @@
+/* AUTO-GENERATED FILE.  DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found.  It
+ * should not be modified by hand.
+ */
+
+package jme3test.android;
+
+public final class R {
+    public static final class array {
+        public static final int eglconfig_array=0x7f060000;
+        public static final int logging_array=0x7f060001;
+    }
+    public static final class attr {
+    }
+    public static final class drawable {
+        public static final int icon=0x7f020000;
+    }
+    public static final class id {
+        public static final int LinearLayout01=0x7f070000;
+        public static final int LinearLayout02=0x7f070002;
+        public static final int ListView01=0x7f070009;
+        public static final int TextView01=0x7f070003;
+        public static final int spinnerConfig=0x7f070006;
+        public static final int spinnerLogging=0x7f070008;
+        public static final int tvConfig=0x7f070005;
+        public static final int tvDemoName=0x7f070001;
+        public static final int tvDescription=0x7f070004;
+        public static final int tvLogging=0x7f070007;
+    }
+    public static final class layout {
+        public static final int demo_row=0x7f030000;
+        public static final int main=0x7f030001;
+    }
+    public static final class raw {
+        public static final int oddbounce=0x7f040000;
+    }
+    public static final class string {
+        public static final int app_name=0x7f050000;
+        public static final int eglconfig_prompt=0x7f050001;
+        public static final int eglconfig_text=0x7f050002;
+        public static final int logging_prompt=0x7f050003;
+        public static final int logging_text=0x7f050004;
+    }
+}
diff --git a/engine/src/android/jme3test/android/SimpleSoundTest.java b/engine/src/android/jme3test/android/SimpleSoundTest.java
new file mode 100644
index 0000000..9503bca
--- /dev/null
+++ b/engine/src/android/jme3test/android/SimpleSoundTest.java
@@ -0,0 +1,40 @@
+package jme3test.android;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.audio.AudioNode;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.InputListener;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.math.Vector3f;

+

+public class SimpleSoundTest extends SimpleApplication implements InputListener {

+

+    private AudioNode gun;

+    private AudioNode nature;

+

+    @Override

+    public void simpleInitApp() {

+        gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav");

+        gun.setPositional(true);

+        gun.setLocalTranslation(new Vector3f(0, 0, 0));

+        gun.setMaxDistance(100);

+        gun.setRefDistance(5);

+

+        nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);

+        nature.setVolume(3);

+        nature.setLooping(true);

+        nature.play();

+

+        inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+        inputManager.addListener(this, "click");

+

+        rootNode.attachChild(gun);

+        rootNode.attachChild(nature);

+    }

+

+    public void onAction(String name, boolean isPressed, float tpf) {

+        if (name.equals("click") && isPressed) {

+            gun.playInstance();

+        }

+    }

+}

diff --git a/engine/src/android/jme3test/android/SimpleTexturedTest.java b/engine/src/android/jme3test/android/SimpleTexturedTest.java
new file mode 100644
index 0000000..3f7b8d4
--- /dev/null
+++ b/engine/src/android/jme3test/android/SimpleTexturedTest.java
@@ -0,0 +1,150 @@
+
+/*
+ * Android 2.2+ SimpleTextured test.
+ *
+ * created: Mon Nov  8 00:08:22 EST 2010
+ */
+
+package jme3test.android;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Texture;
+import com.jme3.util.TangentBinormalGenerator;
+
+
+public class SimpleTexturedTest extends SimpleApplication {
+
+	private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(SimpleTexturedTest.class.getName());
+
+
+	private Node spheresContainer = new Node("spheres-container");
+
+
+	private boolean lightingEnabled = true;
+	private boolean texturedEnabled = true;
+	private boolean spheres = true;
+
+	@Override
+	public void simpleInitApp() {
+	    
+	    //flyCam.setRotationSpeed(0.01f);
+
+
+		Mesh shapeSphere = null;
+		Mesh shapeBox = null;
+
+
+		shapeSphere = new Sphere(16, 16, .5f);
+		shapeBox = new Box(Vector3f.ZERO, 0.3f, 0.3f, 0.3f);
+
+
+	//	ModelConverter.optimize(geom);
+
+		Texture texture = assetManager.loadTexture(new TextureKey("Interface/Logo/Monkey.jpg"));
+		Texture textureMonkey = assetManager.loadTexture(new TextureKey("Interface/Logo/Monkey.jpg"));
+
+		Material material = null;
+		Material materialMonkey = null;
+
+		if (texturedEnabled) {
+			if (lightingEnabled) {
+				material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+				material.setBoolean("VertexLighting", true);
+				material.setFloat("Shininess", 127);
+				material.setBoolean("LowQuality", true);
+				material.setTexture("DiffuseMap", texture);
+				
+				materialMonkey = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+				materialMonkey.setBoolean("VertexLighting", true);
+				materialMonkey.setFloat("Shininess", 127);
+				materialMonkey.setBoolean("LowQuality", true);
+				materialMonkey.setTexture("DiffuseMap", textureMonkey);
+				
+			} else {
+				material = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md");
+				material.setTexture("ColorMap", texture);
+				
+				materialMonkey = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md");
+				materialMonkey.setTexture("ColorMap", textureMonkey);
+			}
+		} else {
+			material = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
+			material.setColor("Color", ColorRGBA.Red);
+			materialMonkey = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
+			materialMonkey.setColor("Color", ColorRGBA.Red);			
+		}
+
+		TangentBinormalGenerator.generate(shapeSphere);
+		TangentBinormalGenerator.generate(shapeBox);
+
+		int iFlipper = 0;
+		for (int y = -1; y < 2; y++) {
+			for (int x = -1; x < 2; x++){
+				Geometry geomClone = null;
+				
+				//iFlipper++;
+				if (iFlipper % 2 == 0)
+				{
+					geomClone = new Geometry("geometry-" + y + "-" + x, shapeBox);
+				}
+				else
+				{
+					geomClone = new Geometry("geometry-" + y + "-" + x, shapeSphere);
+				}
+				if (iFlipper % 3 == 0)
+				{
+					geomClone.setMaterial(materialMonkey);
+				}
+				else
+				{
+					geomClone.setMaterial(material);
+				}
+				geomClone.setLocalTranslation(x, y, 0);
+                
+//				Transform t = geom.getLocalTransform().clone();
+//				Transform t2 = geomClone.getLocalTransform().clone();
+//				t.combineWithParent(t2);
+//				geomClone.setLocalTransform(t);
+
+				spheresContainer.attachChild(geomClone); 
+			}
+		}
+
+		spheresContainer.setLocalTranslation(new Vector3f(0, 0, -4f));
+		spheresContainer.setLocalScale(2.0f);
+
+		rootNode.attachChild(spheresContainer);
+
+		PointLight pointLight = new PointLight();
+
+		pointLight.setColor(new ColorRGBA(0.7f, 0.7f, 1.0f, 1.0f));
+
+		pointLight.setPosition(new Vector3f(0f, 0f, 0f));
+		pointLight.setRadius(8);
+
+		rootNode.addLight(pointLight);
+	}
+
+	@Override
+	public void simpleUpdate(float tpf) {
+
+		// secondCounter has been removed from SimpleApplication
+                //if (secondCounter == 0)
+		//	logger.info("Frames per second: " + timer.getFrameRate());
+
+		spheresContainer.rotate(0.2f * tpf, 0.4f * tpf, 0.8f * tpf);
+	}
+
+}
+
diff --git a/engine/src/android/jme3test/android/TestAmbient.java b/engine/src/android/jme3test/android/TestAmbient.java
new file mode 100644
index 0000000..50ca739
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestAmbient.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import android.media.SoundPool;
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.math.Vector3f;
+
+public class TestAmbient extends SimpleApplication {
+
+    private AudioNode footsteps, beep;
+    private AudioNode nature, waves;
+    
+    SoundPool soundPool;
+    
+//    private PointAudioSource waves;
+    private float time = 0;
+    private float nextTime = 1;
+
+    public static void main(String[] args){
+        TestAmbient test = new TestAmbient();
+        test.start();
+    }
+    
+
+    @Override
+    public void simpleInitApp()
+    {     
+        /*
+        footsteps  = new AudioNode(audioRenderer, assetManager, "Sound/Effects/Foot steps.ogg", true);
+        
+        footsteps.setPositional(true);
+        footsteps.setLocalTranslation(new Vector3f(4, -1, 30));
+        footsteps.setMaxDistance(5);
+        footsteps.setRefDistance(1);
+        footsteps.setLooping(true);
+
+        beep = new AudioNode(audioRenderer, assetManager, "Sound/Effects/Beep.ogg", true);
+        beep.setVolume(3);
+        beep.setLooping(true);
+        
+        audioRenderer.playSourceInstance(footsteps);
+        audioRenderer.playSource(beep);
+        */
+        
+        waves  = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", true);
+        waves.setPositional(true);
+
+        nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);
+        
+        waves.setLocalTranslation(new Vector3f(4, -1, 30));
+        waves.setMaxDistance(5);
+        waves.setRefDistance(1);
+        
+        nature.setVolume(3);
+        audioRenderer.playSourceInstance(waves);
+        audioRenderer.playSource(nature);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf)
+    {
+
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/TestBumpModel.java b/engine/src/android/jme3test/android/TestBumpModel.java
new file mode 100644
index 0000000..196286f
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestBumpModel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.ogre.OgreMeshKey;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestBumpModel extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestBumpModel app = new TestBumpModel();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Spatial signpost = (Spatial) assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml"));
+        signpost.setMaterial( (Material) assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"));
+        TangentBinormalGenerator.generate(signpost);
+        rootNode.attachChild(signpost);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial( (Material) assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        // flourescent main light
+        pl = new PointLight();
+        pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f));
+        rootNode.addLight(pl);
+        
+        AmbientLight al = new AmbientLight();
+        al.setColor(new ColorRGBA(0.44f, 0.40f, 0.20f, 1.0f));
+        rootNode.addLight(al);
+        
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1,-1,-1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.92f, 0.85f, 0.8f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/TestMovingParticle.java b/engine/src/android/jme3test/android/TestMovingParticle.java
new file mode 100644
index 0000000..eda7db9
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestMovingParticle.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+
+/**
+ * Particle that moves in a circle.
+ *
+ * @author Kirill Vainer
+ */
+public class TestMovingParticle extends SimpleApplication {
+    
+    private ParticleEmitter emit;
+    private float angle = 0;
+    
+    public static void main(String[] args) {
+        TestMovingParticle app = new TestMovingParticle();
+        app.start();
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        emit = new ParticleEmitter("Emitter", Type.Triangle, 300);
+        emit.setGravity(0, 0, 0);
+        emit.setVelocityVariation(1);
+        emit.setLowLife(1);
+        emit.setHighLife(1);
+        emit.setInitialVelocity(new Vector3f(0, .5f, 0));
+        emit.setImagesX(15);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
+        emit.setMaterial(mat);
+        
+        rootNode.attachChild(emit);
+        
+        AmbientLight al = new AmbientLight();
+        al.setColor(new ColorRGBA(0.84f, 0.80f, 0.80f, 1.0f));
+        rootNode.addLight(al);
+        
+        
+        
+        inputManager.addListener(new ActionListener() {
+            
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if ("setNum".equals(name) && isPressed) {
+                    emit.setNumParticles(1000);
+                }
+            }
+        }, "setNum");
+        
+        inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE));
+    }
+    
+    @Override
+    public void simpleUpdate(float tpf) {
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        float x = FastMath.cos(angle) * 2;
+        float y = FastMath.sin(angle) * 2;
+        emit.setLocalTranslation(x, 0, y);
+    }
+}
diff --git a/engine/src/android/jme3test/android/TestNormalMapping.java b/engine/src/android/jme3test/android/TestNormalMapping.java
new file mode 100644
index 0000000..2dde1a4
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestNormalMapping.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestNormalMapping extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestNormalMapping app = new TestNormalMapping();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Sphere sphMesh = new Sphere(32, 32, 1);
+        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
+        sphMesh.updateGeometry(32, 32, 1, false, false);
+        TangentBinormalGenerator.generate(sphMesh);
+
+        Geometry sphere = new Geometry("Rock Ball", sphMesh);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        sphere.setMaterial(mat);
+        rootNode.attachChild(sphere);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setPosition(new Vector3f(0f, 0f, 4f));
+        rootNode.addLight(pl);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(new ColorRGBA(0.44f, 0.40f, 0.20f, 1.0f));
+        rootNode.addLight(al);
+       
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1,-1,-1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.92f, 0.85f, 0.8f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/TestSkyLoadingLagoon.java b/engine/src/android/jme3test/android/TestSkyLoadingLagoon.java
new file mode 100644
index 0000000..507c661
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestSkyLoadingLagoon.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Texture;
+import com.jme3.util.SkyFactory;
+
+public class TestSkyLoadingLagoon extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestSkyLoadingLagoon app = new TestSkyLoadingLagoon();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        
+        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
+        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
+        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
+        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
+        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
+        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
+        
+        
+        /*
+        Texture west = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_x.png");
+        Texture east = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_x.png");
+        Texture north = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_z.png");
+        Texture south = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_z.png");
+        Texture up = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_y.png");
+        Texture down = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_y.png");
+        */
+        
+        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
+        rootNode.attachChild(sky);
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/TestSkyLoadingPrimitives.java b/engine/src/android/jme3test/android/TestSkyLoadingPrimitives.java
new file mode 100644
index 0000000..282e237
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestSkyLoadingPrimitives.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Texture;
+import com.jme3.util.SkyFactory;
+
+public class TestSkyLoadingPrimitives extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestSkyLoadingPrimitives app = new TestSkyLoadingPrimitives();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        /*
+        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
+        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
+        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
+        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
+        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
+        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
+        */
+
+        Texture west = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_x.png");
+        Texture east = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_x.png");
+        Texture north = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_z.png");
+        Texture south = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_z.png");
+        Texture up = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_y.png");
+        Texture down = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_y.png");
+        
+        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
+        rootNode.attachChild(sky);
+    }
+
+}
diff --git a/engine/src/android/jme3test/android/TestUnshadedModel.java b/engine/src/android/jme3test/android/TestUnshadedModel.java
new file mode 100644
index 0000000..4e4ff8c
--- /dev/null
+++ b/engine/src/android/jme3test/android/TestUnshadedModel.java
@@ -0,0 +1,44 @@
+package jme3test.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestUnshadedModel extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestUnshadedModel app = new TestUnshadedModel();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Sphere sphMesh = new Sphere(32, 32, 1);
+        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
+        sphMesh.updateGeometry(32, 32, 1, false, false);
+        TangentBinormalGenerator.generate(sphMesh);
+
+        Geometry sphere = new Geometry("Rock Ball", sphMesh);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        mat.setColor("Ambient", ColorRGBA.DarkGray);
+        mat.setColor("Diffuse", ColorRGBA.White);
+        mat.setBoolean("UseMaterialColors", true);
+        sphere.setMaterial(mat);
+        rootNode.attachChild(sphere);
+
+        PointLight pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setPosition(new Vector3f(4f, 0f, 0f));
+        rootNode.addLight(pl);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White);
+        rootNode.addLight(al);
+    }
+}
diff --git a/engine/src/android/jme3tools/android/Fixed.java b/engine/src/android/jme3tools/android/Fixed.java
new file mode 100644
index 0000000..4420999
--- /dev/null
+++ b/engine/src/android/jme3tools/android/Fixed.java
@@ -0,0 +1,431 @@
+package jme3tools.android;
+
+import java.util.Random;
+
+/**
+ *	Fixed point maths class. This can be tailored for specific needs by
+ *	changing the bits allocated to the 'fraction' part (see <code>FIXED_POINT
+ *	</code>, which would also require <code>SIN_PRECALC</code> and <code>
+ *	COS_PRECALC</code> updating).
+ *
+ *  <p><a href="http://blog.numfum.com/2007/09/java-fixed-point-maths.html">
+ *  http://blog.numfum.com/2007/09/java-fixed-point-maths.html</a></p>
+ *
+ *	@version 1.0
+ *	@author CW
+ * 
+ * @deprecated Most devices with OpenGL ES 2.0 have an FPU. Please use
+ * floats instead of this class for decimal math.
+ */
+@Deprecated
+public final class Fixed {
+
+    /**
+     *	Number of bits used for 'fraction'.
+     */
+    public static final int FIXED_POINT = 16;
+    /**
+     *	Decimal one as represented by the Fixed class.
+     */
+    public static final int ONE = 1 << FIXED_POINT;
+    /**
+     *	Half in fixed point.
+     */
+    public static final int HALF = ONE >> 1;
+    /**
+     *	Quarter circle resolution for trig functions (should be a power of
+     *	two). This is the number of discrete steps in 90 degrees.
+     */
+    public static final int QUARTER_CIRCLE = 64;
+    /**
+     *	Mask used to limit angles to one revolution. If a quarter circle is 64
+     * (i.e. 90 degrees is broken into 64 steps) then the mask is 255.
+     */
+    public static final int FULL_CIRCLE_MASK = QUARTER_CIRCLE * 4 - 1;
+    /**
+     *	The trig table is generated at a higher precision than the typical
+     *	16.16 format used for the rest of the fixed point maths. The table
+     *	values are then shifted to match the actual fixed point used.
+     */
+    private static final int TABLE_SHIFT = 30;
+    /**
+     *	Equivalent to: sin((2 * PI) / (QUARTER_CIRCLE * 4))
+     *	<p>
+     *	Note: if either QUARTER_CIRCLE or TABLE_SHIFT is changed this value
+     *	will need recalculating (put the above formular into a calculator set
+     *	radians, then shift the result by <code>TABLE_SHIFT</code>).
+     */
+    private static final int SIN_PRECALC = 26350943;
+    /**
+     *	Equivalent to: cos((2 * PI) / (QUARTER_CIRCLE * 4)) * 2
+     *
+     *	Note: if either QUARTER_CIRCLE or TABLE_SHIFT is changed this value
+     *	will need recalculating ((put the above formular into a calculator set
+     *	radians, then shift the result by <code>TABLE_SHIFT</code>).
+     */
+    private static final int COS_PRECALC = 2146836866;
+    /**
+     *	One quarter sine wave as fixed point values.
+     */
+    private static final int[] SINE_TABLE = new int[QUARTER_CIRCLE + 1];
+    /**
+     *	Scale value for indexing ATAN_TABLE[].
+     */
+    private static final int ATAN_SHIFT;
+    /**
+     *	Reverse atan lookup table.
+     */
+    private static final byte[] ATAN_TABLE;
+    /**
+     *	ATAN_TABLE.length
+     */
+    private static final int ATAN_TABLE_LEN;
+
+    /*
+     *	Generates the tables and fills in any remaining static ints.
+     */
+    static {
+        // Generate the sine table using recursive synthesis.
+        SINE_TABLE[0] = 0;
+        SINE_TABLE[1] = SIN_PRECALC;
+        for (int n = 2; n < QUARTER_CIRCLE + 1; n++) {
+            SINE_TABLE[n] = (int) (((long) SINE_TABLE[n - 1] * COS_PRECALC) >> TABLE_SHIFT) - SINE_TABLE[n - 2];
+        }
+        // Scale the values to the fixed point format used.
+        for (int n = 0; n < QUARTER_CIRCLE + 1; n++) {
+            SINE_TABLE[n] = SINE_TABLE[n] + (1 << (TABLE_SHIFT - FIXED_POINT - 1)) >> TABLE_SHIFT - FIXED_POINT;
+        }
+
+        // Calculate a shift used to scale atan lookups
+        int rotl = 0;
+        int tan0 = tan(0);
+        int tan1 = tan(1);
+        while (rotl < 32) {
+            if ((tan1 >>= 1) > (tan0 >>= 1)) {
+                rotl++;
+            } else {
+                break;
+            }
+        }
+        ATAN_SHIFT = rotl;
+        // Create the a table of tan values
+        int[] lut = new int[QUARTER_CIRCLE];
+        for (int n = 0; n < QUARTER_CIRCLE; n++) {
+            lut[n] = tan(n) >> rotl;
+        }
+        ATAN_TABLE_LEN = lut[QUARTER_CIRCLE - 1];
+        // Then from the tan values create a reverse lookup
+        ATAN_TABLE = new byte[ATAN_TABLE_LEN];
+        for (byte n = 0; n < QUARTER_CIRCLE - 1; n++) {
+            int min = lut[n];
+            int max = lut[n + 1];
+            for (int i = min; i < max; i++) {
+                ATAN_TABLE[i] = n;
+            }
+        }
+    }
+    /**
+     *	How many decimal places to use when converting a fixed point value to
+     *	a decimal string.
+     *
+     *	@see #toString
+     */
+    private static final int STRING_DECIMAL_PLACES = 2;
+    /**
+     *	Value to add in order to round down a fixed point number when
+     *	converting to a string.
+     */
+    private static final int STRING_DECIMAL_PLACES_ROUND;
+
+    static {
+        int i = 10;
+        for (int n = 1; n < STRING_DECIMAL_PLACES; n++) {
+            i *= i;
+        }
+        if (STRING_DECIMAL_PLACES == 0) {
+            STRING_DECIMAL_PLACES_ROUND = ONE / 2;
+        } else {
+            STRING_DECIMAL_PLACES_ROUND = ONE / (2 * i);
+        }
+    }
+    /**
+     *	Random number generator. The standard <code>java.utll.Random</code> is
+     *	used since it is available to both J2ME and J2SE. If a guaranteed
+     *	sequence is required this would not be adequate.
+     */
+    private static Random rng = null;
+
+    /**
+     *	Fixed can't be instantiated.
+     */
+    private Fixed() {
+    }
+
+    /**
+     * Returns an integer as a fixed point value.
+     */
+    public static int intToFixed(int n) {
+        return n << FIXED_POINT;
+    }
+
+    /**
+     * Returns a fixed point value as a float.
+     */
+    public static float fixedToFloat(int i) {
+        float fp = i;
+        fp = fp / ((float) ONE);
+        return fp;
+    }
+
+    /**
+     * Returns a float as a fixed point value.
+     */
+    public static int floatToFixed(float fp) {
+        return (int) (fp * ((float) ONE));
+    }
+
+    /**
+     *	Converts a fixed point value into a decimal string.
+     */
+    public static String toString(int n) {
+        StringBuffer sb = new StringBuffer(16);
+        sb.append((n += STRING_DECIMAL_PLACES_ROUND) >> FIXED_POINT);
+        sb.append('.');
+        n &= ONE - 1;
+        for (int i = 0; i < STRING_DECIMAL_PLACES; i++) {
+            n *= 10;
+            sb.append((n / ONE) % 10);
+        }
+        return sb.toString();
+    }
+
+    /**
+     *	Multiplies two fixed point values and returns the result.
+     */
+    public static int mul(int a, int b) {
+        return (int) ((long) a * (long) b >> FIXED_POINT);
+    }
+
+    /**
+     *	Divides two fixed point values and returns the result.
+     */
+    public static int div(int a, int b) {
+        return (int) (((long) a << FIXED_POINT * 2) / (long) b >> FIXED_POINT);
+    }
+
+    /**
+     *	Sine of an angle.
+     *
+     *	@see #QUARTER_CIRCLE
+     */
+    public static int sin(int n) {
+        n &= FULL_CIRCLE_MASK;
+        if (n < QUARTER_CIRCLE * 2) {
+            if (n < QUARTER_CIRCLE) {
+                return SINE_TABLE[n];
+            } else {
+                return SINE_TABLE[QUARTER_CIRCLE * 2 - n];
+            }
+        } else {
+            if (n < QUARTER_CIRCLE * 3) {
+                return -SINE_TABLE[n - QUARTER_CIRCLE * 2];
+            } else {
+                return -SINE_TABLE[QUARTER_CIRCLE * 4 - n];
+            }
+        }
+    }
+
+    /**
+     *	Cosine of an angle.
+     *
+     *	@see #QUARTER_CIRCLE
+     */
+    public static int cos(int n) {
+        n &= FULL_CIRCLE_MASK;
+        if (n < QUARTER_CIRCLE * 2) {
+            if (n < QUARTER_CIRCLE) {
+                return SINE_TABLE[QUARTER_CIRCLE - n];
+            } else {
+                return -SINE_TABLE[n - QUARTER_CIRCLE];
+            }
+        } else {
+            if (n < QUARTER_CIRCLE * 3) {
+                return -SINE_TABLE[QUARTER_CIRCLE * 3 - n];
+            } else {
+                return SINE_TABLE[n - QUARTER_CIRCLE * 3];
+            }
+        }
+    }
+
+    /**
+     *	Tangent of an angle.
+     *
+     *	@see #QUARTER_CIRCLE
+     */
+    public static int tan(int n) {
+        return div(sin(n), cos(n));
+    }
+
+    /**
+     *	Returns the arc tangent of an angle.
+     */
+    public static int atan(int n) {
+        n = n + (1 << (ATAN_SHIFT - 1)) >> ATAN_SHIFT;
+        if (n < 0) {
+            if (n <= -ATAN_TABLE_LEN) {
+                return -(QUARTER_CIRCLE - 1);
+            }
+            return -ATAN_TABLE[-n];
+        } else {
+            if (n >= ATAN_TABLE_LEN) {
+                return QUARTER_CIRCLE - 1;
+            }
+            return ATAN_TABLE[n];
+        }
+    }
+
+    /**
+     *	Returns the polar angle of a rectangular coordinate.
+     */
+    public static int atan(int x, int y) {
+        int n = atan(div(x, abs(y) + 1)); // kludge to prevent ArithmeticException
+        if (y > 0) {
+            return n;
+        }
+        if (y < 0) {
+            if (x < 0) {
+                return -QUARTER_CIRCLE * 2 - n;
+            }
+            if (x > 0) {
+                return QUARTER_CIRCLE * 2 - n;
+            }
+            return QUARTER_CIRCLE * 2;
+        }
+        if (x > 0) {
+            return QUARTER_CIRCLE;
+        }
+        return -QUARTER_CIRCLE;
+    }
+
+    /**
+     *	Rough calculation of the hypotenuse. Whilst not accurate it is very fast.
+     *	<p>
+     *	Derived from a piece in Graphics Gems.
+     */
+    public static int hyp(int x1, int y1, int x2, int y2) {
+        if ((x2 -= x1) < 0) {
+            x2 = -x2;
+        }
+        if ((y2 -= y1) < 0) {
+            y2 = -y2;
+        }
+        return x2 + y2 - (((x2 > y2) ? y2 : x2) >> 1);
+    }
+
+    /**
+     *	Fixed point square root.
+     *	<p>
+     *	Derived from a 1993 Usenet algorithm posted by Christophe Meessen.
+     */
+    public static int sqrt(int n) {
+        if (n <= 0) {
+            return 0;
+        }
+        long sum = 0;
+        int bit = 0x40000000;
+        while (bit >= 0x100) { // lower values give more accurate results
+            long tmp = sum | bit;
+            if (n >= tmp) {
+                n -= tmp;
+                sum = tmp + bit;
+            }
+            bit >>= 1;
+            n <<= 1;
+        }
+        return (int) (sum >> 16 - (FIXED_POINT / 2));
+    }
+
+    /**
+     *	Returns the absolute value.
+     */
+    public static int abs(int n) {
+        return (n < 0) ? -n : n;
+    }
+
+    /**
+     *	Returns the sign of a value, -1 for negative numbers, otherwise 1.
+     */
+    public static int sgn(int n) {
+        return (n < 0) ? -1 : 1;
+    }
+
+    /**
+     *	Returns the minimum of two values.
+     */
+    public static int min(int a, int b) {
+        return (a < b) ? a : b;
+    }
+
+    /**
+     *	Returns the maximum of two values.
+     */
+    public static int max(int a, int b) {
+        return (a > b) ? a : b;
+    }
+
+    /**
+     *	Clamps the value n between min and max.
+     */
+    public static int clamp(int n, int min, int max) {
+        return (n < min) ? min : (n > max) ? max : n;
+    }
+
+    /**
+     *	Wraps the value n between 0 and the required limit.
+     */
+    public static int wrap(int n, int limit) {
+        return ((n %= limit) < 0) ? limit + n : n;
+    }
+
+    /**
+     *	Returns the nearest int to a fixed point value. Equivalent to <code>
+     *	Math.round()</code> in the standard library.
+     */
+    public static int round(int n) {
+        return n + HALF >> FIXED_POINT;
+    }
+
+    /**
+     *	Returns the nearest int rounded down from a fixed point value.
+     *	Equivalent to <code>Math.floor()</code> in the standard library.
+     */
+    public static int floor(int n) {
+        return n >> FIXED_POINT;
+    }
+
+    /**
+     *	Returns the nearest int rounded up from a fixed point value.
+     *	Equivalent to <code>Math.ceil()</code> in the standard library.
+     */
+    public static int ceil(int n) {
+        return n + (ONE - 1) >> FIXED_POINT;
+    }
+
+    /**
+     *	Returns a fixed point value greater than or equal to decimal 0.0 and
+     *	less than 1.0 (in 16.16 format this would be 0 to 65535 inclusive).
+     */
+    public static int rand() {
+        if (rng == null) {
+            rng = new Random();
+        }
+        return rng.nextInt() >>> (32 - FIXED_POINT);
+    }
+
+    /**
+     *	Returns a random number between 0 and <code>n</code> (exclusive).
+     */
+    public static int rand(int n) {
+        return (rand() * n) >> FIXED_POINT;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/android/res/layout/about.xml b/engine/src/android/res/layout/about.xml
new file mode 100644
index 0000000..1d8c2b0
--- /dev/null
+++ b/engine/src/android/res/layout/about.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	android:orientation="vertical"
+	android:layout_width="fill_parent"
+	android:layout_height="fill_parent"
+>
+
+    <LinearLayout
+		android:id="@+id/buttonsContainer"
+		xmlns:android="http://schemas.android.com/apk/res/android"
+		android:orientation="vertical"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent"
+	>
+        <TextView  
+			android:layout_width="fill_parent" 
+			android:layout_height="wrap_content" 
+			android:text="copyright (c) 2009-2010 JMonkeyEngine"
+		/>
+
+        <TextView  
+			android:layout_width="fill_parent" 
+			android:layout_height="wrap_content" 
+			android:text="http://www.jmonkeyengine.org"
+		/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/engine/src/android/res/layout/tests.xml b/engine/src/android/res/layout/tests.xml
new file mode 100644
index 0000000..70bd5ec
--- /dev/null
+++ b/engine/src/android/res/layout/tests.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	android:orientation="vertical"
+	android:layout_width="fill_parent"
+	android:layout_height="fill_parent"
+>
+    <LinearLayout
+		android:id="@+id/buttonsContainer"
+		xmlns:android="http://schemas.android.com/apk/res/android"
+		android:orientation="vertical"
+		android:layout_width="fill_parent"
+		android:layout_height="fill_parent">
+<!--
+		<Button
+			android:id="@+id/SimpleTextured"
+			android:layout_width="wrap_content"
+			android:layout_height="fill_parent"
+			android:text="Simple Textured"
+			android:layout_weight="1"
+		/>
+-->
+    </LinearLayout>
+</LinearLayout>
diff --git a/engine/src/android/res/menu/options.xml b/engine/src/android/res/menu/options.xml
new file mode 100644
index 0000000..8efec52
--- /dev/null
+++ b/engine/src/android/res/menu/options.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/about_button"
+        android:title="@string/about"
+	/>
+
+    <item
+        android:id="@+id/quit_button"
+        android:title="@string/quit"
+	/>
+</menu>
diff --git a/engine/src/android/res/values/strings.xml b/engine/src/android/res/values/strings.xml
new file mode 100644
index 0000000..92d63fa
--- /dev/null
+++ b/engine/src/android/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">JMEAndroidTest</string>
+    <string name="about">About</string>
+    <string name="quit">Quit</string>
+</resources>
diff --git a/engine/src/blender/Common/MatDefs/Texture3D/tex3D.frag b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.frag
new file mode 100644
index 0000000..55862ac
--- /dev/null
+++ b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.frag
@@ -0,0 +1,7 @@
+uniform sampler3D m_Texture;

+

+varying vec3 texCoord;

+

+void main(){

+    gl_FragColor= texture3D(m_Texture,texCoord);

+}
\ No newline at end of file
diff --git a/engine/src/blender/Common/MatDefs/Texture3D/tex3D.j3md b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.j3md
new file mode 100644
index 0000000..1ba2605
--- /dev/null
+++ b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.j3md
@@ -0,0 +1,16 @@
+MaterialDef My MaterialDef {
+
+    MaterialParameters {
+        Texture3D Texture
+    }
+
+    Technique {
+        VertexShader GLSL100:   jme3test/texture/tex3D.vert
+        FragmentShader GLSL100: jme3test/texture/tex3D.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+    }
+
+}
diff --git a/engine/src/blender/Common/MatDefs/Texture3D/tex3D.vert b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.vert
new file mode 100644
index 0000000..f91b7b3
--- /dev/null
+++ b/engine/src/blender/Common/MatDefs/Texture3D/tex3D.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inTexCoord;

+attribute vec3 inPosition;

+

+varying vec3 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);

+    texCoord=inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java
new file mode 100644
index 0000000..a098682
--- /dev/null
+++ b/engine/src/blender/com/jme3/asset/BlenderKey.java
@@ -0,0 +1,730 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.asset;

+

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.collision.Collidable;

+import com.jme3.collision.CollisionResults;

+import com.jme3.collision.UnsupportedCollisionException;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.light.AmbientLight;

+import com.jme3.light.Light;

+import com.jme3.material.Material;

+import com.jme3.material.RenderState.FaceCullMode;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.Node;

+import com.jme3.scene.SceneGraphVisitor;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.ogre.AnimData;

+import com.jme3.texture.Texture;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Queue;

+

+/**

+ * Blender key. Contains path of the blender file and its loading properties.

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class BlenderKey extends ModelKey {

+

+	protected static final int					DEFAULT_FPS				= 25;

+	/**

+	 * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time

+	 * between the frames.

+	 */

+	protected int								fps						= DEFAULT_FPS;

+	/** Width of generated textures (in pixels). */

+	protected int								generatedTextureWidth	= 60;

+	/** Height of generated textures (in pixels). */

+	protected int								generatedTextureHeight	= 60;

+	/** Depth of generated textures (in pixels). */

+	protected int								generatedTextureDepth	= 60;

+	/**

+	 * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.

+	 */

+	protected int								featuresToLoad			= FeaturesToLoad.ALL;

+	/** This variable determines if assets that are not linked to the objects should be loaded. */

+	protected boolean							loadUnlinkedAssets;

+	/** The root path for all the assets. */

+	protected String							assetRootPath;

+	/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */

+	protected boolean							fixUpAxis				= true;

+	/**

+	 * The name of world settings that the importer will use. If not set or specified name does not occur in the file

+	 * then the first world settings in the file will be used.

+	 */

+	protected String							usedWorld;

+	/**

+	 * User's default material that is set fo objects that have no material definition in blender. The default value is

+	 * null. If the value is null the importer will use its own default material (gray color - like in blender).

+	 */

+	protected Material							defaultMaterial;

+	/** Face cull mode. By default it is disabled. */

+	protected FaceCullMode						faceCullMode			= FaceCullMode.Off;

+	/**

+	 * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.

+	 * If set to -1 then the current layer will be loaded.

+	 */

+	protected int								layersToLoad			= -1;

+

+	/**

+	 * Constructor used by serialization mechanisms.

+	 */

+	public BlenderKey() {}

+

+	/**

+	 * Constructor. Creates a key for the given file name.

+	 * @param name

+	 *        the name (path) of a file

+	 */

+	public BlenderKey(String name) {

+		super(name);

+	}

+

+	/**

+	 * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25.

+	 * @return the frames per second amount

+	 */

+	public int getFps() {

+		return fps;

+	}

+

+	/**

+	 * This method sets frames per second amount.

+	 * @param fps

+	 *        the frames per second amount

+	 */

+	public void setFps(int fps) {

+		this.fps = fps;

+	}

+

+	/**

+	 * This method sets the width of generated texture (in pixels). By default the value is 140 px.

+	 * @param generatedTextureWidth

+	 *        the width of generated texture

+	 */

+	public void setGeneratedTextureWidth(int generatedTextureWidth) {

+		this.generatedTextureWidth = generatedTextureWidth;

+	}

+

+	/**

+	 * This method returns the width of generated texture (in pixels). By default the value is 140 px.

+	 * @return the width of generated texture

+	 */

+	public int getGeneratedTextureWidth() {

+		return generatedTextureWidth;

+	}

+

+	/**

+	 * This method sets the height of generated texture (in pixels). By default the value is 20 px.

+	 * @param generatedTextureHeight

+	 *        the height of generated texture

+	 */

+	public void setGeneratedTextureHeight(int generatedTextureHeight) {

+		this.generatedTextureHeight = generatedTextureHeight;

+	}

+

+	/**

+	 * This method returns the height of generated texture (in pixels). By default the value is 20 px.

+	 * @return the height of generated texture

+	 */

+	public int getGeneratedTextureHeight() {

+		return generatedTextureHeight;

+	}

+	

+	/**

+	 * This method sets the depth of generated texture (in pixels). By default the value is 20 px.

+	 * @param generatedTextureDepth

+	 *        the depth of generated texture

+	 */

+	public void setGeneratedTextureDepth(int generatedTextureDepth) {

+		this.generatedTextureDepth = generatedTextureDepth;

+	}

+

+	/**

+	 * This method returns the depth of generated texture (in pixels). By default the value is 20 px.

+	 * @return the depth of generated texture

+	 */

+	public int getGeneratedTextureDepth() {

+		return generatedTextureDepth;

+	}

+

+	/**

+	 * This method returns the face cull mode.

+	 * @return the face cull mode

+	 */

+	public FaceCullMode getFaceCullMode() {

+		return faceCullMode;

+	}

+

+	/**

+	 * This method sets the face cull mode.

+	 * @param faceCullMode

+	 *        the face cull mode

+	 */

+	public void setFaceCullMode(FaceCullMode faceCullMode) {

+		this.faceCullMode = faceCullMode;

+	}

+

+	/**

+	 * This method sets layers to be loaded.

+	 * @param layersToLoad

+	 *        layers to be loaded

+	 */

+	public void setLayersToLoad(int layersToLoad) {

+		this.layersToLoad = layersToLoad;

+	}

+

+	/**

+	 * This method returns layers to be loaded.

+	 * @return layers to be loaded

+	 */

+	public int getLayersToLoad() {

+		return layersToLoad;

+	}

+

+	/**

+	 * This method sets the asset root path.

+	 * @param assetRootPath

+	 *        the assets root path

+	 */

+	public void setAssetRootPath(String assetRootPath) {

+		this.assetRootPath = assetRootPath;

+	}

+

+	/**

+	 * This method returns the asset root path.

+	 * @return the asset root path

+	 */

+	public String getAssetRootPath() {

+		return assetRootPath;

+	}

+

+	/**

+	 * This method adds features to be loaded.

+	 * @param featuresToLoad

+	 *        bitwise flag of FeaturesToLoad interface values

+	 */

+	public void includeInLoading(int featuresToLoad) {

+		this.featuresToLoad |= featuresToLoad;

+	}

+

+	/**

+	 * This method removes features from being loaded.

+	 * @param featuresNotToLoad

+	 *        bitwise flag of FeaturesToLoad interface values

+	 */

+	public void excludeFromLoading(int featuresNotToLoad) {

+		this.featuresToLoad &= ~featuresNotToLoad;

+	}

+

+	/**

+	 * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by

+	 * the blender file loader.

+	 * @return features that will be loaded by the blender file loader

+	 */

+	public int getFeaturesToLoad() {

+		return featuresToLoad;

+	}

+

+	/**

+	 * This method determines if unlinked assets should be loaded.

+	 * If not then only objects on selected layers will be loaded and their assets if required.

+	 * If yes then all assets will be loaded even if they are on inactive layers or are not linked

+	 * to anything.

+	 * @return <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise

+	 */

+	public boolean isLoadUnlinkedAssets() {

+		return loadUnlinkedAssets;

+	}

+

+	/**

+	 * This method sets if unlinked assets should be loaded.

+	 * If not then only objects on selected layers will be loaded and their assets if required.

+	 * If yes then all assets will be loaded even if they are on inactive layers or are not linked

+	 * to anything.

+	 * @param loadUnlinkedAssets

+	 *        <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise

+	 */

+	public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) {

+		this.loadUnlinkedAssets = loadUnlinkedAssets;

+	}

+

+	/**

+	 * This method creates an object where loading results will be stores. Only those features will be allowed to store

+	 * that were specified by features-to-load flag.

+	 * @return an object to store loading results

+	 */

+	public LoadingResults prepareLoadingResults() {

+		return new LoadingResults(featuresToLoad);

+	}

+

+	/**

+	 * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y

+	 * is up axis.

+	 * @param fixUpAxis

+	 *        the up axis state variable

+	 */

+	public void setFixUpAxis(boolean fixUpAxis) {

+		this.fixUpAxis = fixUpAxis;

+	}

+

+	/**

+	 * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By

+	 * default Y is up axis.

+	 * @return the up axis state variable

+	 */

+	public boolean isFixUpAxis() {

+		return fixUpAxis;

+	}

+

+	/**

+	 * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is

+	 * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used

+	 * during loading (assumin any exists in the file).

+	 * @param usedWorld

+	 *        the name of the WORLD block used during loading

+	 */

+	public void setUsedWorld(String usedWorld) {

+		this.usedWorld = usedWorld;

+	}

+

+	/**

+	 * This mehtod returns the name of the WORLD data block taht should be used during file loading.

+	 * @return the name of the WORLD block used during loading

+	 */

+	public String getUsedWorld() {

+		return usedWorld;

+	}

+

+	/**

+	 * This method sets the default material for objects.

+	 * @param defaultMaterial

+	 *        the default material

+	 */

+	public void setDefaultMaterial(Material defaultMaterial) {

+		this.defaultMaterial = defaultMaterial;

+	}

+

+	/**

+	 * This method returns the default material.

+	 * @return the default material

+	 */

+	public Material getDefaultMaterial() {

+		return defaultMaterial;

+	}

+

+	@Override

+	public void write(JmeExporter e) throws IOException {

+		super.write(e);

+		OutputCapsule oc = e.getCapsule(this);

+		oc.write(fps, "fps", DEFAULT_FPS);

+		oc.write(generatedTextureWidth, "generated-texture-width", 20);

+		oc.write(generatedTextureHeight, "generated-texture-height", 20);

+		oc.write(generatedTextureDepth, "generated-texture-depth", 20);

+		oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);

+		oc.write(loadUnlinkedAssets, "load-unlinked-assets", false);

+		oc.write(assetRootPath, "asset-root-path", null);

+		oc.write(fixUpAxis, "fix-up-axis", true);

+		oc.write(usedWorld, "used-world", null);

+		oc.write(defaultMaterial, "default-material", null);

+		oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);

+		oc.write(layersToLoad, "layers-to-load", -1);

+	}

+

+	@Override

+	public void read(JmeImporter e) throws IOException {

+		super.read(e);

+		InputCapsule ic = e.getCapsule(this);

+		fps = ic.readInt("fps", DEFAULT_FPS);

+		generatedTextureWidth = ic.readInt("generated-texture-width", 20);

+		generatedTextureHeight = ic.readInt("generated-texture-height", 20);

+		generatedTextureDepth = ic.readInt("generated-texture-depth", 20);

+		featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);

+		loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false);

+		assetRootPath = ic.readString("asset-root-path", null);

+		fixUpAxis = ic.readBoolean("fix-up-axis", true);

+		usedWorld = ic.readString("used-world", null);

+		defaultMaterial = (Material) ic.readSavable("default-material", null);

+		faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);

+		layersToLoad = ic.readInt("layers-to=load", -1);

+	}

+

+	@Override

+	public int hashCode() {

+		final int prime = 31;

+		int result = super.hashCode();

+		result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());

+		result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());

+		result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());

+		result = prime * result + featuresToLoad;

+		result = prime * result + (fixUpAxis ? 1231 : 1237);

+		result = prime * result + fps;

+		result = prime * result + generatedTextureDepth;

+		result = prime * result + generatedTextureHeight;

+		result = prime * result + generatedTextureWidth;

+		result = prime * result + layersToLoad;

+		result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);

+		result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());

+		return result;

+	}

+

+	@Override

+	public boolean equals(Object obj) {

+		if (this == obj) {

+			return true;

+		}

+		if (!super.equals(obj)) {

+			return false;

+		}

+		if (this.getClass() != obj.getClass()) {

+			return false;

+		}

+		BlenderKey other = (BlenderKey) obj;

+		if (assetRootPath == null) {

+			if (other.assetRootPath != null) {

+				return false;

+			}

+		} else if (!assetRootPath.equals(other.assetRootPath)) {

+			return false;

+		}

+		if (defaultMaterial == null) {

+			if (other.defaultMaterial != null) {

+				return false;

+			}

+		} else if (!defaultMaterial.equals(other.defaultMaterial)) {

+			return false;

+		}

+		if (faceCullMode != other.faceCullMode) {

+			return false;

+		}

+		if (featuresToLoad != other.featuresToLoad) {

+			return false;

+		}

+		if (fixUpAxis != other.fixUpAxis) {

+			return false;

+		}

+		if (fps != other.fps) {

+			return false;

+		}

+		if (generatedTextureDepth != other.generatedTextureDepth) {

+			return false;

+		}

+		if (generatedTextureHeight != other.generatedTextureHeight) {

+			return false;

+		}

+		if (generatedTextureWidth != other.generatedTextureWidth) {

+			return false;

+		}

+		if (layersToLoad != other.layersToLoad) {

+			return false;

+		}

+		if (loadUnlinkedAssets != other.loadUnlinkedAssets) {

+			return false;

+		}

+		if (usedWorld == null) {

+			if (other.usedWorld != null) {

+				return false;

+			}

+		} else if (!usedWorld.equals(other.usedWorld)) {

+			return false;

+		}

+		return true;

+	}

+

+	/**

+	 * This interface describes the features of the scene that are to be loaded.

+	 * @author Marcin Roguski (Kaelthas)

+	 */

+	public static interface FeaturesToLoad {

+

+		int	SCENES		= 0x0000FFFF;

+		int	OBJECTS		= 0x0000000B;

+		int	ANIMATIONS	= 0x00000004;

+		int	MATERIALS	= 0x00000003;

+		int	TEXTURES	= 0x00000001;

+		int	CAMERAS		= 0x00000020;

+		int	LIGHTS		= 0x00000010;

+		int	ALL			= 0xFFFFFFFF;

+	}

+

+	/**

+	 * This class holds the loading results according to the given loading flag.

+	 * @author Marcin Roguski (Kaelthas)

+	 */

+	public static class LoadingResults extends Spatial {

+

+		/** Bitwise mask of features that are to be loaded. */

+		private final int		featuresToLoad;

+		/** The scenes from the file. */

+		private List<Node>		scenes;

+		/** Objects from all scenes. */

+		private List<Node>		objects;

+		/** Materials from all objects. */

+		private List<Material>	materials;

+		/** Textures from all objects. */

+		private List<Texture>	textures;

+		/** Animations of all objects. */

+		private List<AnimData>	animations;

+		/** All cameras from the file. */

+		private List<Camera>	cameras;

+		/** All lights from the file. */

+		private List<Light>		lights;

+

+		/**

+		 * Private constructor prevents users to create an instance of this class from outside the

+		 * @param featuresToLoad

+		 *        bitwise mask of features that are to be loaded

+		 * @see FeaturesToLoad FeaturesToLoad

+		 */

+		private LoadingResults(int featuresToLoad) {

+			this.featuresToLoad = featuresToLoad;

+			if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) {

+				scenes = new ArrayList<Node>();

+			}

+			if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {

+				objects = new ArrayList<Node>();

+				if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {

+					materials = new ArrayList<Material>();

+					if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {

+						textures = new ArrayList<Texture>();

+					}

+				}

+				if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {

+					animations = new ArrayList<AnimData>();

+				}

+			}

+			if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {

+				cameras = new ArrayList<Camera>();

+			}

+			if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {

+				lights = new ArrayList<Light>();

+			}

+		}

+

+		/**

+		 * This method returns a bitwise flag describing what features of the blend file will be included in the result.

+		 * @return bitwise mask of features that are to be loaded

+		 * @see FeaturesToLoad FeaturesToLoad

+		 */

+		public int getLoadedFeatures() {

+			return featuresToLoad;

+		}

+

+		/**

+		 * This method adds a scene to the result set.

+		 * @param scene

+		 *        scene to be added to the result set

+		 */

+		public void addScene(Node scene) {

+			if (scenes != null) {

+				scenes.add(scene);

+			}

+		}

+

+		/**

+		 * This method adds an object to the result set.

+		 * @param object

+		 *        object to be added to the result set

+		 */

+		public void addObject(Node object) {

+			if (objects != null) {

+				objects.add(object);

+			}

+		}

+

+		/**

+		 * This method adds a material to the result set.

+		 * @param material

+		 *        material to be added to the result set

+		 */

+		public void addMaterial(Material material) {

+			if (materials != null) {

+				materials.add(material);

+			}

+		}

+

+		/**

+		 * This method adds a texture to the result set.

+		 * @param texture

+		 *        texture to be added to the result set

+		 */

+		public void addTexture(Texture texture) {

+			if (textures != null) {

+				textures.add(texture);

+			}

+		}

+

+		/**

+		 * This method adds a camera to the result set.

+		 * @param camera

+		 *        camera to be added to the result set

+		 */

+		public void addCamera(Camera camera) {

+			if (cameras != null) {

+				cameras.add(camera);

+			}

+		}

+

+		/**

+		 * This method adds a light to the result set.

+		 * @param light

+		 *        light to be added to the result set

+		 */

+		@Override

+		public void addLight(Light light) {

+			if (lights != null) {

+				lights.add(light);

+			}

+		}

+

+		/**

+		 * This method returns all loaded scenes.

+		 * @return all loaded scenes

+		 */

+		public List<Node> getScenes() {

+			return scenes;

+		}

+

+		/**

+		 * This method returns all loaded objects.

+		 * @return all loaded objects

+		 */

+		public List<Node> getObjects() {

+			return objects;

+		}

+

+		/**

+		 * This method returns all loaded materials.

+		 * @return all loaded materials

+		 */

+		public List<Material> getMaterials() {

+			return materials;

+		}

+

+		/**

+		 * This method returns all loaded textures.

+		 * @return all loaded textures

+		 */

+		public List<Texture> getTextures() {

+			return textures;

+		}

+

+		/**

+		 * This method returns all loaded animations.

+		 * @return all loaded animations

+		 */

+		public List<AnimData> getAnimations() {

+			return animations;

+		}

+

+		/**

+		 * This method returns all loaded cameras.

+		 * @return all loaded cameras

+		 */

+		public List<Camera> getCameras() {

+			return cameras;

+		}

+

+		/**

+		 * This method returns all loaded lights.

+		 * @return all loaded lights

+		 */

+		public List<Light> getLights() {

+			return lights;

+		}

+

+		@Override

+		public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {

+			return 0;

+		}

+

+		@Override

+		public void updateModelBound() {}

+

+		@Override

+		public void setModelBound(BoundingVolume modelBound) {}

+

+		@Override

+		public int getVertexCount() {

+			return 0;

+		}

+

+		@Override

+		public int getTriangleCount() {

+			return 0;

+		}

+

+		@Override

+		public Spatial deepClone() {

+			return null;

+		}

+

+		@Override

+		public void depthFirstTraversal(SceneGraphVisitor visitor) {}

+

+		@Override

+		protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {}

+	}

+

+	/**

+	 * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient

+	 * light.

+	 * @author Marcin Roguski (Kaelthas)

+	 */

+	public static class WorldData {

+

+		/** The ambient light. */

+		private AmbientLight	ambientLight;

+

+		/**

+		 * This method returns the world's ambient light.

+		 * @return the world's ambient light

+		 */

+		public AmbientLight getAmbientLight() {

+			return ambientLight;

+		}

+

+		/**

+		 * This method sets the world's ambient light.

+		 * @param ambientLight

+		 *        the world's ambient light

+		 */

+		public void setAmbientLight(AmbientLight ambientLight) {

+			this.ambientLight = ambientLight;

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java b/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java
new file mode 100644
index 0000000..70f9f9e
--- /dev/null
+++ b/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java
@@ -0,0 +1,37 @@
+package com.jme3.asset;

+

+

+/**

+ * This key is mostly used to distinguish between textures that are loaded from

+ * the given assets and those being generated automatically. Every generated

+ * texture will have this kind of key attached.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class GeneratedTextureKey extends TextureKey {

+	/**

+	 * Constructor. Stores the name. Extension and folder name are empty

+	 * strings.

+	 * 

+	 * @param name

+	 *            the name of the texture

+	 */

+	public GeneratedTextureKey(String name) {

+		super(name);

+	}

+

+	@Override

+	public String getExtension() {

+		return "";

+	}

+

+	@Override

+	public String getFolder() {

+		return "";

+	}

+

+	@Override

+	public String toString() {

+		return "Generated texture [" + name + "]";

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
new file mode 100644
index 0000000..8c49804
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
@@ -0,0 +1,176 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender;

+

+import com.jme3.math.FastMath;

+import com.jme3.math.Quaternion;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.objects.Properties;

+import com.jme3.util.BufferUtils;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.List;

+

+/**

+ * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can

+ * hold the state of the calculations.

+ * @author Marcin Roguski

+ */

+public abstract class AbstractBlenderHelper {

+

+	/** The version of the blend file. */

+	protected final int	blenderVersion;

+	/** This variable indicates if the Y asxis is the UP axis or not. */

+	protected boolean						fixUpAxis;

+	/** Quaternion used to rotate data when Y is up axis. */

+	protected Quaternion					upAxisRotationQuaternion;

+	

+	/**

+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender

+	 * versions.

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public AbstractBlenderHelper(String blenderVersion, boolean fixUpAxis) {

+		this.blenderVersion = Integer.parseInt(blenderVersion);

+		this.fixUpAxis = fixUpAxis;

+		if(fixUpAxis) {

+			upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);

+		}

+	}

+	

+	/**

+	 * This method clears the state of the helper so that it can be used for different calculations of another feature.

+	 */

+	public void clearState() {}

+

+	/**

+	 * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are

+	 * being created and stored in the memory. It can be unwise especially inside loops.

+	 * @param text

+	 *        the text to be checked

+	 * @return <b>true</b> if the text is blank and <b>false</b> otherwise

+	 */

+	protected boolean isBlank(String text) {

+		if (text != null) {

+			for (int i = 0; i < text.length(); ++i) {

+				if (!Character.isWhitespace(text.charAt(i))) {

+					return false;

+				}

+			}

+		}

+		return true;

+	}

+

+        /**

+	 * Generate a new ByteBuffer using the given array of byte[4] objects. The ByteBuffer will be 4 * data.length

+	 * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc.

+	 * @param data

+	 *        list of byte[4] objects to place into a new ByteBuffer

+	 */

+	protected ByteBuffer createByteBuffer(List<byte[]> data) {

+		if (data == null) {

+			return null;

+		}

+		ByteBuffer buff = BufferUtils.createByteBuffer(4 * data.size());

+		for (byte[] v : data) {

+			if (v != null) {

+				buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]);

+			} else {

+				buff.put((byte)0).put((byte)0).put((byte)0).put((byte)0);

+			}

+		}

+		buff.flip();

+		return buff;

+	}

+        

+	/**

+	 * Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length

+	 * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc.

+	 * @param data

+	 *        list of float[4] objects to place into a new FloatBuffer

+	 */

+	protected FloatBuffer createFloatBuffer(List<float[]> data) {

+		if (data == null) {

+			return null;

+		}

+		FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size());

+		for (float[] v : data) {

+			if (v != null) {

+				buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]);

+			} else {

+				buff.put(0).put(0).put(0).put(0);

+			}

+		}

+		buff.flip();

+		return buff;

+	}

+

+	/**

+	 * This method loads the properties if they are available and defined for the structure.

+	 * @param structure

+	 *        the structure we read the properties from

+	 * @param blenderContext

+	 *        the blender context

+	 * @return loaded properties or null if they are not available

+	 * @throws BlenderFileException

+	 *         an exception is thrown when the blend file is somehow corrupted

+	 */

+	protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {

+		Properties properties = null;

+		Structure id = (Structure) structure.getFieldValue("ID");

+		if (id != null) {

+			Pointer pProperties = (Pointer) id.getFieldValue("properties");

+			if (pProperties.isNotNull()) {

+				Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0);

+				properties = new Properties();

+				properties.load(propertiesStructure, blenderContext);

+			}

+		}

+		return properties;

+	}

+	

+	/**

+	 * This method analyzes the given structure and the data contained within

+	 * blender context and decides if the feature should be loaded.

+	 * @param structure

+	 *        structure to be analyzed

+	 * @param blenderContext

+	 *        the blender context

+	 * @return <b>true</b> if the feature should be loaded and false otherwise

+	 */

+	public abstract boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext);

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
new file mode 100644
index 0000000..b42f760
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
@@ -0,0 +1,187 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender;

+

+import com.jme3.asset.AssetLoader;

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.asset.BlenderKey.WorldData;

+import com.jme3.light.AmbientLight;

+import com.jme3.light.Light;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.cameras.CameraHelper;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.lights.LightHelper;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+import com.jme3.scene.plugins.blender.meshes.MeshHelper;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class converts blender file blocks into jMonkeyEngine data structures.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */ abstract class AbstractBlenderLoader implements AssetLoader {

+	private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName());

+	

+	protected BlenderContext	blenderContext;

+

+	/**

+	 * This method converts the given structure to a scene node.

+	 * @param structure

+	 *        structure of a scene

+	 * @return scene's node

+	 */

+	public Node toScene(Structure structure) {

+		if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) {

+			return null;

+		}

+		Node result = new Node(structure.getName());

+		try {

+			List<Structure> base = ((Structure)structure.getFieldValue("base")).evaluateListBase(blenderContext);

+			for(Structure b : base) {

+				Pointer pObject = (Pointer) b.getFieldValue("object");

+				if(pObject.isNotNull()) {

+					Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0);

+					Object object = this.toObject(objectStructure);

+					if(object instanceof Spatial && ((Spatial) object).getParent()==null) {

+						result.attachChild((Spatial) object);

+					} else if(object instanceof Light) {

+						result.addLight((Light)object);

+					}

+				}

+			}

+		} catch (BlenderFileException e) {

+			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);

+		}

+		return result;

+	}

+

+	/**

+	 * This method converts the given structure to a camera.

+	 * @param structure

+	 *        structure of a camera

+	 * @return camera's node

+	 */

+	public Camera toCamera(Structure structure) throws BlenderFileException {

+		CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);

+		if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {

+			return cameraHelper.toCamera(structure);

+		}

+		return null;

+	}

+

+	/**

+	 * This method converts the given structure to a light.

+	 * @param structure

+	 *        structure of a light

+	 * @return light's node

+	 */

+	public Light toLight(Structure structure) throws BlenderFileException {

+		LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);

+		if (lightHelper.shouldBeLoaded(structure, blenderContext)) {

+			return lightHelper.toLight(structure, blenderContext);

+		}

+		return null;

+	}

+

+	/**

+	 * This method converts the given structure to a node.

+	 * @param structure

+	 *        structure of an object

+	 * @return object's node

+	 */

+	public Object toObject(Structure structure) throws BlenderFileException {

+		ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+		if (objectHelper.shouldBeLoaded(structure, blenderContext)) {

+			return objectHelper.toObject(structure, blenderContext);

+		}

+		return null;

+	}

+

+	/**

+	 * This method converts the given structure to a list of geometries.

+	 * @param structure

+	 *        structure of a mesh

+	 * @return list of geometries

+	 */

+	public List<Geometry> toMesh(Structure structure) throws BlenderFileException {

+		MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);

+		if (meshHelper.shouldBeLoaded(structure, blenderContext)) {

+			return meshHelper.toMesh(structure, blenderContext);

+		}

+		return null;

+	}

+

+	/**

+	 * This method converts the given structure to a material.

+	 * @param structure

+	 *        structure of a material

+	 * @return material's node

+	 */

+	public Material toMaterial(Structure structure) throws BlenderFileException {

+		MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+		if (materialHelper.shouldBeLoaded(structure, blenderContext)) {

+			return materialHelper.toMaterial(structure, blenderContext);

+		}

+		return null;

+	}

+

+	/**

+	 * This method returns the data read from the WORLD file block. The block contains data that can be stored as

+	 * separate jme features and therefore cannot be returned as a single jME scene feature.

+	 * @param structure

+	 *        the structure with WORLD block data

+	 * @return data read from the WORLD block that can be added to the scene

+	 */

+	public WorldData toWorldData(Structure structure) {

+		WorldData result = new WorldData();

+

+		// reading ambient light

+		AmbientLight ambientLight = new AmbientLight();

+		float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();

+		float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();

+		float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();

+		ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));

+		result.setAmbientLight(ambientLight);

+

+		return result;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
new file mode 100644
index 0000000..1ed1c1c
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
@@ -0,0 +1,660 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender;

+

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.EmptyStackException;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Stack;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.animation.Skeleton;

+import com.jme3.asset.AssetManager;

+import com.jme3.asset.BlenderKey;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.scene.plugins.blender.animations.BoneContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.constraints.Constraint;

+import com.jme3.scene.plugins.blender.file.BlenderInputStream;

+import com.jme3.scene.plugins.blender.file.DnaBlockData;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.materials.MaterialContext;

+import com.jme3.scene.plugins.blender.meshes.MeshContext;

+import com.jme3.scene.plugins.blender.modifiers.Modifier;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * The class that stores temporary data and manages it during loading the belnd

+ * file. This class is intended to be used in a single loading thread. It holds

+ * the state of loading operations.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class BlenderContext {

+	private static final Logger					LOGGER					= Logger.getLogger(BlenderContext.class.getName());

+

+	/** The blender file version. */

+	private int									blenderVersion;

+	/** The blender key. */

+	private BlenderKey							blenderKey;

+	/** The header of the file block. */

+	private DnaBlockData						dnaBlockData;

+	/** The input stream of the blend file. */

+	private BlenderInputStream					inputStream;

+	/** The asset manager. */

+	private AssetManager						assetManager;

+	/**

+	 * A map containing the file block headers. The key is the old pointer

+	 * address.

+	 */

+	private Map<Long, FileBlockHeader>			fileBlockHeadersByOma	= new HashMap<Long, FileBlockHeader>();

+	/** A map containing the file block headers. The key is the block code. */

+	private Map<Integer, List<FileBlockHeader>>	fileBlockHeadersByCode	= new HashMap<Integer, List<FileBlockHeader>>();

+	/**

+	 * This map stores the loaded features by their old memory address. The

+	 * first object in the value table is the loaded structure and the second -

+	 * the structure already converted into proper data.

+	 */

+	private Map<Long, Object[]>					loadedFeatures			= new HashMap<Long, Object[]>();

+	/**

+	 * This map stores the loaded features by their name. Only features with ID

+	 * structure can be stored here. The first object in the value table is the

+	 * loaded structure and the second - the structure already converted into

+	 * proper data.

+	 */

+	private Map<String, Object[]>				loadedFeaturesByName	= new HashMap<String, Object[]>();

+	/** A stack that hold the parent structure of currently loaded feature. */

+	private Stack<Structure>					parentStack				= new Stack<Structure>();

+	/**

+	 * A map storing loaded ipos. The key is the ipo's owner old memory address

+	 * and the value is the ipo.

+	 */

+	private Map<Long, Ipo>						loadedIpos				= new HashMap<Long, Ipo>();

+	/** A list of modifiers for the specified object. */

+	protected Map<Long, List<Modifier>>			modifiers				= new HashMap<Long, List<Modifier>>();

+	/** A list of constraints for the specified object. */

+	protected Map<Long, List<Constraint>>		constraints				= new HashMap<Long, List<Constraint>>();

+	/** Anim data loaded for features. */

+	private Map<Long, AnimData>					animData				= new HashMap<Long, AnimData>();

+	/** Loaded skeletons. */

+	private Map<Long, Skeleton>					skeletons				= new HashMap<Long, Skeleton>();

+	/** A map of mesh contexts. */

+	protected Map<Long, MeshContext>			meshContexts			= new HashMap<Long, MeshContext>();

+	/** A map of bone contexts. */

+	protected Map<Long, BoneContext>			boneContexts			= new HashMap<Long, BoneContext>();

+	/** A map of material contexts. */

+	protected Map<Material, MaterialContext>	materialContexts		= new HashMap<Material, MaterialContext>();

+	/** A map og helpers that perform loading. */

+	private Map<String, AbstractBlenderHelper>	helpers					= new HashMap<String, AbstractBlenderHelper>();

+

+	/**

+	 * This method sets the blender file version.

+	 * 

+	 * @param blenderVersion

+	 *            the blender file version

+	 */

+	public void setBlenderVersion(String blenderVersion) {

+		this.blenderVersion = Integer.parseInt(blenderVersion);

+	}

+

+	/**

+	 * @return the blender file version

+	 */

+	public int getBlenderVersion() {

+		return blenderVersion;

+	}

+

+	/**

+	 * This method sets the blender key.

+	 * 

+	 * @param blenderKey

+	 *            the blender key

+	 */

+	public void setBlenderKey(BlenderKey blenderKey) {

+		this.blenderKey = blenderKey;

+	}

+

+	/**

+	 * This method returns the blender key.

+	 * 

+	 * @return the blender key

+	 */

+	public BlenderKey getBlenderKey() {

+		return blenderKey;

+	}

+

+	/**

+	 * This method sets the dna block data.

+	 * 

+	 * @param dnaBlockData

+	 *            the dna block data

+	 */

+	public void setBlockData(DnaBlockData dnaBlockData) {

+		this.dnaBlockData = dnaBlockData;

+	}

+

+	/**

+	 * This method returns the dna block data.

+	 * 

+	 * @return the dna block data

+	 */

+	public DnaBlockData getDnaBlockData() {

+		return dnaBlockData;

+	}

+

+	/**

+	 * This method returns the asset manager.

+	 * 

+	 * @return the asset manager

+	 */

+	public AssetManager getAssetManager() {

+		return assetManager;

+	}

+

+	/**

+	 * This method sets the asset manager.

+	 * 

+	 * @param assetManager

+	 *            the asset manager

+	 */

+	public void setAssetManager(AssetManager assetManager) {

+		this.assetManager = assetManager;

+	}

+

+	/**

+	 * This method returns the input stream of the blend file.

+	 * 

+	 * @return the input stream of the blend file

+	 */

+	public BlenderInputStream getInputStream() {

+		return inputStream;

+	}

+

+	/**

+	 * This method sets the input stream of the blend file.

+	 * 

+	 * @param inputStream

+	 *            the input stream of the blend file

+	 */

+	public void setInputStream(BlenderInputStream inputStream) {

+		this.inputStream = inputStream;

+	}

+

+	/**

+	 * This method adds a file block header to the map. Its old memory address

+	 * is the key.

+	 * 

+	 * @param oldMemoryAddress

+	 *            the address of the block header

+	 * @param fileBlockHeader

+	 *            the block header to store

+	 */

+	public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {

+		fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);

+		List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));

+		if (headers == null) {

+			headers = new ArrayList<FileBlockHeader>();

+			fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers);

+		}

+		headers.add(fileBlockHeader);

+	}

+

+	/**

+	 * This method returns the block header of a given memory address. If the

+	 * header is not present then null is returned.

+	 * 

+	 * @param oldMemoryAddress

+	 *            the address of the block header

+	 * @return loaded header or null if it was not yet loaded

+	 */

+	public FileBlockHeader getFileBlock(Long oldMemoryAddress) {

+		return fileBlockHeadersByOma.get(oldMemoryAddress);

+	}

+

+	/**

+	 * This method returns a list of file blocks' headers of a specified code.

+	 * 

+	 * @param code

+	 *            the code of file blocks

+	 * @return a list of file blocks' headers of a specified code

+	 */

+	public List<FileBlockHeader> getFileBlocks(Integer code) {

+		return fileBlockHeadersByCode.get(code);

+	}

+

+	/**

+	 * This method clears the saved block headers stored in the features map.

+	 */

+	public void clearFileBlocks() {

+		fileBlockHeadersByOma.clear();

+		fileBlockHeadersByCode.clear();

+	}

+

+	/**

+	 * This method adds a helper instance to the helpers' map.

+	 * 

+	 * @param <T>

+	 *            the type of the helper

+	 * @param clazz

+	 *            helper's class definition

+	 * @param helper

+	 *            the helper instance

+	 */

+	public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) {

+		helpers.put(clazz.getSimpleName(), helper);

+	}

+

+	@SuppressWarnings("unchecked")

+	public <T> T getHelper(Class<?> clazz) {

+		return (T) helpers.get(clazz.getSimpleName());

+	}

+

+	/**

+	 * This method adds a loaded feature to the map. The key is its unique old

+	 * memory address.

+	 * 

+	 * @param oldMemoryAddress

+	 *            the address of the feature

+	 * @param featureName

+	 *            the name of the feature

+	 * @param structure

+	 *            the filled structure of the feature

+	 * @param feature

+	 *            the feature we want to store

+	 */

+	public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) {

+		if (oldMemoryAddress == null || structure == null || feature == null) {

+			throw new IllegalArgumentException("One of the given arguments is null!");

+		}

+		Object[] storedData = new Object[] { structure, feature };

+		loadedFeatures.put(oldMemoryAddress, storedData);

+		if (featureName != null) {

+			loadedFeaturesByName.put(featureName, storedData);

+		}

+	}

+

+	/**

+	 * This method returns the feature of a given memory address. If the feature

+	 * is not yet loaded then null is returned.

+	 * 

+	 * @param oldMemoryAddress

+	 *            the address of the feature

+	 * @param loadedFeatureDataType

+	 *            the type of data we want to retreive it can be either filled

+	 *            structure or already converted feature

+	 * @return loaded feature or null if it was not yet loaded

+	 */

+	public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) {

+		Object[] result = loadedFeatures.get(oldMemoryAddress);

+		if (result != null) {

+			return result[loadedFeatureDataType.getIndex()];

+		}

+		return null;

+	}

+

+	/**

+	 * This method returns the feature of a given name. If the feature is not

+	 * yet loaded then null is returned.

+	 * 

+	 * @param featureName

+	 *            the name of the feature

+	 * @param loadedFeatureDataType

+	 *            the type of data we want to retreive it can be either filled

+	 *            structure or already converted feature

+	 * @return loaded feature or null if it was not yet loaded

+	 */

+	public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) {

+		Object[] result = loadedFeaturesByName.get(featureName);

+		if (result != null) {

+			return result[loadedFeatureDataType.getIndex()];

+		}

+		return null;

+	}

+

+	/**

+	 * This method clears the saved features stored in the features map.

+	 */

+	public void clearLoadedFeatures() {

+		loadedFeatures.clear();

+	}

+

+	/**

+	 * This method adds the structure to the parent stack.

+	 * 

+	 * @param parent

+	 *            the structure to be added to the stack

+	 */

+	public void pushParent(Structure parent) {

+		parentStack.push(parent);

+	}

+

+	/**

+	 * This method removes the structure from the top of the parent's stack.

+	 * 

+	 * @return the structure that was removed from the stack

+	 */

+	public Structure popParent() {

+		try {

+			return parentStack.pop();

+		} catch (EmptyStackException e) {

+			return null;

+		}

+	}

+

+	/**

+	 * This method retreives the structure at the top of the parent's stack but

+	 * does not remove it.

+	 * 

+	 * @return the structure from the top of the stack

+	 */

+	public Structure peekParent() {

+		try {

+			return parentStack.peek();

+		} catch (EmptyStackException e) {

+			return null;

+		}

+	}

+

+	/**

+	 * This method adds new ipo curve for the feature.

+	 * 

+	 * @param ownerOMA

+	 *            the OMA of blender feature that owns the ipo

+	 * @param ipo

+	 *            the ipo to be added

+	 */

+	public void addIpo(Long ownerOMA, Ipo ipo) {

+		loadedIpos.put(ownerOMA, ipo);

+	}

+

+	/**

+	 * This method removes the ipo curve from the feature.

+	 * 

+	 * @param ownerOma

+	 *            the OMA of blender feature that owns the ipo

+	 */

+	public Ipo removeIpo(Long ownerOma) {

+		return loadedIpos.remove(ownerOma);

+	}

+

+	/**

+	 * This method returns the ipo curve of the feature.

+	 * 

+	 * @param ownerOMA

+	 *            the OMA of blender feature that owns the ipo

+	 */

+	public Ipo getIpo(Long ownerOMA) {

+		return loadedIpos.get(ownerOMA);

+	}

+

+	/**

+	 * This method adds a new modifier to the list.

+	 * 

+	 * @param ownerOMA

+	 *            the owner's old memory address

+	 * @param modifier

+	 *            the object's modifier

+	 */

+	public void addModifier(Long ownerOMA, Modifier modifier) {

+		List<Modifier> objectModifiers = this.modifiers.get(ownerOMA);

+		if (objectModifiers == null) {

+			objectModifiers = new ArrayList<Modifier>();

+			this.modifiers.put(ownerOMA, objectModifiers);

+		}

+		objectModifiers.add(modifier);

+	}

+

+	/**

+	 * This method returns modifiers for the object specified by its old memory

+	 * address and the modifier type. If no modifiers are found - empty list is

+	 * returned. If the type is null - all modifiers for the object are

+	 * returned.

+	 * 

+	 * @param objectOMA

+	 *            object's old memory address

+	 * @param type

+	 *            the type of the modifier

+	 * @return the list of object's modifiers

+	 */

+	public List<Modifier> getModifiers(Long objectOMA, String type) {

+		List<Modifier> result = new ArrayList<Modifier>();

+		List<Modifier> readModifiers = modifiers.get(objectOMA);

+		if (readModifiers != null && readModifiers.size() > 0) {

+			for (Modifier modifier : readModifiers) {

+				if (type == null || type.isEmpty() || modifier.getType().equals(type)) {

+					result.add(modifier);

+				}

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method adds a new modifier to the list.

+	 * 

+	 * @param ownerOMA

+	 *            the owner's old memory address

+	 * @param constraints

+	 *            the object's constraints

+	 */

+	public void addConstraints(Long ownerOMA, List<Constraint> constraints) {

+		List<Constraint> objectConstraints = this.constraints.get(ownerOMA);

+		if (objectConstraints == null) {

+			objectConstraints = new ArrayList<Constraint>();

+			this.constraints.put(ownerOMA, objectConstraints);

+		}

+		objectConstraints.addAll(constraints);

+	}

+

+	/**

+	 * This method returns constraints for the object specified by its old

+	 * memory address. If no modifiers are found - <b>null</b> is returned.

+	 * 

+	 * @param objectOMA

+	 *            object's old memory address

+	 * @return the list of object's modifiers or null

+	 */

+	public List<Constraint> getConstraints(Long objectOMA) {

+		return objectOMA == null ? null : constraints.get(objectOMA);

+	}

+

+	/**

+	 * This method sets the anim data for the specified OMA of its owner.

+	 * 

+	 * @param ownerOMA

+	 *            the owner's old memory address

+	 * @param animData

+	 *            the animation data for the feature specified by ownerOMA

+	 */

+	public void setAnimData(Long ownerOMA, AnimData animData) {

+		this.animData.put(ownerOMA, animData);

+	}

+

+	/**

+	 * This method returns the animation data for the specified owner.

+	 * 

+	 * @param ownerOMA

+	 *            the old memory address of the animation data owner

+	 * @return the animation data or null if none exists

+	 */

+	public AnimData getAnimData(Long ownerOMA) {

+		return this.animData.get(ownerOMA);

+	}

+

+	/**

+	 * This method sets the skeleton for the specified OMA of its owner.

+	 * 

+	 * @param skeletonOMA

+	 *            the skeleton's old memory address

+	 * @param skeleton

+	 *            the skeleton specified by the given OMA

+	 */

+	public void setSkeleton(Long skeletonOMA, Skeleton skeleton) {

+		this.skeletons.put(skeletonOMA, skeleton);

+	}

+

+	/**

+	 * This method returns the skeleton for the specified OMA of its owner.

+	 * 

+	 * @param skeletonOMA

+	 *            the skeleton's old memory address

+	 * @return the skeleton specified by the given OMA

+	 */

+	public Skeleton getSkeleton(Long skeletonOMA) {

+		return this.skeletons.get(skeletonOMA);

+	}

+

+	/**

+	 * This method sets the mesh context for the given mesh old memory address.

+	 * If the context is already set it will be replaced.

+	 * 

+	 * @param meshOMA

+	 *            the mesh's old memory address

+	 * @param meshContext

+	 *            the mesh's context

+	 */

+	public void setMeshContext(Long meshOMA, MeshContext meshContext) {

+		this.meshContexts.put(meshOMA, meshContext);

+	}

+

+	/**

+	 * This method returns the mesh context for the given mesh old memory

+	 * address. If no context exists then <b>null</b> is returned.

+	 * 

+	 * @param meshOMA

+	 *            the mesh's old memory address

+	 * @return mesh's context

+	 */

+	public MeshContext getMeshContext(Long meshOMA) {

+		return this.meshContexts.get(meshOMA);

+	}

+

+	/**

+	 * This method sets the bone context for the given bone old memory address.

+	 * If the context is already set it will be replaced.

+	 * 

+	 * @param boneOMA

+	 *            the bone's old memory address

+	 * @param boneContext

+	 *            the bones's context

+	 */

+	public void setBoneContext(Long boneOMA, BoneContext boneContext) {

+		this.boneContexts.put(boneOMA, boneContext);

+	}

+

+	/**

+	 * This method returns the bone context for the given bone old memory

+	 * address. If no context exists then <b>null</b> is returned.

+	 * 

+	 * @param boneOMA

+	 *            the bone's old memory address

+	 * @return bone's context

+	 */

+	public BoneContext getBoneContext(Long boneOMA) {

+		return boneContexts.get(boneOMA);

+	}

+

+	/**

+	 * This method sets the material context for the given material. If the

+	 * context is already set it will be replaced.

+	 * 

+	 * @param material

+	 *            the material

+	 * @param materialContext

+	 *            the material's context

+	 */

+	public void setMaterialContext(Material material, MaterialContext materialContext) {

+		this.materialContexts.put(material, materialContext);

+	}

+

+	/**

+	 * This method returns the material context for the given material. If no

+	 * context exists then <b>null</b> is returned.

+	 * 

+	 * @param material

+	 *            the material

+	 * @return material's context

+	 */

+	public MaterialContext getMaterialContext(Material material) {

+		return materialContexts.get(material);

+	}

+

+	/**

+	 * This metod returns the default material.

+	 * 

+	 * @return the default material

+	 */

+	public synchronized Material getDefaultMaterial() {

+		if (blenderKey.getDefaultMaterial() == null) {

+			Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+			defaultMaterial.setColor("Color", ColorRGBA.DarkGray);

+			blenderKey.setDefaultMaterial(defaultMaterial);

+		}

+		return blenderKey.getDefaultMaterial();

+	}

+

+	public void dispose() {

+		try {

+			inputStream.close();

+		} catch (IOException e) {

+			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);

+		}

+		loadedFeatures.clear();

+		loadedFeaturesByName.clear();

+	}

+

+	/**

+	 * This enum defines what loaded data type user wants to retreive. It can be

+	 * either filled structure or already converted data.

+	 * 

+	 * @author Marcin Roguski

+	 */

+	public static enum LoadedFeatureDataType {

+

+		LOADED_STRUCTURE(0), LOADED_FEATURE(1);

+		private int	index;

+

+		private LoadedFeatureDataType(int index) {

+			this.index = index;

+		}

+

+		public int getIndex() {

+			return index;

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
new file mode 100644
index 0000000..3c81ba6
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
@@ -0,0 +1,228 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender;

+

+import com.jme3.asset.AssetInfo;

+import com.jme3.asset.BlenderKey;

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.asset.BlenderKey.LoadingResults;

+import com.jme3.asset.BlenderKey.WorldData;

+import com.jme3.asset.ModelKey;

+import com.jme3.light.Light;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.animations.ArmatureHelper;

+import com.jme3.scene.plugins.blender.animations.IpoHelper;

+import com.jme3.scene.plugins.blender.cameras.CameraHelper;

+import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;

+import com.jme3.scene.plugins.blender.curves.CurvesHelper;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.BlenderInputStream;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.lights.LightHelper;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+import com.jme3.scene.plugins.blender.meshes.MeshHelper;

+import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+import com.jme3.scene.plugins.blender.particles.ParticlesHelper;

+import com.jme3.scene.plugins.blender.textures.TextureHelper;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class BlenderLoader extends AbstractBlenderLoader {

+

+	private static final Logger		LOGGER	= Logger.getLogger(BlenderLoader.class.getName());

+

+	/** The blocks read from the file. */

+	protected List<FileBlockHeader>	blocks;

+

+	@Override

+	public Spatial load(AssetInfo assetInfo) throws IOException {

+		try {

+			this.setup(assetInfo);

+

+			BlenderKey blenderKey = blenderContext.getBlenderKey();

+			LoadingResults loadingResults = blenderKey.prepareLoadingResults();

+			WorldData worldData = null;// a set of data used in different scene aspects

+			for (FileBlockHeader block : blocks) {

+				switch (block.getCode()) {

+					case FileBlockHeader.BLOCK_OB00:// Object

+						Object object = this.toObject(block.getStructure(blenderContext));

+						if (object instanceof Node) {

+							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {

+								LOGGER.log(Level.INFO, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });

+								if (this.isRootObject(loadingResults, (Node)object)) {

+									loadingResults.addObject((Node) object);

+								}

+							}

+						} else if (object instanceof Camera) {

+							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) {

+								loadingResults.addCamera((Camera) object);

+							}

+						} else if (object instanceof Light) {

+							if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {

+								loadingResults.addLight((Light) object);

+							}

+						}

+						break;

+					case FileBlockHeader.BLOCK_MA00:// Material

+						if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {

+							loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));

+						}

+						break;

+					case FileBlockHeader.BLOCK_SC00:// Scene

+						if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) {

+							loadingResults.addScene(this.toScene(block.getStructure(blenderContext)));

+						}

+						break;

+					case FileBlockHeader.BLOCK_WO00:// World

+						if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used

+							Structure worldStructure = block.getStructure(blenderContext);

+							String worldName = worldStructure.getName();

+							if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {

+								worldData = this.toWorldData(worldStructure);

+								if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {

+									loadingResults.addLight(worldData.getAmbientLight());

+								}

+							}

+						}

+						break;

+				}

+			}

+			blenderContext.dispose();

+			return loadingResults;

+		} catch (BlenderFileException e) {

+			LOGGER.log(Level.SEVERE, e.getMessage(), e);

+		}

+		return null;

+	}

+

+	/**

+	 * This method indicates if the given spatial is a root object. It means it

+	 * has no parent or is directly attached to one of the already loaded scene

+	 * nodes.

+	 * 

+	 * @param loadingResults

+	 *        loading results containing the scene nodes

+	 * @param spatial

+	 *        spatial object

+	 * @return <b>true</b> if the given spatial is a root object and

+	 *         <b>false</b> otherwise

+	 */

+	protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) {

+		if(spatial.getParent() == null) {

+			return true;

+		}

+		for(Node scene : loadingResults.getScenes()) {

+			if(spatial.getParent().equals(scene)) {

+				return true;

+			}

+		}

+		return false;

+	}

+	

+	/**

+	 * This method sets up the loader.

+	 * @param assetInfo

+	 *        the asset info

+	 * @throws BlenderFileException

+	 *         an exception is throw when something wrong happens with blender file

+	 */

+	protected void setup(AssetInfo assetInfo) throws BlenderFileException {

+		// registering loaders

+		ModelKey modelKey = (ModelKey) assetInfo.getKey();

+		BlenderKey blenderKey;

+		if (modelKey instanceof BlenderKey) {

+			blenderKey = (BlenderKey) modelKey;

+		} else {

+			blenderKey = new BlenderKey(modelKey.getName());

+			blenderKey.setAssetRootPath(modelKey.getFolder());

+		}

+

+		// opening stream

+		BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager());

+

+		// reading blocks

+		blocks = new ArrayList<FileBlockHeader>();

+		FileBlockHeader fileBlock;

+		blenderContext = new BlenderContext();

+		blenderContext.setBlenderVersion(inputStream.getVersionNumber());

+		blenderContext.setAssetManager(assetInfo.getManager());

+		blenderContext.setInputStream(inputStream);

+		blenderContext.setBlenderKey(blenderKey);

+

+		// creating helpers

+		blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+		blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));

+

+		// setting additional data to helpers

+		MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+		materialHelper.setFaceCullMode(blenderKey.getFaceCullMode());

+

+		// reading the blocks (dna block is automatically saved in the blender context when found)//TODO: zmienić to

+		FileBlockHeader sceneFileBlock = null;

+		do {

+			fileBlock = new FileBlockHeader(inputStream, blenderContext);

+			if (!fileBlock.isDnaBlock()) {

+				blocks.add(fileBlock);

+				// save the scene's file block

+				if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00 && blenderKey.getLayersToLoad() < 0) {

+					sceneFileBlock = fileBlock;

+				}

+			}

+		} while (!fileBlock.isLastBlock());

+		// VERIFY LAYERS TO BE LOADED BEFORE LOADING FEATURES

+		if (sceneFileBlock != null) {

+			int lay = ((Number) sceneFileBlock.getStructure(blenderContext).getFieldValue("lay")).intValue();

+			blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java
new file mode 100644
index 0000000..ec117d8
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.BlenderKey;
+import com.jme3.light.Light;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
+ * @author Marcin Roguski
+ */
+public class BlenderModelLoader extends BlenderLoader {
+
+    private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName());
+
+    @Override
+    public Spatial load(AssetInfo assetInfo) throws IOException {
+        try {
+            this.setup(assetInfo);
+            
+            BlenderKey blenderKey = blenderContext.getBlenderKey();
+            Node modelRoot = new Node(blenderKey.getName());
+            
+            for (FileBlockHeader block : blocks) {
+                if (block.getCode() == FileBlockHeader.BLOCK_OB00) {
+                    Object object = this.toObject(block.getStructure(blenderContext));
+                    if (object instanceof Node) {
+                        LOGGER.log(Level.INFO, "{0}: {1}--> {2}", new Object[]{((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName()});
+                        if (((Node) object).getParent() == null) {
+                            modelRoot.attachChild( (Node) object );
+                        }
+                    }else if (object instanceof Light){
+                        modelRoot.addLight( (Light) object );
+                    }
+                }
+            }
+            blenderContext.dispose();
+            return modelRoot;
+        } catch (BlenderFileException e) {
+            LOGGER.log(Level.SEVERE, e.getMessage(), e);
+        }
+        return null;
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
new file mode 100644
index 0000000..120e556
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
@@ -0,0 +1,263 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.animations;

+

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.animation.Bone;

+import com.jme3.animation.BoneTrack;

+import com.jme3.animation.Skeleton;

+import com.jme3.math.Matrix4f;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.curves.BezierCurve;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class defines the methods to calculate certain aspects of animation and

+ * armature functionalities.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class ArmatureHelper extends AbstractBlenderHelper {

+	private static final Logger	LOGGER		= Logger.getLogger(ArmatureHelper.class.getName());

+

+	/** A map of bones and their old memory addresses. */

+	private Map<Bone, Long>		bonesOMAs	= new HashMap<Bone, Long>();

+

+	/**

+	 * This constructor parses the given blender version and stores the result.

+	 * Some functionalities may differ in different blender versions.

+	 * 

+	 * @param blenderVersion

+	 *            the version read from the blend file

+	 * @param fixUpAxis

+	 *            a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public ArmatureHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	/**

+	 * This method builds the object's bones structure.

+	 * 

+	 * @param boneStructure

+	 *            the structure containing the bones' data

+	 * @param parent

+	 *            the parent bone

+	 * @param result

+	 *            the list where the newly created bone will be added

+	 * @param bonesPoseChannels

+	 *            a map of bones poses channels

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there is problem with the blender

+	 *             file

+	 */

+	public void buildBones(Structure boneStructure, Bone parent, List<Bone> result, Matrix4f arbt, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {

+		BoneContext bc = new BoneContext(boneStructure, arbt, bonesPoseChannels, blenderContext);

+		bc.buildBone(result, bonesOMAs, blenderContext);

+	}

+

+	/**

+	 * This method returns the old memory address of a bone. If the bone does

+	 * not exist in the blend file - zero is returned.

+	 * 

+	 * @param bone

+	 *            the bone whose old memory address we seek

+	 * @return the old memory address of the given bone

+	 */

+	public Long getBoneOMA(Bone bone) {

+		Long result = bonesOMAs.get(bone);

+		if (result == null) {

+			result = Long.valueOf(0);

+		}

+		return result;

+	}

+

+	/**

+	 * This method returns a map where the key is the object's group index that

+	 * is used by a bone and the key is the bone index in the armature.

+	 * 

+	 * @param defBaseStructure

+	 *            a bPose structure of the object

+	 * @return bone group-to-index map

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {

+		Map<Integer, Integer> result = null;

+		if (skeleton.getBoneCount() != 0) {

+			result = new HashMap<Integer, Integer>();

+			List<Structure> deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup

+			int groupIndex = 0;

+			for (Structure deformGroup : deformGroups) {

+				String deformGroupName = deformGroup.getFieldValue("name").toString();

+				Integer boneIndex = this.getBoneIndex(skeleton, deformGroupName);

+				if (boneIndex != null) {

+					result.put(Integer.valueOf(groupIndex), boneIndex);

+				}

+				++groupIndex;

+			}

+		}

+		return result;

+	}

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return true;

+	}

+

+	/**

+	 * This method retuns the bone tracks for animation.

+	 * 

+	 * @param actionStructure

+	 *            the structure containing the tracks

+	 * @param blenderContext

+	 *            the blender context

+	 * @return a list of tracks for the specified animation

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the blend

+	 *             file

+	 */

+	public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {

+		if (blenderVersion < 250) {

+			return this.getTracks249(actionStructure, skeleton, blenderContext);

+		} else {

+			return this.getTracks250(actionStructure, skeleton, blenderContext);

+		}

+	}

+

+	/**

+	 * This method retuns the bone tracks for animation for blender version 2.50

+	 * and higher.

+	 * 

+	 * @param actionStructure

+	 *            the structure containing the tracks

+	 * @param blenderContext

+	 *            the blender context

+	 * @return a list of tracks for the specified animation

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the blend

+	 *             file

+	 */

+	private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {

+		LOGGER.log(Level.INFO, "Getting tracks!");

+		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);

+		int fps = blenderContext.getBlenderKey().getFps();

+		Structure groups = (Structure) actionStructure.getFieldValue("groups");

+		List<Structure> actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup

+		List<BoneTrack> tracks = new ArrayList<BoneTrack>();

+		for (Structure actionGroup : actionGroups) {

+			String name = actionGroup.getFieldValue("name").toString();

+			int boneIndex = this.getBoneIndex(skeleton, name);

+			if (boneIndex >= 0) {

+				List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext);

+				BezierCurve[] bezierCurves = new BezierCurve[channels.size()];

+				int channelCounter = 0;

+				for (Structure c : channels) {

+					int type = ipoHelper.getCurveType(c, blenderContext);

+					Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");

+					List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());

+					bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);

+				}

+

+				Ipo ipo = new Ipo(bezierCurves, fixUpAxis);

+				tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, 0, ipo.getLastFrame(), fps, false));

+			}

+		}

+		return tracks.toArray(new BoneTrack[tracks.size()]);

+	}

+

+	/**

+	 * This method retuns the bone tracks for animation for blender version 2.49

+	 * (and probably several lower versions too).

+	 * 

+	 * @param actionStructure

+	 *            the structure containing the tracks

+	 * @param blenderContext

+	 *            the blender context

+	 * @return a list of tracks for the specified animation

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the blend

+	 *             file

+	 */

+	private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {

+		LOGGER.log(Level.INFO, "Getting tracks!");

+		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);

+		int fps = blenderContext.getBlenderKey().getFps();

+		Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");

+		List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel

+		List<BoneTrack> tracks = new ArrayList<BoneTrack>();

+		for (Structure bActionChannel : actionChannels) {

+			String name = bActionChannel.getFieldValue("name").toString();

+			int boneIndex = this.getBoneIndex(skeleton, name);

+			if (boneIndex >= 0) {

+				Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");

+				if (!p.isNull()) {

+					Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0);

+					Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);

+					tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, 0, ipo.getLastFrame(), fps, false));

+				}

+			}

+		}

+		return tracks.toArray(new BoneTrack[tracks.size()]);

+	}

+

+	/**

+	 * This method returns the index of the bone in the given skeleton.

+	 * 

+	 * @param skeleton

+	 *            the skeleton

+	 * @param boneName

+	 *            the name of the bone

+	 * @return the index of the bone

+	 */

+	private int getBoneIndex(Skeleton skeleton, String boneName) {

+		int result = -1;

+		for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) {

+			if (boneName.equals(skeleton.getBone(i).getName())) {

+				result = i;

+			}

+		}

+		return result;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
new file mode 100644
index 0000000..1ef9bae
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
@@ -0,0 +1,204 @@
+package com.jme3.scene.plugins.blender.animations;

+

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map;

+

+import com.jme3.animation.Bone;

+import com.jme3.math.Matrix4f;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+

+/**

+ * This class holds the basic data that describes a bone.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class BoneContext {

+	/** The structure of the bone. */

+	private Structure			boneStructure;

+	/** Bone's pose channel structure. */

+	private Structure			poseChannel;

+	/** Bone's name. */

+	private String				boneName;

+	/** This variable indicates if the Y axis should be the UP axis. */

+	private boolean				fixUpAxis;

+	/** The bone's armature matrix. */

+	private Matrix4f			armatureMatrix;

+	/** The parent context. */

+	private BoneContext			parent;

+	/** The children of this context. */

+	private List<BoneContext>	children		= new ArrayList<BoneContext>();

+	/** Created bone (available after calling 'buildBone' method). */

+	private Bone				bone;

+	/** Bone's pose transform (available after calling 'buildBone' method). */

+	private Transform			poseTransform	= new Transform();

+	/** The bone's rest matrix. */

+	private Matrix4f			restMatrix;

+	/** Bone's total inverse transformation. */

+	private Matrix4f			inverseTotalTransformation;

+	/** Bone's parent inverse matrix. */

+	private Matrix4f			inverseParentMatrix;

+

+	/**

+	 * Constructor. Creates the basic set of bone's data.

+	 * 

+	 * @param boneStructure

+	 *            the bone's structure

+	 * @param objectToArmatureMatrix

+	 *            object-to-armature transformation matrix

+	 * @param bonesPoseChannels

+	 *            a map of pose channels for each bone OMA

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             an exception is thrown when problem with blender data reading

+	 *             occurs

+	 */

+	public BoneContext(Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {

+		this(boneStructure, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext);

+	}

+

+	/**

+	 * Constructor. Creates the basic set of bone's data.

+	 * 

+	 * @param boneStructure

+	 *            the bone's structure

+	 * @param parent

+	 *            bone's parent (null if the bone is the root bone)

+	 * @param objectToArmatureMatrix

+	 *            object-to-armature transformation matrix

+	 * @param bonesPoseChannels

+	 *            a map of pose channels for each bone OMA

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             an exception is thrown when problem with blender data reading

+	 *             occurs

+	 */

+	private BoneContext(Structure boneStructure, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {

+		this.parent = parent;

+		this.boneStructure = boneStructure;

+		boneName = boneStructure.getFieldValue("name").toString();

+		ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+		armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true);

+

+		fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();

+		this.computeRestMatrix(objectToArmatureMatrix);

+		List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);

+		for (Structure child : childbase) {

+			this.children.add(new BoneContext(child, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext));

+		}

+

+		poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress());

+

+		blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);

+	}

+

+	/**

+	 * This method computes the rest matrix for the bone.

+	 * 

+	 * @param objectToArmatureMatrix

+	 *            object-to-armature transformation matrix

+	 */

+	private void computeRestMatrix(Matrix4f objectToArmatureMatrix) {

+		if (parent != null) {

+			inverseParentMatrix = parent.inverseTotalTransformation.clone();

+		} else if (fixUpAxis) {

+			inverseParentMatrix = objectToArmatureMatrix.clone();

+		} else {

+			inverseParentMatrix = Matrix4f.IDENTITY.clone();

+		}

+

+		restMatrix = armatureMatrix.clone();

+		inverseTotalTransformation = restMatrix.invert();

+

+		restMatrix = inverseParentMatrix.mult(restMatrix);

+

+		for (BoneContext child : this.children) {

+			child.computeRestMatrix(objectToArmatureMatrix);

+		}

+	}

+

+	/**

+	 * This method computes the pose transform for the bone.

+	 */

+	@SuppressWarnings("unchecked")

+	private void computePoseTransform() {

+		DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc");

+		DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size");

+		DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat");

+		if (fixUpAxis) {

+			poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue());

+			poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue()));

+			poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue());

+		} else {

+			poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());

+			poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue()));

+			poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());

+		}

+

+		Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());

+		localTransform.setScale(bone.getLocalScale());

+		localTransform.getTranslation().addLocal(poseTransform.getTranslation());

+		localTransform.getRotation().multLocal(poseTransform.getRotation());

+		localTransform.getScale().multLocal(poseTransform.getScale());

+

+		poseTransform.set(localTransform);

+	}

+

+	/**

+	 * This method builds the bone. It recursively builds the bone's children.

+	 * 

+	 * @param bones

+	 *            a list of bones where the newly created bone will be added

+	 * @param boneOMAs

+	 *            the map between bone and its old memory address

+	 * @param blenderContext

+	 *            the blender context

+	 * @return newly created bone

+	 */

+	public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) {

+		Long boneOMA = boneStructure.getOldMemoryAddress();

+		bone = new Bone(boneName);

+		bones.add(bone);

+		boneOMAs.put(bone, boneOMA);

+		blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);

+

+		Matrix4f pose = this.restMatrix.clone();

+		ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+

+		Vector3f poseLocation = pose.toTranslationVector();

+		Quaternion rotation = pose.toRotationQuat();

+		Vector3f scale = objectHelper.getScale(pose);

+

+		bone.setBindTransforms(poseLocation, rotation, scale);

+		for (BoneContext child : children) {

+			bone.addChild(child.buildBone(bones, boneOMAs, blenderContext));

+		}

+

+		this.computePoseTransform();

+

+		return bone;

+	}

+

+	/**

+	 * @return bone's pose transformation

+	 */

+	public Transform getPoseTransform() {

+		return poseTransform;

+	}

+

+	/**

+	 * @return built bone (available after calling 'buildBone' method)

+	 */

+	public Bone getBone() {

+		return bone;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java
new file mode 100644
index 0000000..90994cc
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java
@@ -0,0 +1,128 @@
+package com.jme3.scene.plugins.blender.animations;

+

+import com.jme3.animation.Bone;

+import com.jme3.animation.BoneTrack;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import java.util.Arrays;

+

+/**

+ * The purpose of this class is to imitate bone's movement when calculating inverse kinematics.

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class CalculationBone extends Node {

+	private Bone bone;

+	/** The bone's tracks. Will be altered at the end of calculation process. */

+	private BoneTrack track;

+	/** The starting position of the bone. */

+	private Vector3f startTranslation;

+	/** The starting rotation of the bone. */

+	private Quaternion startRotation;

+	/** The starting scale of the bone. */

+	private Vector3f startScale;

+	private Vector3f[] translations;

+	private Quaternion[] rotations;

+	private Vector3f[] scales;

+

+	public CalculationBone(Bone bone, int boneFramesCount) {

+		this.bone = bone;

+		this.startRotation = bone.getModelSpaceRotation().clone();

+		this.startTranslation = bone.getModelSpacePosition().clone();

+		this.startScale = bone.getModelSpaceScale().clone();

+		this.reset();

+		if(boneFramesCount > 0) {

+			this.translations = new Vector3f[boneFramesCount];

+			this.rotations = new Quaternion[boneFramesCount];

+			this.scales = new Vector3f[boneFramesCount];

+			

+			Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);

+			Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);

+			Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);

+		}

+	}

+	

+	/**

+	 * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.

+	 * @param bone

+	 *        the bone this class will imitate

+	 * @param track

+	 *        the bone's tracks

+	 */

+	public CalculationBone(Bone bone, BoneTrack track) {

+		this(bone, 0);

+		this.track = track;

+		this.translations = track.getTranslations();

+		this.rotations = track.getRotations();

+		this.scales = track.getScales();

+	}

+

+	public int getBoneFramesCount() {

+		return this.translations==null ? 0 : this.translations.length;

+	}

+	

+	/**

+	 * This method returns the end point of the bone. If the bone has parent it is calculated from the start point

+	 * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered

+	 * to be 1 point up along Y axis (scale is applied if set to != 1.0);

+	 * @return the end point of this bone

+	 */

+	//TODO: set to Z axis if user defined it this way

+	public Vector3f getEndPoint() {

+		if (this.getParent() == null) {

+			return new Vector3f(0, this.getLocalScale().y, 0);

+		} else {

+			Node parent = this.getParent();

+			return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());

+		}

+	}

+

+	/**

+	 * This method resets the calculation bone to the starting position.

+	 */

+	public void reset() {

+		this.setLocalTranslation(startTranslation);

+		this.setLocalRotation(startRotation);

+		this.setLocalScale(startScale);

+	}

+

+	@Override

+	public int attachChild(Spatial child) {

+		if (this.getChildren() != null && this.getChildren().size() > 1) {

+			throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");

+		}

+		return super.attachChild(child);

+	}

+

+	public Spatial rotate(Quaternion rot, int frame) {

+		Spatial spatial = super.rotate(rot);

+		this.updateWorldTransforms();

+		if (this.getChildren() != null && this.getChildren().size() > 0) {

+			CalculationBone child = (CalculationBone) this.getChild(0);

+			child.updateWorldTransforms();

+		}

+		rotations[frame].set(this.getLocalRotation());

+		translations[frame].set(this.getLocalTranslation());

+		if (scales != null) {

+			scales[frame].set(this.getLocalScale());

+		}

+		return spatial;

+	}

+

+	public void applyCalculatedTracks() {

+		if(track != null) {

+			track.setKeyframes(track.getTimes(), translations, rotations, scales);

+		} else {

+			bone.setUserControl(true);

+			bone.setUserTransforms(translations[0], rotations[0], scales[0]);

+			bone.setUserControl(false);

+			bone.updateWorldVectors();

+		}

+	}

+

+	@Override

+	public String toString() {

+		return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation();

+	}

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
new file mode 100644
index 0000000..58ce48c
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
@@ -0,0 +1,236 @@
+package com.jme3.scene.plugins.blender.animations;

+

+import com.jme3.animation.BoneTrack;

+import com.jme3.animation.SpatialTrack;

+import com.jme3.animation.Track;

+import com.jme3.math.FastMath;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.plugins.blender.curves.BezierCurve;

+

+/**

+ * This class is used to calculate bezier curves value for the given frames. The

+ * Ipo (interpolation object) consists of several b-spline curves (connected 3rd

+ * degree bezier curves) of a different type.

+ * 

+ * @author Marcin Roguski

+ */

+public class Ipo {

+

+	public static final int	AC_LOC_X	= 1;

+	public static final int	AC_LOC_Y	= 2;

+	public static final int	AC_LOC_Z	= 3;

+	public static final int	OB_ROT_X	= 7;

+	public static final int	OB_ROT_Y	= 8;

+	public static final int	OB_ROT_Z	= 9;

+	public static final int	AC_SIZE_X	= 13;

+	public static final int	AC_SIZE_Y	= 14;

+	public static final int	AC_SIZE_Z	= 15;

+	public static final int	AC_QUAT_W	= 25;

+	public static final int	AC_QUAT_X	= 26;

+	public static final int	AC_QUAT_Y	= 27;

+	public static final int	AC_QUAT_Z	= 28;

+

+	/** A list of bezier curves for this interpolation object. */

+	private BezierCurve[]	bezierCurves;

+	/** Each ipo contains one bone track. */

+	private Track			calculatedTrack;

+	/** This variable indicates if the Y asxis is the UP axis or not. */

+	protected boolean		fixUpAxis;

+

+	/**

+	 * Constructor. Stores the bezier curves.

+	 * 

+	 * @param bezierCurves

+	 *            a table of bezier curves

+	 */

+	public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis) {

+		this.bezierCurves = bezierCurves;

+		this.fixUpAxis = fixUpAxis;

+	}

+

+	/**

+	 * This method calculates the ipo value for the first curve.

+	 * 

+	 * @param frame

+	 *            the frame for which the value is calculated

+	 * @return calculated ipo value

+	 */

+	public float calculateValue(int frame) {

+		return this.calculateValue(frame, 0);

+	}

+

+	/**

+	 * This method calculates the ipo value for the curve of the specified

+	 * index. Make sure you do not exceed the curves amount. Alway chech the

+	 * amount of curves before calling this method.

+	 * 

+	 * @param frame

+	 *            the frame for which the value is calculated

+	 * @param curveIndex

+	 *            the index of the curve

+	 * @return calculated ipo value

+	 */

+	public float calculateValue(int frame, int curveIndex) {

+		return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);

+	}

+

+	/**

+	 * This method returns the curves amount.

+	 * 

+	 * @return the curves amount

+	 */

+	public int getCurvesAmount() {

+		return bezierCurves.length;

+	}

+

+	/**

+	 * This method returns the frame where last bezier triple center point of

+	 * the specified bezier curve is located.

+	 * 

+	 * @return the frame number of the last defined bezier triple point for the

+	 *         specified ipo

+	 */

+	public int getLastFrame() {

+		int result = 1;

+		for (int i = 0; i < bezierCurves.length; ++i) {

+			int tempResult = bezierCurves[i].getLastFrame();

+			if (tempResult > result) {

+				result = tempResult;

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method calculates the value of the curves as a bone track between

+	 * the specified frames.

+	 * 

+	 * @param targetIndex

+	 *            the index of the target for which the method calculates the

+	 *            tracks IMPORTANT! Aet to -1 (or any negative number) if you

+	 *            want to load spatial animation.

+	 * @param startFrame

+	 *            the firs frame of tracks (inclusive)

+	 * @param stopFrame

+	 *            the last frame of the tracks (inclusive)

+	 * @param fps

+	 *            frame rate (frames per second)

+	 * @param spatialTrack

+	 *            this flag indicates if the track belongs to a spatial or to a

+	 *            bone; the diference is important because it appears that bones

+	 *            in blender have the same type of coordinate system (Y as UP)

+	 *            as jme while other features have different one (Z is UP)

+	 * @return bone track for the specified bone

+	 */

+	public Track calculateTrack(int targetIndex, int startFrame, int stopFrame, int fps, boolean spatialTrack) {

+		if (calculatedTrack == null) {

+			// preparing data for track

+			int framesAmount = stopFrame - startFrame;

+			float start = (startFrame - 1.0f) / fps;

+			float timeBetweenFrames = 1.0f / fps;

+

+			float[] times = new float[framesAmount + 1];

+			Vector3f[] translations = new Vector3f[framesAmount + 1];

+			float[] translation = new float[3];

+			Quaternion[] rotations = new Quaternion[framesAmount + 1];

+			float[] quaternionRotation = new float[4];

+			float[] objectRotation = new float[3];

+			Vector3f[] scales = new Vector3f[framesAmount + 1];

+			float[] scale = new float[] { 1.0f, 1.0f, 1.0f };

+			float degreeToRadiansFactor = FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here

+

+			// calculating track data

+			for (int frame = startFrame; frame <= stopFrame; ++frame) {

+				int index = frame - startFrame;

+				times[index] = start + (frame - 1) * timeBetweenFrames;

+				for (int j = 0; j < bezierCurves.length; ++j) {

+					double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);

+					switch (bezierCurves[j].getType()) {

+					// LOCATION

+						case AC_LOC_X:

+							translation[0] = (float) value;

+							break;

+						case AC_LOC_Y:

+							if (fixUpAxis && spatialTrack) {

+								translation[2] = (float) -value;

+							} else {

+								translation[1] = (float) value;

+							}

+							break;

+						case AC_LOC_Z:

+							translation[fixUpAxis && spatialTrack ? 1 : 2] = (float) value;

+							break;

+

+						// ROTATION (used with object animation)

+						// the value here is in degrees divided by 10 (so in

+						// example: 9 = PI/2)

+						case OB_ROT_X:

+							objectRotation[0] = (float) value * degreeToRadiansFactor;

+							break;

+						case OB_ROT_Y:

+							if (fixUpAxis) {

+								objectRotation[2] = (float) -value * degreeToRadiansFactor;

+							} else {

+								objectRotation[1] = (float) value * degreeToRadiansFactor;

+							}

+							break;

+						case OB_ROT_Z:

+							objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor;

+							break;

+

+						// SIZE

+						case AC_SIZE_X:

+							scale[0] = (float) value;

+							break;

+						case AC_SIZE_Y:

+							if (fixUpAxis && spatialTrack) {

+								scale[2] = (float) value;

+							} else {

+								scale[1] = (float) value;

+							}

+							break;

+						case AC_SIZE_Z:

+							scale[fixUpAxis && spatialTrack ? 1 : 2] = (float) value;

+							break;

+

+						// QUATERNION ROTATION (used with bone animation), dunno

+						// why but here we shouldn't check the

+						// spatialTrack flag value

+						case AC_QUAT_W:

+							quaternionRotation[3] = (float) value;

+							break;

+						case AC_QUAT_X:

+							quaternionRotation[0] = (float) value;

+							break;

+						case AC_QUAT_Y:

+							if (fixUpAxis) {

+								quaternionRotation[2] = -(float) value;

+							} else {

+								quaternionRotation[1] = (float) value;

+							}

+							break;

+						case AC_QUAT_Z:

+							if (fixUpAxis) {

+								quaternionRotation[1] = (float) value;

+							} else {

+								quaternionRotation[2] = (float) value;

+							}

+							break;

+						default:

+							throw new IllegalStateException("Unknown ipo curve type: " + bezierCurves[j].getType());

+					}

+				}

+				translations[index] = new Vector3f(translation[0], translation[1], translation[2]);

+				rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);

+				scales[index] = new Vector3f(scale[0], scale[1], scale[2]);

+			}

+			if (spatialTrack) {

+				calculatedTrack = new SpatialTrack(times, translations, rotations, scales);

+			} else {

+				calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);

+			}

+		}

+		return calculatedTrack;

+	}

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java
new file mode 100644
index 0000000..ea0a207
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java
@@ -0,0 +1,199 @@
+package com.jme3.scene.plugins.blender.animations;

+

+import java.util.List;

+

+import com.jme3.animation.BoneTrack;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.curves.BezierCurve;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.BlenderInputStream;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class helps to compute values from interpolation curves for features

+ * like animation or constraint influence. The curves are 3rd degree bezier

+ * curves.

+ * 

+ * @author Marcin Roguski

+ */

+public class IpoHelper extends AbstractBlenderHelper {

+

+	/**

+	 * This constructor parses the given blender version and stores the result.

+	 * Some functionalities may differ in different blender versions.

+	 * 

+	 * @param blenderVersion

+	 *            the version read from the blend file

+	 * @param fixUpAxis

+	 *            a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public IpoHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	/**

+	 * This method creates an ipo object used for interpolation calculations.

+	 * 

+	 * @param ipoStructure

+	 *            the structure with ipo definition

+	 * @param blenderContext

+	 *            the blender context

+	 * @return the ipo object

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Structure curvebase = (Structure) ipoStructure.getFieldValue("curve");

+

+		// preparing bezier curves

+		Ipo result = null;

+		List<Structure> curves = curvebase.evaluateListBase(blenderContext);// IpoCurve

+		if (curves.size() > 0) {

+			BezierCurve[] bezierCurves = new BezierCurve[curves.size()];

+			int frame = 0;

+			for (Structure curve : curves) {

+				Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");

+				List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());

+				int type = ((Number) curve.getFieldValue("adrcode")).intValue();

+				bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);

+			}

+			curves.clear();

+			result = new Ipo(bezierCurves, fixUpAxis);

+			blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result);

+		}

+		return result;

+	}

+

+	/**

+	 * This method creates an ipo object used for interpolation calculations. It

+	 * should be called for blender version 2.50 and higher.

+	 * 

+	 * @param actionStructure

+	 *            the structure with action definition

+	 * @param blenderContext

+	 *            the blender context

+	 * @return the ipo object

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Ipo result = null;

+		List<Structure> curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase(blenderContext);// FCurve

+		if (curves.size() > 0) {

+			BezierCurve[] bezierCurves = new BezierCurve[curves.size()];

+			int frame = 0;

+			for (Structure curve : curves) {

+				Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");

+				List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());

+				int type = this.getCurveType(curve, blenderContext);

+				bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);

+			}

+			curves.clear();

+			result = new Ipo(bezierCurves, fixUpAxis);

+		}

+		return result;

+	}

+

+	/**

+	 * This method returns the type of the ipo curve.

+	 * 

+	 * @param structure

+	 *            the structure must contain the 'rna_path' field and

+	 *            'array_index' field (the type is not important here)

+	 * @param blenderContext

+	 *            the blender context

+	 * @return the type of the curve

+	 */

+	public int getCurveType(Structure structure, BlenderContext blenderContext) {

+		// reading rna path first

+		BlenderInputStream bis = blenderContext.getInputStream();

+		int currentPosition = bis.getPosition();

+		Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path");

+		FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress());

+		bis.setPosition(dataFileBlock.getBlockPosition());

+		String rnaPath = bis.readString();

+		bis.setPosition(currentPosition);

+		int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue();

+

+		// determining the curve type

+		if (rnaPath.endsWith("location")) {

+			return Ipo.AC_LOC_X + arrayIndex;

+		}

+		if (rnaPath.endsWith("rotation_quaternion")) {

+			return Ipo.AC_QUAT_W + arrayIndex;

+		}

+		if (rnaPath.endsWith("scale")) {

+			return Ipo.AC_SIZE_X + arrayIndex;

+		}

+		if (rnaPath.endsWith("rotation")) {

+			return Ipo.OB_ROT_X + arrayIndex;

+		}

+		throw new IllegalStateException("Unknown curve rna path: " + rnaPath);

+	}

+

+	/**

+	 * This method creates an ipo with only a single value. No track type is

+	 * specified so do not use it for calculating tracks.

+	 * 

+	 * @param constValue

+	 *            the value of this ipo

+	 * @return constant ipo

+	 */

+	public Ipo fromValue(float constValue) {

+		return new ConstIpo(constValue);

+	}

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return true;

+	}

+

+	/**

+	 * Ipo constant curve. This is a curve with only one value and no specified

+	 * type. This type of ipo cannot be used to calculate tracks. It should only

+	 * be used to calculate single value for a given frame.

+	 * 

+	 * @author Marcin Roguski

+	 */

+	private class ConstIpo extends Ipo {

+

+		/** The constant value of this ipo. */

+		private float	constValue;

+

+		/**

+		 * Constructor. Stores the constant value of this ipo.

+		 * 

+		 * @param constValue

+		 *            the constant value of this ipo

+		 */

+		public ConstIpo(float constValue) {

+			super(null, false);

+			this.constValue = constValue;

+		}

+

+		@Override

+		public float calculateValue(int frame) {

+			return constValue;

+		}

+

+		@Override

+		public float calculateValue(int frame, int curveIndex) {

+			return constValue;

+		}

+

+		@Override

+		public int getCurvesAmount() {

+			return 0;

+		}

+

+		@Override

+		public BoneTrack calculateTrack(int boneIndex, int startFrame, int stopFrame, int fps, boolean boneTrack) {

+			throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
new file mode 100644
index 0000000..e69eb3b
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
@@ -0,0 +1,118 @@
+package com.jme3.scene.plugins.blender.cameras;

+

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * A class that is used to load cameras into the scene.

+ * @author Marcin Roguski

+ */

+public class CameraHelper extends AbstractBlenderHelper {

+

+    private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());

+    protected static final int DEFAULT_CAM_WIDTH = 640;

+    protected static final int DEFAULT_CAM_HEIGHT = 480;

+    

+    /**

+     * This constructor parses the given blender version and stores the result. Some functionalities may differ in

+     * different blender versions.

+     * @param blenderVersion

+     *        the version read from the blend file

+     * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+     */

+    public CameraHelper(String blenderVersion, boolean fixUpAxis) {

+        super(blenderVersion, fixUpAxis);

+    }

+    

+	/**

+	 * This method converts the given structure to jme camera.

+	 * 

+	 * @param structure

+	 *            camera structure

+	 * @return jme camera object

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the

+	 *             blender file

+	 */

+    public Camera toCamera(Structure structure) throws BlenderFileException {

+    	if (blenderVersion >= 250) {

+            return this.toCamera250(structure);

+        } else {

+        	return this.toCamera249(structure);

+        }

+    }

+

+	/**

+	 * This method converts the given structure to jme camera. Should be used form blender 2.5+.

+	 * 

+	 * @param structure

+	 *            camera structure

+	 * @return jme camera object

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the

+	 *             blender file

+	 */

+    public Camera toCamera250(Structure structure) throws BlenderFileException {

+        Camera result = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);

+        int type = ((Number) structure.getFieldValue("type")).intValue();

+        if (type != 0 && type != 1) {

+            LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);

+            type = 0;

+        }

+        //type==0 - perspective; type==1 - orthographic; perspective is used as default

+        result.setParallelProjection(type == 1);

+        float aspect = 0;

+        float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();

+        float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();

+        if (type == 0) {

+            aspect = ((Number) structure.getFieldValue("lens")).floatValue();

+        } else {

+            aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();

+        }

+        result.setFrustumPerspective(45, aspect, clipsta, clipend);

+        return result;

+    }

+    

+    /**

+	 * This method converts the given structure to jme camera. Should be used form blender 2.49.

+	 * 

+	 * @param structure

+	 *            camera structure

+	 * @return jme camera object

+	 * @throws BlenderFileException

+	 *             an exception is thrown when there are problems with the

+	 *             blender file

+	 */

+    public Camera toCamera249(Structure structure) throws BlenderFileException {

+        Camera result = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);

+        int type = ((Number) structure.getFieldValue("type")).intValue();

+        if (type != 0 && type != 1) {

+            LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);

+            type = 0;

+        }

+        //type==0 - perspective; type==1 - orthographic; perspective is used as default

+        result.setParallelProjection(type == 1);

+        float aspect = 0;

+        float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();

+        float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();

+        if (type == 0) {

+            aspect = ((Number) structure.getFieldValue("lens")).floatValue();

+        } else {

+            aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();

+        }

+        result.setFrustumPerspective(aspect, result.getWidth() / result.getHeight(), clipsta, clipend);

+        return result;

+    }

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BlenderTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BlenderTrack.java
new file mode 100644
index 0000000..40611a1
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BlenderTrack.java
@@ -0,0 +1,147 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.io.IOException;

+

+import com.jme3.animation.AnimChannel;

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.BoneTrack;

+import com.jme3.animation.SpatialTrack;

+import com.jme3.animation.Track;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.util.TempVars;

+

+/**

+ * This class holds either the bone track or spatial track. Is made to improve

+ * code readability.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */final class BlenderTrack implements Track {

+	/** The spatial track. */

+	private SpatialTrack spatialTrack;

+	/** The bone track. */

+	private BoneTrack boneTrack;

+

+	/**

+	 * Constructs the object using spatial track (bone track is null).

+	 * 

+	 * @param spatialTrack

+	 *            the spatial track

+	 */

+	public BlenderTrack(SpatialTrack spatialTrack) {

+		this.spatialTrack = spatialTrack;

+	}

+

+	/**

+	 * Constructs the object using bone track (spatial track is null).

+	 * 

+	 * @param spatialTrack

+	 *            the spatial track

+	 */

+	public BlenderTrack(BoneTrack boneTrack) {

+		this.boneTrack = boneTrack;

+	}

+

+	/**

+	 * @return the stored track (either bone or spatial)

+	 */

+	public Track getTrack() {

+		return boneTrack != null ? boneTrack : spatialTrack;

+	}

+

+	/**

+	 * @return the array of rotations of this track

+	 */

+	public Quaternion[] getRotations() {

+		if (boneTrack != null) {

+			return boneTrack.getRotations();

+		}

+		return spatialTrack.getRotations();

+	}

+

+	/**

+	 * @return the array of scales for this track

+	 */

+	public Vector3f[] getScales() {

+		if (boneTrack != null) {

+			return boneTrack.getScales();

+		}

+		return spatialTrack.getScales();

+	}

+

+	/**

+	 * @return the arrays of time for this track

+	 */

+	public float[] getTimes() {

+		if (boneTrack != null) {

+			return boneTrack.getTimes();

+		}

+		return spatialTrack.getTimes();

+	}

+

+	/**

+	 * @return the array of translations of this track

+	 */

+	public Vector3f[] getTranslations() {

+		if (boneTrack != null) {

+			return boneTrack.getTranslations();

+		}

+		return spatialTrack.getTranslations();

+	}

+

+	/**

+	 * Set the translations, rotations and scales for this bone track

+	 * 

+	 * @param times

+	 *            a float array with the time of each frame

+	 * @param translations

+	 *            the translation of the bone for each frame

+	 * @param rotations

+	 *            the rotation of the bone for each frame

+	 * @param scales

+	 *            the scale of the bone for each frame

+	 */

+	public void setKeyframes(float[] times, Vector3f[] translations,

+			Quaternion[] rotations, Vector3f[] scales) {

+		if (boneTrack != null) {

+			boneTrack.setKeyframes(times, translations, rotations, scales);

+		} else {

+			spatialTrack.setKeyframes(times, translations, rotations, scales);

+		}

+	}

+

+	@Override

+	public void write(JmeExporter ex) throws IOException {

+	}

+

+	@Override

+	public void read(JmeImporter im) throws IOException {

+	}

+

+	@Override

+	public void setTime(float time, float weight, AnimControl control,

+			AnimChannel channel, TempVars vars) {

+		if (boneTrack != null) {

+			boneTrack.setTime(time, weight, control, channel, vars);

+		} else {

+			spatialTrack.setTime(time, weight, control, channel, vars);

+		}

+	}

+

+	@Override

+	public float getLength() {

+		return spatialTrack == null ? boneTrack.getLength() : spatialTrack

+				.getLength();

+	}

+

+	@Override

+	public BlenderTrack clone() {

+		if (boneTrack != null) {

+			return new BlenderTrack(boneTrack.clone());

+		}

+		return new BlenderTrack(spatialTrack.clone());

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java
new file mode 100644
index 0000000..6846d14
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java
@@ -0,0 +1,147 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.animation.Bone;

+import com.jme3.animation.BoneTrack;

+import com.jme3.animation.Skeleton;

+import com.jme3.animation.SpatialTrack;

+import com.jme3.animation.Track;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+

+/**

+ * The implementation of a constraint.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public abstract class Constraint {

+	/** The name of this constraint. */

+	protected final String name;

+	/** The constraint's owner. */

+	protected final Feature owner;

+	/** The constraint's target. */

+	protected final Feature target;

+	/** The structure with constraint's data. */

+	protected final Structure data;

+	/** The ipo object defining influence. */

+	protected final Ipo ipo;

+	/** The blender context. */

+	protected final BlenderContext blenderContext;

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public Constraint(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		this.blenderContext = blenderContext;

+		this.name = constraintStructure.getFieldValue("name").toString();

+		Pointer pData = (Pointer) constraintStructure.getFieldValue("data");

+		if (pData.isNotNull()) {

+			data = pData.fetchData(blenderContext.getInputStream()).get(0);

+			Pointer pTar = (Pointer)data.getFieldValue("tar");

+			if(pTar!= null && pTar.isNotNull()) {

+				Structure targetStructure = pTar.fetchData(blenderContext.getInputStream()).get(0);

+				Long targetOMA = pTar.getOldMemoryAddress();

+				Space targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());

+				ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+				Spatial target = (Spatial) objectHelper.toObject(targetStructure, blenderContext);

+				this.target = new Feature(target, targetSpace, targetOMA, blenderContext);

+			} else {

+				this.target = null;

+			}

+		} else {

+			throw new BlenderFileException("The constraint has no data specified!");

+		}

+		Space ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());

+		this.owner = new Feature(ownerSpace, ownerOMA, blenderContext);

+		this.ipo = influenceIpo;

+	}

+

+	/**

+	 * This method bakes the required sontraints into its owner.

+	 */

+	public void bake() {

+		this.owner.update();

+		if(this.target != null) {

+			this.target.update();

+		}

+		this.bakeConstraint();

+	}

+	

+	/**

+	 * Bake the animation's constraints into its owner.

+	 */

+	protected abstract void bakeConstraint();

+	

+    /**

+     * This method returns the bone traces for the bone that is affected by the given constraint.

+     * @param skeleton

+     *        the skeleton containing bones

+     * @param boneAnimation

+     *        the bone animation that affects the skeleton

+     * @return the bone track for the bone that is being affected by the constraint

+     */

+    protected BlenderTrack getTrack(Object owner, Skeleton skeleton, Animation animation) {

+    	if(owner instanceof Bone) {

+    		int boneIndex = skeleton.getBoneIndex((Bone) owner);

+    		for (Track track : animation.getTracks()) {

+                if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) {

+                    return new BlenderTrack(((BoneTrack) track));

+                }

+            }

+    		throw new IllegalStateException("Cannot find track for: " + owner);

+    	} else {

+    		return new BlenderTrack((SpatialTrack)animation.getTracks()[0]);

+    	}

+    }

+    

+	/**

+	 * The space of target or owner transformation.

+	 * 

+	 * @author Marcin Roguski (Kaelthas)

+	 */

+	public static enum Space {

+

+		CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID;

+

+		/**

+		 * This method returns the enum instance when given the appropriate

+		 * value from the blend file.

+		 * 

+		 * @param c

+		 *            the blender's value of the space modifier

+		 * @return the scape enum instance

+		 */

+		public static Space valueOf(byte c) {

+			switch (c) {

+				case 0:

+					return CONSTRAINT_SPACE_WORLD;

+				case 1:

+					return CONSTRAINT_SPACE_LOCAL;

+				case 2:

+					return CONSTRAINT_SPACE_POSE;

+				case 3:

+					return CONSTRAINT_SPACE_PARLOCAL;

+				default:

+					return CONSTRAINT_SPACE_INVALID;

+			}

+		}

+	}

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintAction.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintAction.java
new file mode 100644
index 0000000..6c58ac0
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintAction.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Action' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintAction extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintAction.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintAction(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+	

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Action' constraint

+		LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintChildOf.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintChildOf.java
new file mode 100644
index 0000000..592d740
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintChildOf.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'ChildOf' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintChildOf extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintChildOf.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintChildOf(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement ChildOf constraint

+		LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintClampTo.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintClampTo.java
new file mode 100644
index 0000000..5c265c7
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintClampTo.java
@@ -0,0 +1,43 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Clamp to' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintClampTo extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintClampTo.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintClampTo(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext)

+			throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		//TODO: implement when curves are implemented

+		LOGGER.log(Level.INFO, "'Clamp to' not yet implemented! Curves not yet implemented!", name);

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDampTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDampTrack.java
new file mode 100644
index 0000000..cf2784f
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDampTrack.java
@@ -0,0 +1,43 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * The damp track constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintDampTrack extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintDampTrack.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintDampTrack(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		// TODO Auto-generated constructor stub

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO Auto-generated method stub

+		LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDistLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDistLimit.java
new file mode 100644
index 0000000..ff3b99a
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintDistLimit.java
@@ -0,0 +1,120 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Matrix4f;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Dist limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintDistLimit extends Constraint {

+	private static final int LIMITDIST_INSIDE = 0;

+	private static final int LIMITDIST_OUTSIDE = 1;

+	private static final int LIMITDIST_ONSURFACE = 2;

+    

+	protected int mode;

+	protected float dist;

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintDistLimit(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		mode = ((Number) data.getFieldValue("mode")).intValue();

+		dist = ((Number) data.getFieldValue("dist")).floatValue();

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			if(owner instanceof Spatial) {

+				Vector3f targetLocation = ((Spatial) owner).getWorldTranslation();

+				for(Animation animation : animData.anims) {

+					BlenderTrack blenderTrack = this.getTrack(owner, animData.skeleton, animation);

+					int maxFrames = blenderTrack.getTimes().length;

+					Vector3f[] translations = blenderTrack.getTranslations();

+					for (int frame = 0; frame < maxFrames; ++frame) {

+						Vector3f v = translations[frame].subtract(targetLocation);

+						this.distLimit(v, targetLocation, ipo.calculateValue(frame));

+						translations[frame].addLocal(v);

+					}

+					blenderTrack.setKeyframes(blenderTrack.getTimes(), translations, blenderTrack.getRotations(), blenderTrack.getScales());

+				}

+			}

+		}

+		

+		// apply static constraint only to spatials

+		if(owner instanceof Spatial) {

+			Matrix4f targetWorldMatrix = target.getWorldTransformMatrix();

+			Vector3f targetLocation = targetWorldMatrix.toTranslationVector();

+			Matrix4f m = this.owner.getParentWorldTransformMatrix();

+			m.invertLocal();

+			Matrix4f ownerWorldMatrix = this.owner.getWorldTransformMatrix();

+			Vector3f ownerLocation = ownerWorldMatrix.toTranslationVector();

+			this.distLimit(ownerLocation, targetLocation, ipo.calculateValue(0));

+			((Spatial) owner).setLocalTranslation(m.mult(ownerLocation));

+		}

+	}

+	

+	/**

+	 * 

+	 * @param currentLocation

+	 * @param targetLocation

+	 * @param influence

+	 */

+	private void distLimit(Vector3f currentLocation, Vector3f targetLocation, float influence) {

+		Vector3f v = currentLocation.subtract(targetLocation);

+		float currentDistance = v.length();

+		

+		switch (mode) {

+			case LIMITDIST_INSIDE:

+				if (currentDistance >= dist) {

+					v.normalizeLocal();

+					v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));

+					currentLocation.set(v.addLocal(targetLocation));

+				}

+				break;

+			case LIMITDIST_ONSURFACE:

+				if (currentDistance > dist) {

+					v.normalizeLocal();

+					v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));

+					currentLocation.set(v.addLocal(targetLocation));

+				} else if(currentDistance < dist) {

+					v.normalizeLocal().multLocal(dist * influence);

+					currentLocation.set(targetLocation.add(v));

+				}

+				break;

+			case LIMITDIST_OUTSIDE:

+				if (currentDistance <= dist) {

+					v = targetLocation.subtract(currentLocation).normalizeLocal().multLocal(dist * influence);

+					currentLocation.set(targetLocation.add(v));

+				}

+				break;

+			default:

+				throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintFollowPath.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintFollowPath.java
new file mode 100644
index 0000000..5f8be2c
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintFollowPath.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Follow path' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintFollowPath extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintFollowPath.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintFollowPath(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		//TODO: implement when curves are implemented

+		LOGGER.log(Level.INFO, "'Follow path' not implemented! Curves not yet implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
new file mode 100644
index 0000000..d75728f
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
@@ -0,0 +1,203 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.lang.reflect.InvocationTargetException;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.animations.IpoHelper;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class should be used for constraint calculations.

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class ConstraintHelper extends AbstractBlenderHelper {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());

+	

+	private static final Map<String, Class<? extends Constraint>> constraintClasses = new HashMap<String, Class<? extends Constraint>>(22);

+	static {

+		constraintClasses.put("bActionConstraint", ConstraintAction.class);

+		constraintClasses.put("bChildOfConstraint", ConstraintChildOf.class);

+		constraintClasses.put("bClampToConstraint", ConstraintClampTo.class);

+		constraintClasses.put("bDistLimitConstraint", ConstraintDistLimit.class);

+		constraintClasses.put("bFollowPathConstraint", ConstraintFollowPath.class);

+		constraintClasses.put("bKinematicConstraint", ConstraintInverseKinematics.class);

+		constraintClasses.put("bLockTrackConstraint", ConstraintLockTrack.class);

+		constraintClasses.put("bLocateLikeConstraint", ConstraintLocLike.class);

+		constraintClasses.put("bLocLimitConstraint", ConstraintLocLimit.class);

+		constraintClasses.put("bMinMaxConstraint", ConstraintMinMax.class);

+		constraintClasses.put("bNullConstraint", ConstraintNull.class);

+		constraintClasses.put("bPythonConstraint", ConstraintPython.class);

+		constraintClasses.put("bRigidBodyJointConstraint", ConstraintRigidBodyJoint.class);

+		constraintClasses.put("bRotateLikeConstraint", ConstraintRotLike.class);

+		constraintClasses.put("bShrinkWrapConstraint", ConstraintShrinkWrap.class);

+		constraintClasses.put("bSizeLikeConstraint", ConstraintSizeLike.class);

+		constraintClasses.put("bSizeLimitConstraint", ConstraintSizeLimit.class);

+		constraintClasses.put("bStretchToConstraint", ConstraintStretchTo.class);

+		constraintClasses.put("bTransformConstraint", ConstraintTransform.class);

+		constraintClasses.put("bRotLimitConstraint", ConstraintRotLimit.class);

+		//Blender 2.50+

+		constraintClasses.put("bSplineIKConstraint", ConstraintSplineInverseKinematic.class);

+		constraintClasses.put("bDampTrackConstraint", ConstraintDampTrack.class);

+		constraintClasses.put("bPivotConstraint", ConstraintDampTrack.class);

+	}

+	

+	/**

+	 * Helper constructor. It's main task is to generate the affection functions. These functions are common to all

+	 * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall

+	 * consider refactoring. The constructor parses the given blender version and stores the result. Some

+	 * functionalities may differ in different blender versions.

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	/**

+	 * This method reads constraints for for the given structure. The

+	 * constraints are loaded only once for object/bone.

+	 * 

+	 * @param objectStructure

+	 *            the structure we read constraint's for

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 */

+	public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {

+		LOGGER.fine("Loading constraints.");

+		// reading influence ipos for the constraints

+		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);

+		Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();

+		Pointer pActions = (Pointer) objectStructure.getFieldValue("action");

+		if (pActions.isNotNull()) {

+			List<Structure> actions = pActions.fetchData(blenderContext.getInputStream());

+			for (Structure action : actions) {

+				Structure chanbase = (Structure) action.getFieldValue("chanbase");

+				List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);

+				for (Structure actionChannel : actionChannels) {

+					Map<String, Ipo> ipos = new HashMap<String, Ipo>();

+					Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");

+					List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext);

+					for (Structure constraintChannel : constraintChannels) {

+						Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");

+						if (pIpo.isNotNull()) {

+							String constraintName = constraintChannel.getFieldValue("name").toString();

+							Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext);

+							ipos.put(constraintName, ipo);

+						}

+					}

+					String actionName = actionChannel.getFieldValue("name").toString();

+					constraintsIpos.put(actionName, ipos);

+				}

+			}

+		}

+		

+		//loading constraints connected with the object's bones

+		Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");

+		if (pPose.isNotNull()) {

+			List<Structure> poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext);

+			for (Structure poseChannel : poseChannels) {

+				List<Constraint> constraintsList = new ArrayList<Constraint>();

+				Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());

+				

+				//the name is read directly from structure because bone might not yet be loaded

+				String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();

+				List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);

+				for (Structure constraint : constraints) {

+					String constraintName = constraint.getFieldValue("name").toString();

+					Map<String, Ipo> ipoMap = constraintsIpos.get(name);

+					Ipo ipo = ipoMap==null ? null : ipoMap.get(constraintName);

+					if (ipo == null) {

+						float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();

+						ipo = ipoHelper.fromValue(enforce);

+					}

+					constraintsList.add(this.createConstraint(constraint, boneOMA, ipo, blenderContext));

+				}

+				blenderContext.addConstraints(boneOMA, constraintsList);

+			}

+		}

+

+		//loading constraints connected with the object itself

+		List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext);

+		List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());

+		

+		for(Structure constraint : constraints) {

+			String constraintName = constraint.getFieldValue("name").toString();

+			String objectName = objectStructure.getName();

+			

+			Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);

+			Ipo ipo = objectConstraintsIpos!=null ? objectConstraintsIpos.get(constraintName) : null;

+			if (ipo == null) {

+				float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();

+				ipo = ipoHelper.fromValue(enforce);

+			}

+			constraintsList.add(this.createConstraint(constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));

+		}

+		blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);

+	}

+	

+	/**

+	 * This method creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint's owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	protected Constraint createConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, 

+						BlenderContext blenderContext) throws BlenderFileException {

+		String constraintClassName = this.getConstraintClassName(constraintStructure, blenderContext);

+		Class<? extends Constraint> constraintClass = constraintClasses.get(constraintClassName);

+		if(constraintClass != null) {

+			try {

+				return (Constraint) constraintClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, influenceIpo, 

+						blenderContext);

+			} catch (IllegalArgumentException e) {

+				throw new BlenderFileException(e.getLocalizedMessage(), e);

+			} catch (SecurityException e) {

+				throw new BlenderFileException(e.getLocalizedMessage(), e);

+			} catch (InstantiationException e) {

+				throw new BlenderFileException(e.getLocalizedMessage(), e);

+			} catch (IllegalAccessException e) {

+				throw new BlenderFileException(e.getLocalizedMessage(), e);

+			} catch (InvocationTargetException e) {

+				throw new BlenderFileException(e.getLocalizedMessage(), e);

+			}

+		} else {

+			throw new BlenderFileException("Unknown constraint type: " + constraintClassName);

+		}

+	}

+	

+	protected String getConstraintClassName(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Pointer pData = (Pointer)constraintStructure.getFieldValue("data");

+		if(pData.isNotNull()) {

+			Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);

+			return data.getType();

+			

+		}

+		return constraintStructure.getType();

+	}

+	

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return true;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintInverseKinematics.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintInverseKinematics.java
new file mode 100644
index 0000000..ef85446
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintInverseKinematics.java
@@ -0,0 +1,162 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.animation.Skeleton;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.CalculationBone;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Inverse kinematics' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintInverseKinematics extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintInverseKinematics.class.getName());

+	private static final float IK_SOLVER_ERROR = 0.5f;

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintInverseKinematics(Structure constraintStructure,

+			Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+//		try {

+			// IK solver is only attached to bones

+//			Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);

+//			AnimData animData = blenderContext.getAnimData(ownerOMA);

+//			if(animData == null) {

+				//TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza

+//			}

+			

+			//prepare a list of all parents of this bone

+//			CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation);

+			

+			// get the target point

+//			Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE);

+//			Vector3f pt = null;// Point Target

+//			if (targetObject instanceof Bone) {

+//				pt = ((Bone) targetObject).getModelSpacePosition();

+//			} else if (targetObject instanceof Spatial) {

+//				pt = ((Spatial) targetObject).getWorldTranslation();

+//			} else if (targetObject instanceof Skeleton) {

+//				Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE);

+//				ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+//				Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext);

+//				pt = transform.getTranslation();

+//			} else {

+//				throw new IllegalStateException(

+//						"Unknown target object type! Should be Node, Bone or Skeleton and there is: "

+//						+ targetObject.getClass().getName());

+//			}

+			

+			//fetching the owner's bone track

+//			BoneTrack ownerBoneTrack = null;

+//			int boneIndex = skeleton.getBoneIndex(ownerBone);

+//			for (int i = 0; i < boneAnimation.getTracks().length; ++i) {

+//				if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) {

+//					ownerBoneTrack = boneAnimation.getTracks()[i];

+//					break;

+//				}

+//			}

+//			int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length;

+//			

+//			// preparing data

+//			int maxIterations = ((Number) data.getFieldValue("iterations")).intValue();

+//			CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation);

+//			for (int i = 0; i < bones.length; ++i) {

+//				System.out.println(Arrays.toString(bones[i].track.getTranslations()));

+//				System.out.println(Arrays.toString(bones[i].track.getRotations()));

+//				System.out.println("===============================");

+//			}

+//			Quaternion rotation = new Quaternion();

+//			//all tracks should have the same amount of frames

+//			int framesCount = bones[0].getBoneFramesCount();

+//			assert framesCount >=1;

+//			for (int frame = 0; frame < framesCount; ++frame) {

+//				float error = IK_SOLVER_ERROR;

+//				int iteration = 0;

+//				while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) {

+//					// rotating the bones

+//					for (int i = 0; i < bones.length - 1; ++i) {

+//						Vector3f pe = bones[i].getEndPoint();

+//						Vector3f pc = bones[i + 1].getWorldTranslation().clone();

+//

+//						Vector3f peSUBpc = pe.subtract(pc).normalizeLocal();

+//						Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal();

+//

+//						float theta = FastMath.acos(peSUBpc.dot(ptSUBpc));

+//						Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal();

+//						bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame);

+//					}

+//					error = pt.subtract(bones[0].getEndPoint()).length();

+//					++iteration;

+//				}

+//			}

+//

+//			for (CalculationBone bone : bones) {

+//				bone.applyCalculatedTracks();

+//			}

+//

+//			System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

+//			for (int i = 0; i < bones.length; ++i) {

+//				System.out.println(Arrays.toString(bones[i].track.getTranslations()));

+//				System.out.println(Arrays.toString(bones[i].track.getRotations()));

+//				System.out.println("===============================");

+//			}

+//		} catch(BlenderFileException e) {

+//			LOGGER.severe(e.getLocalizedMessage());

+//		}

+	}

+	

+	/**

+	 * This method returns bones used for rotation calculations.

+	 * @param bone

+	 *        the bone to which the constraint is applied

+	 * @param skeleton

+	 *        the skeleton owning the bone and its ancestors

+	 * @param boneAnimation

+	 *        the bone animation data that stores the traces for the skeleton's bones

+	 * @return a list of bones to imitate the bone's movement during IK solving

+	 */

+	private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) {

+//		Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);

+//		List<CalculationBone> bonesList = new ArrayList<CalculationBone>();

+//		do {

+//			bonesList.add(new CalculationBone(ownerBone, 1));

+//			int boneIndex = skeleton.getBoneIndex(ownerBone);

+//			for (int i = 0; i < boneAnimation.getTracks().length; ++i) {

+//				if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) {

+//					bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i]));

+//					break;

+//				}

+//			}

+//			ownerBone = ownerBone.getParent();

+//		} while (ownerBone != null);

+//		//attaching children

+//		CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]);

+//		for (int i = result.length - 1; i > 0; --i) {

+//			result[i].attachChild(result[i - 1]);

+//		}

+//		return result;

+		return null;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLike.java
new file mode 100644
index 0000000..834dfb4
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLike.java
@@ -0,0 +1,122 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Loc like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLocLike extends Constraint {

+	private static final int LOCLIKE_X = 0x01;

+	private static final int LOCLIKE_Y = 0x02;

+	private static final int LOCLIKE_Z = 0x04;

+    //protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender

+    private static final int LOCLIKE_X_INVERT = 0x10;

+    private static final int LOCLIKE_Y_INVERT = 0x20;

+    private static final int LOCLIKE_Z_INVERT = 0x40;

+    private static final int LOCLIKE_OFFSET = 0x80;

+    

+    protected int flag;

+    

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintLocLike(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+		

+		if(blenderContext.getBlenderKey().isFixUpAxis()) {

+			//swapping Y and X limits flag in the bitwise flag

+			int y = flag & LOCLIKE_Y;

+			int invY = flag & LOCLIKE_Y_INVERT;

+			int z = flag & LOCLIKE_Z;

+			int invZ = flag & LOCLIKE_Z_INVERT;

+			flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;//clear the other flags to swap them

+			flag |= y << 2;

+			flag |= invY << 2;

+			flag |= z >> 2;

+			flag |= invZ >> 2;

+		}

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			Transform targetTransform = this.target.getTransform();

+			for(Animation animation : animData.anims) {

+				BlenderTrack blenderTrack = this.getTrack(owner, animData.skeleton, animation);

+				Vector3f[] translations = blenderTrack.getTranslations();

+				int maxFrames = translations.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					this.locLike(translations[frame], targetTransform.getTranslation(), ipo.calculateValue(frame));

+				}

+				blenderTrack.setKeyframes(blenderTrack.getTimes(), translations, blenderTrack.getRotations(), blenderTrack.getScales());

+			}

+		}

+		

+		if(owner instanceof Spatial) {

+			Transform targetTransform = this.target.getTransform();

+			Transform ownerTransform = this.owner.getTransform();

+			Vector3f ownerLocation = ownerTransform.getTranslation();

+			this.locLike(ownerLocation, targetTransform.getTranslation(), ipo.calculateValue(0));

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+	

+	private void locLike(Vector3f ownerLocation, Vector3f targetLocation, float influence) {

+		Vector3f startLocation = ownerLocation.clone();

+		Vector3f offset = Vector3f.ZERO;

+		if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location

+			offset = startLocation;

+		}

+

+		if ((flag & LOCLIKE_X) != 0) {

+			ownerLocation.x = targetLocation.x;

+			if ((flag & LOCLIKE_X_INVERT) != 0) {

+				ownerLocation.x = -ownerLocation.x;

+			}

+		}

+		if ((flag & LOCLIKE_Y) != 0) {

+			ownerLocation.y = targetLocation.y;

+			if ((flag & LOCLIKE_Y_INVERT) != 0) {

+				ownerLocation.y = -ownerLocation.y;

+			}

+		}

+		if ((flag & LOCLIKE_Z) != 0) {

+			ownerLocation.z = targetLocation.z;

+			if ((flag & LOCLIKE_Z_INVERT) != 0) {

+				ownerLocation.z = -ownerLocation.z;

+			}

+		}

+		ownerLocation.addLocal(offset);

+		

+		if(influence < 1.0f) {

+			startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);

+			ownerLocation.addLocal(startLocation);

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLimit.java
new file mode 100644
index 0000000..74e2ec1
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLocLimit.java
@@ -0,0 +1,137 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Loc limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLocLimit extends Constraint {

+    private static final int LIMIT_XMIN = 0x01;

+    private static final int LIMIT_XMAX = 0x02;

+    private static final int LIMIT_YMIN = 0x04;

+    private static final int LIMIT_YMAX = 0x08;

+    private static final int LIMIT_ZMIN = 0x10;

+    private static final int LIMIT_ZMAX = 0x20;

+    

+    protected float[][] limits = new float[3][2];

+    protected int flag;

+    

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintLocLimit(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+		if(blenderContext.getBlenderKey().isFixUpAxis()) {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[2][0] = -((Number) data.getFieldValue("ymin")).floatValue();

+			limits[2][1] = -((Number) data.getFieldValue("ymax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+			

+			//swapping Y and X limits flag in the bitwise flag

+			int ymin = flag & LIMIT_YMIN;

+			int ymax = flag & LIMIT_YMAX;

+			int zmin = flag & LIMIT_ZMIN;

+			int zmax = flag & LIMIT_ZMAX;

+			flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them

+			flag |= ymin << 2;

+			flag |= ymax << 2;

+			flag |= zmin >> 2;

+			flag |= zmax >> 2;

+		} else {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("ymin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("ymax")).floatValue();

+			limits[2][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[2][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+		}

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			for(Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				Vector3f[] translations = track.getTranslations();

+				int maxFrames = translations.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					this.locLimit(translations[frame], ipo.calculateValue(frame));

+				}

+				track.setKeyframes(track.getTimes(), translations, track.getRotations(), track.getScales());

+			}

+		}

+		

+		if(owner instanceof Spatial) {

+			Transform ownerTransform = this.owner.getTransform();

+			Vector3f ownerLocation = ownerTransform.getTranslation();

+			this.locLimit(ownerLocation, ipo.calculateValue(0));

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+	

+	/**

+	 * This method modifies the given translation.

+	 * @param translation the translation to be modified.

+	 * @param influence the influence value

+	 */

+	private void locLimit(Vector3f translation, float influence) {

+		if ((flag & LIMIT_XMIN) != 0) {

+			if (translation.x < limits[0][0]) {

+				translation.x -= (translation.x - limits[0][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_XMAX) != 0) {

+			if (translation.x > limits[0][1]) {

+				translation.x -= (translation.x - limits[0][1]) * influence;

+			}

+		}

+		if ((flag & LIMIT_YMIN) != 0) {

+			if (translation.y < limits[1][0]) {

+				translation.y -= (translation.y - limits[1][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_YMAX) != 0) {

+			if (translation.y > limits[1][1]) {

+				translation.y -= (translation.y - limits[1][1]) * influence;

+			}

+		}

+		if ((flag & LIMIT_ZMIN) != 0) {

+			if (translation.z < limits[2][0]) {

+				translation.z -= (translation.z - limits[2][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_ZMAX) != 0) {

+			if (translation.z > limits[2][1]) {

+				translation.z -= (translation.z - limits[2][1]) * influence;

+			}

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLockTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLockTrack.java
new file mode 100644
index 0000000..e54d70a
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintLockTrack.java
@@ -0,0 +1,43 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Action' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintLockTrack extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintLockTrack.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintLockTrack(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext)

+			throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Lock track' constraint

+		LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintMinMax.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintMinMax.java
new file mode 100644
index 0000000..f4a5456
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintMinMax.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Min max' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintMinMax extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintMinMax.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintMinMax(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Min max' constraint

+		LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintNull.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintNull.java
new file mode 100644
index 0000000..4409969
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintNull.java
@@ -0,0 +1,37 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class represents 'Null' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintNull extends Constraint {

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintNull(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext)

+			throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPivot.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPivot.java
new file mode 100644
index 0000000..f0f3bae
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPivot.java
@@ -0,0 +1,44 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * The pivot constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintPivot extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintPivot.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintPivot(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo,

+			BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		// TODO Auto-generated constructor stub

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO Auto-generated method stub

+		LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPython.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPython.java
new file mode 100644
index 0000000..a0beb78
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintPython.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Python' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintPython extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintPython.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintPython(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Python' constraint

+		LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRigidBodyJoint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRigidBodyJoint.java
new file mode 100644
index 0000000..b398ae6
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRigidBodyJoint.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Rigid body joint' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintRigidBodyJoint extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintRigidBodyJoint.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintRigidBodyJoint(Structure constraintStructure,

+			Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Rigid body joint' constraint

+		LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLike.java
new file mode 100644
index 0000000..db0c93a
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLike.java
@@ -0,0 +1,114 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Transform;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Rot like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintRotLike extends Constraint {

+	private static final int ROTLIKE_X = 0x01;

+	private static final int ROTLIKE_Y = 0x02;

+	private static final int ROTLIKE_Z = 0x04;

+	private static final int ROTLIKE_X_INVERT = 0x10;

+    private static final int ROTLIKE_Y_INVERT = 0x20;

+    private static final int ROTLIKE_Z_INVERT = 0x40;

+    private static final int ROTLIKE_OFFSET = 0x80;

+    

+    protected int flag;

+    

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintRotLike(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			Transform targetTransform = this.target.getTransform();

+			Quaternion targetRotation = targetTransform.getRotation();

+			for(Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				float[] targetAngles = targetRotation.toAngles(null);

+				Quaternion[] rotations = track.getRotations();

+				int maxFrames = rotations.length;

+				float[] angles = new float[3];

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					rotations[frame].toAngles(angles);

+					this.rotLike(rotations[frame], angles, targetAngles, ipo.calculateValue(frame));

+				}

+				track.setKeyframes(track.getTimes(), track.getTranslations(), rotations, track.getScales());

+			}

+		}

+		

+		if(owner instanceof Spatial) {

+			Transform targetTransform = this.target.getTransform();

+			Transform ownerTransform = this.owner.getTransform();

+			Quaternion ownerRotation = ownerTransform.getRotation();

+			this.rotLike(ownerRotation, ownerRotation.toAngles(null), targetTransform.getRotation().toAngles(null), ipo.calculateValue(0));

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+	

+	private void rotLike(Quaternion ownerRotation, float[] ownerAngles, float[] targetAngles, float influence) {

+		Quaternion startRotation = ownerRotation.clone();

+		Quaternion offset = Quaternion.IDENTITY;

+		if ((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation

+			offset = startRotation;

+		}

+

+		if ((flag & ROTLIKE_X) != 0) {

+			ownerAngles[0] = targetAngles[0];

+			if ((flag & ROTLIKE_X_INVERT) != 0) {

+				ownerAngles[0] = -ownerAngles[0];

+			}

+		}

+		if ((flag & ROTLIKE_Y) != 0) {

+			ownerAngles[1] = targetAngles[1];

+			if ((flag & ROTLIKE_Y_INVERT) != 0) {

+				ownerAngles[1] = -ownerAngles[1];

+			}

+		}

+		if ((flag & ROTLIKE_Z) != 0) {

+			ownerAngles[2] = targetAngles[2];

+			if ((flag & ROTLIKE_Z_INVERT) != 0) {

+				ownerAngles[2] = -ownerAngles[2];

+			}

+		}

+		ownerRotation.fromAngles(ownerAngles).multLocal(offset);

+

+		if(influence < 1.0f) {

+			

+//			startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);

+//			ownerLocation.addLocal(startLocation);

+			//TODO

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLimit.java
new file mode 100644
index 0000000..d63f342
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintRotLimit.java
@@ -0,0 +1,180 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.animation.Bone;

+import com.jme3.math.FastMath;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Transform;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Rot limit' constraint type in blender.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ConstraintRotLimit extends Constraint {

+	private static final int	LIMIT_XROT	= 0x01;

+	private static final int	LIMIT_YROT	= 0x02;

+	private static final int	LIMIT_ZROT	= 0x04;

+

+	protected float[][]			limits		= new float[3][2];

+	protected int				flag;

+	protected boolean			updated;

+

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintRotLimit(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+		if (blenderContext.getBlenderKey().isFixUpAxis() && owner.spatial != null) {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[2][0] = -((Number) data.getFieldValue("ymin")).floatValue();

+			limits[2][1] = -((Number) data.getFieldValue("ymax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+

+			// swapping Y and X limits flag in the bitwise flag

+			int limitY = flag & LIMIT_YROT;

+			int limitZ = flag & LIMIT_ZROT;

+			flag &= LIMIT_XROT;// clear the other flags to swap them

+			flag |= limitY << 1;

+			flag |= limitZ >> 1;

+		} else {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("ymin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("ymax")).floatValue();

+			limits[2][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[2][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+		}

+

+		// until blender 2.49 the rotations values were stored in degrees

+		if (blenderContext.getBlenderVersion() <= 249) {

+			for (int i = 0; i < limits.length; ++i) {

+				limits[i][0] *= FastMath.DEG_TO_RAD;

+				limits[i][1] *= FastMath.DEG_TO_RAD;

+			}

+		}

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		this.update();

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if (animData != null) {

+			for (Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				Quaternion[] rotations = track.getRotations();

+				float[] angles = new float[3];

+				int maxFrames = rotations.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					rotations[frame].toAngles(angles);

+					this.rotLimit(angles, ipo.calculateValue(frame));

+					rotations[frame].fromAngles(angles);

+				}

+				track.setKeyframes(track.getTimes(), track.getTranslations(), rotations, track.getScales());

+			}

+		}

+

+		if (owner instanceof Spatial) {

+			Transform ownerTransform = this.owner.getTransform();

+			float[] angles = ownerTransform.getRotation().toAngles(null);

+			this.rotLimit(angles, ipo.calculateValue(0));

+			ownerTransform.getRotation().fromAngles(angles);

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+

+	/**

+	 * This method computes new constrained angles.

+	 * 

+	 * @param angles

+	 *            angles to be altered

+	 * @param influence

+	 *            the alteration influence

+	 */

+	private void rotLimit(float[] angles, float influence) {

+		if ((flag & LIMIT_XROT) != 0) {

+			float difference = 0.0f;

+			if (angles[0] < limits[0][0]) {

+				difference = (angles[0] - limits[0][0]) * influence;

+			} else if (angles[0] > limits[0][1]) {

+				difference = (angles[0] - limits[0][1]) * influence;

+			}

+			angles[0] -= difference;

+		}

+		if ((flag & LIMIT_YROT) != 0) {

+			float difference = 0.0f;

+			if (angles[1] < limits[1][0]) {

+				difference = (angles[1] - limits[1][0]) * influence;

+			} else if (angles[1] > limits[1][1]) {

+				difference = (angles[1] - limits[1][1]) * influence;

+			}

+			angles[1] -= difference;

+		}

+		if ((flag & LIMIT_ZROT) != 0) {

+			float difference = 0.0f;

+			if (angles[2] < limits[2][0]) {

+				difference = (angles[2] - limits[2][0]) * influence;

+			} else if (angles[2] > limits[2][1]) {

+				difference = (angles[2] - limits[2][1]) * influence;

+			}

+			angles[2] -= difference;

+		}

+	}

+

+	/**

+	 * This method is called before baking (performes its operations only once).

+	 * It is important to update the state of the limits and owner/target before

+	 * baking the constraint.

+	 */

+	private void update() {

+		if (!updated) {

+			updated = true;

+			if (owner != null) {

+				owner.update();

+			}

+			if (target != null) {

+				target.update();

+			}

+			if (this.owner.getObject() instanceof Bone) {// for bones we need to

+															// change the sign

+															// of the limits

+				for (int i = 0; i < limits.length; ++i) {

+					limits[i][0] *= -1;

+					limits[i][1] *= -1;

+				}

+			}

+

+			// sorting the limits (lower is always first)

+			for (int i = 0; i < limits.length; ++i) {

+				if (limits[i][0] > limits[i][1]) {

+					float temp = limits[i][0];

+					limits[i][0] = limits[i][1];

+					limits[i][1] = temp;

+				}

+			}

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintShrinkWrap.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintShrinkWrap.java
new file mode 100644
index 0000000..7352032
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintShrinkWrap.java
@@ -0,0 +1,92 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.List;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Shrink wrap' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintShrinkWrap extends Constraint {

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintShrinkWrap(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		//loading mesh points (blender ensures that the target is a mesh-object)

+		List<Vector3f> pts = new ArrayList<Vector3f>();

+		Node target = (Node) this.target.getObject();

+		for(Spatial spatial : target.getChildren()) {

+			if(spatial instanceof Geometry) {

+				Mesh mesh = ((Geometry) spatial).getMesh();

+				FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position);

+				for(int i=0;i<floatBuffer.limit();i+=3) {

+					pts.add(new Vector3f(floatBuffer.get(i), floatBuffer.get(i + 1), floatBuffer.get(i + 2)));

+				}

+			}

+		}

+		

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			Object owner = this.owner.getObject();

+			for(Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				Vector3f[] translations = track.getTranslations();

+				Quaternion[] rotations = track.getRotations();

+				int maxFrames = translations.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					Vector3f currentTranslation = translations[frame];

+					

+					//looking for minimum distanced point

+					Vector3f minDistancePoint = null;

+					float distance = Float.MAX_VALUE;

+					for(Vector3f p : pts) {

+						float temp = currentTranslation.distance(p);

+						if(temp < distance) {

+							distance = temp;

+							minDistancePoint = p;

+						}

+					}

+					translations[frame] = minDistancePoint.clone();

+				}

+				

+				track.setKeyframes(track.getTimes(), translations, rotations, track.getScales());

+			}

+		}

+		

+		//TODO: static constraint for spatials

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLike.java
new file mode 100644
index 0000000..8b697aa
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLike.java
@@ -0,0 +1,98 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Size like' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSizeLike extends Constraint {

+	private static final int SIZELIKE_X = 0x01;

+	private static final int SIZELIKE_Y = 0x02;

+	private static final int SIZELIKE_Z = 0x04;

+	private static final int LOCLIKE_OFFSET = 0x80;

+    

+	protected int flag;

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintSizeLike(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+		if(blenderContext.getBlenderKey().isFixUpAxis()) {

+			//swapping Y and X limits flag in the bitwise flag

+			int y = flag & SIZELIKE_Y;

+			int z = flag & SIZELIKE_Z;

+			flag &= SIZELIKE_X | LOCLIKE_OFFSET;//clear the other flags to swap them

+			flag |= y << 1;

+			flag |= z >> 1;

+		}

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			Transform targetTransform = this.target.getTransform();

+			Vector3f targetScale = targetTransform.getScale();

+			for(Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				Vector3f[] scales = track.getScales();

+				int maxFrames = scales.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					this.sizeLike(scales[frame], targetScale, ipo.calculateValue(frame));

+				}

+				track.setKeyframes(track.getTimes(), track.getTranslations(), track.getRotations(), scales);

+			}

+		}

+		

+		if(owner instanceof Spatial) {

+			Transform targetTransform = this.target.getTransform();

+			Transform ownerTransform = this.owner.getTransform();

+			this.sizeLike(ownerTransform.getScale(), targetTransform.getScale(), ipo.calculateValue(0));

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+	

+	private void sizeLike(Vector3f ownerScale, Vector3f targetScale, float influence) {

+		Vector3f offset = Vector3f.ZERO;

+		if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original scale to the copied scale

+			offset = ownerScale.clone();

+		}

+

+		if ((flag & SIZELIKE_X) != 0) {

+			ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x;

+		}

+		if ((flag & SIZELIKE_Y) != 0) {

+			ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y;

+		}

+		if ((flag & SIZELIKE_Z) != 0) {

+			ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;

+		}

+		ownerScale.addLocal(offset);

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLimit.java
new file mode 100644
index 0000000..5d58299
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSizeLimit.java
@@ -0,0 +1,131 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Animation;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This class represents 'Size limit' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSizeLimit extends Constraint {

+	private static final int LIMIT_XMIN = 0x01;

+	private static final int LIMIT_XMAX = 0x02;

+	private static final int LIMIT_YMIN = 0x04;

+	private static final int LIMIT_YMAX = 0x08;

+	private static final int LIMIT_ZMIN = 0x10;

+	private static final int LIMIT_ZMAX = 0x20;

+	

+	protected float[][] limits = new float[3][2];

+    protected int flag;

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintSizeLimit(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		

+		flag = ((Number) data.getFieldValue("flag")).intValue();

+		if(blenderContext.getBlenderKey().isFixUpAxis()) {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[2][0] = -((Number) data.getFieldValue("ymin")).floatValue();

+			limits[2][1] = -((Number) data.getFieldValue("ymax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+			

+			//swapping Y and X limits flag in the bitwise flag

+			int ymin = flag & LIMIT_YMIN;

+			int ymax = flag & LIMIT_YMAX;

+			int zmin = flag & LIMIT_ZMIN;

+			int zmax = flag & LIMIT_ZMAX;

+			flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them

+			flag |= ymin << 2;

+			flag |= ymax << 2;

+			flag |= zmin >> 2;

+			flag |= zmax >> 2;

+		} else {

+			limits[0][0] = ((Number) data.getFieldValue("xmin")).floatValue();

+			limits[0][1] = ((Number) data.getFieldValue("xmax")).floatValue();

+			limits[1][0] = ((Number) data.getFieldValue("ymin")).floatValue();

+			limits[1][1] = ((Number) data.getFieldValue("ymax")).floatValue();

+			limits[2][0] = ((Number) data.getFieldValue("zmin")).floatValue();

+			limits[2][1] = ((Number) data.getFieldValue("zmax")).floatValue();

+		}

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		Object owner = this.owner.getObject();

+		AnimData animData = blenderContext.getAnimData(this.owner.getOma());

+		if(animData != null) {

+			for(Animation animation : animData.anims) {

+				BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);

+				Vector3f[] scales = track.getScales();

+				int maxFrames = scales.length;

+				for (int frame = 0; frame < maxFrames; ++frame) {

+					this.sizeLimit(scales[frame], ipo.calculateValue(frame));

+				}

+				track.setKeyframes(track.getTimes(), track.getTranslations(), track.getRotations(), scales);

+			}

+		}

+		

+		if(owner instanceof Spatial) {

+			Transform ownerTransform = this.owner.getTransform();

+			this.sizeLimit(ownerTransform.getScale(), ipo.calculateValue(0));

+			this.owner.applyTransform(ownerTransform);

+		}

+	}

+	

+	private void sizeLimit(Vector3f scale, float influence) {

+		if ((flag & LIMIT_XMIN) != 0) {

+			if (scale.x < limits[0][0]) {

+				scale.x -= (scale.x - limits[0][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_XMAX) != 0) {

+			if (scale.x > limits[0][1]) {

+				scale.x -= (scale.x - limits[0][1]) * influence;

+			}

+		}

+		if ((flag & LIMIT_YMIN) != 0) {

+			if (scale.y < limits[1][0]) {

+				scale.y -= (scale.y - limits[1][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_YMAX) != 0) {

+			if (scale.y > limits[1][1]) {

+				scale.y -= (scale.y - limits[1][1]) * influence;

+			}

+		}

+		if ((flag & LIMIT_ZMIN) != 0) {

+			if (scale.z < limits[2][0]) {

+				scale.z -= (scale.z - limits[2][0]) * influence;

+			}

+		}

+		if ((flag & LIMIT_ZMAX) != 0) {

+			if (scale.z > limits[2][1]) {

+				scale.z -= (scale.z - limits[2][1]) * influence;

+			}

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSplineInverseKinematic.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSplineInverseKinematic.java
new file mode 100644
index 0000000..0d9f9ab
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintSplineInverseKinematic.java
@@ -0,0 +1,44 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * The spline inverse kinematic constraint. Available for blender 2.50+.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintSplineInverseKinematic extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintSplineInverseKinematic.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure (bConstraint clss in blender 2.49).

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintSplineInverseKinematic(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo,

+			BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+		// TODO Auto-generated constructor stub

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO Auto-generated method stub

+		LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintStretchTo.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintStretchTo.java
new file mode 100644
index 0000000..a5a9f55
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintStretchTo.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Stretch to' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintStretchTo extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintStretchTo.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintStretchTo(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Stretch to' constraint

+		LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintTransform.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintTransform.java
new file mode 100644
index 0000000..5d111a3
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintTransform.java
@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This class represents 'Transform' constraint type in blender.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ConstraintTransform extends Constraint {

+	private static final Logger LOGGER = Logger.getLogger(ConstraintAction.class.getName());

+	

+	/**

+	 * This constructor creates the constraint instance.

+	 * 

+	 * @param constraintStructure

+	 *            the constraint's structure

+	 * @param ownerOMA

+	 *            the old memory address of the constraint owner

+	 * @param influenceIpo

+	 *            the ipo curve of the influence factor

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ConstraintTransform(Structure constraintStructure, Long ownerOMA,

+			Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {

+		super(constraintStructure, ownerOMA, influenceIpo, blenderContext);

+	}

+

+	@Override

+	protected void bakeConstraint() {

+		// TODO: implement 'Transform' constraint

+		LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!");

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Feature.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Feature.java
new file mode 100644
index 0000000..b08a20c
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Feature.java
@@ -0,0 +1,302 @@
+package com.jme3.scene.plugins.blender.constraints;

+

+import com.jme3.animation.Bone;

+import com.jme3.math.Matrix4f;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.constraints.Constraint.Space;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class represents either owner or target of the constraint. It has the

+ * common methods that take the evalueation space of the feature.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class Feature {

+	/** The evalueation space. */

+	protected Space				space;

+	/** Old memory address of the feature. */

+	protected Long				oma;

+	/** The spatial that is hold by the Feature. */

+	protected Spatial			spatial;

+	/** The bone that is hold by the Feature. */

+	protected Bone				bone;

+	/** The blender context. */

+	protected BlenderContext	blenderContext;

+

+	/**

+	 * Constructs the feature. This object should be loaded later

+	 * when it is read from the blender file.

+	 * The update method should be called before the feature is used.

+	 * 

+	 * @param space

+	 *            the spatial's evaluation space

+	 * @param oma

+	 *            the spatial's old memory address

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	public Feature(Space space, Long oma, BlenderContext blenderContext) {

+		this.space = space;

+		this.oma = oma;

+		this.blenderContext = blenderContext;

+	}

+	

+	/**

+	 * Constructs the feature based on spatial.

+	 * 

+	 * @param spatial

+	 *            the spatial

+	 * @param space

+	 *            the spatial's evaluation space

+	 * @param oma

+	 *            the spatial's old memory address

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	public Feature(Spatial spatial, Space space, Long oma, BlenderContext blenderContext) {

+		this(space, oma, blenderContext);

+		this.blenderContext = blenderContext;

+	}

+

+	/**

+	 * Constructs the feature based on bone.

+	 * 

+	 * @param bone

+	 *            the bone

+	 * @param space

+	 *            the bone evaluation space

+	 * @param oma

+	 *            the bone old memory address

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	public Feature(Bone bone, Space space, Long oma, BlenderContext blenderContext) {

+		this(space, oma, blenderContext);

+		this.bone = bone;

+	}

+	

+	/**

+	 * This method should be called before the feature is used.

+	 * It may happen that the object this feature refers to was not yet loaded from blend file

+	 * when the instance of this class was created.

+	 */

+	public void update() {

+		Object owner = blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);

+		if(owner instanceof Spatial) {

+			this.spatial = (Spatial) owner;

+		} else if(owner instanceof Bone) {

+			this.bone = (Bone) owner;

+		} else {

+			throw new IllegalStateException("Unknown type of owner: " + owner.getClass());

+		}

+	}

+	

+	/**

+	 * @return the feature's old memory address

+	 */

+	public Long getOma() {

+		return oma;

+	}

+

+	/**

+	 * @return the object held by the feature (either bone or spatial)

+	 */

+	public Object getObject() {

+		if (spatial != null) {

+			return spatial;

+		}

+		return bone;

+	}

+

+	/**

+	 * @return the feature's transform depending on the evaluation space

+	 */

+	@SuppressWarnings("unchecked")

+	public Transform getTransform() {

+		if (spatial != null) {

+			switch (space) {

+				case CONSTRAINT_SPACE_LOCAL:

+					Structure targetStructure = (Structure) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_STRUCTURE);

+

+					DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc"));

+					Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue());

+					DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot"));

+					Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() });

+					DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size"));

+					Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue());

+

+					if (blenderContext.getBlenderKey().isFixUpAxis()) {

+						float y = loc.y;

+						loc.y = loc.z;

+						loc.z = -y;

+

+						y = rot.getY();

+						float z = rot.getZ();

+						rot.set(rot.getX(), z, -y, rot.getW());

+

+						y = size.y;

+						size.y = size.z;

+						size.z = y;

+					}

+

+					Transform result = new Transform(loc, rot);

+					result.setScale(size);

+					return result;

+				case CONSTRAINT_SPACE_WORLD:

+					return spatial.getWorldTransform();

+				default:

+					throw new IllegalStateException("Invalid space type for target object: " + space.toString());

+			}

+		}

+		// Bone

+		switch (space) {

+			case CONSTRAINT_SPACE_LOCAL:

+				Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());

+				localTransform.setScale(bone.getLocalScale());

+				return localTransform;

+			case CONSTRAINT_SPACE_WORLD:

+				Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation());

+				worldTransform.setScale(bone.getWorldBindScale());

+				return worldTransform;

+			case CONSTRAINT_SPACE_POSE:

+				Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());

+				poseTransform.setScale(bone.getLocalScale());

+				return poseTransform;

+			case CONSTRAINT_SPACE_PARLOCAL:

+				Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());

+				parentLocalTransform.setScale(bone.getLocalScale());

+				return parentLocalTransform;

+			default:

+				throw new IllegalStateException("Invalid space type for target object: " + space.toString());

+		}

+	}

+

+	/**

+	 * This method applies the given transform to the feature in the proper

+	 * evaluation space.

+	 * 

+	 * @param transform

+	 *            the transform to be applied

+	 */

+	public void applyTransform(Transform transform) {

+		if (spatial != null) {

+			switch (space) {

+				case CONSTRAINT_SPACE_LOCAL:

+					Transform ownerLocalTransform = spatial.getLocalTransform();

+					ownerLocalTransform.getTranslation().addLocal(transform.getTranslation());

+					ownerLocalTransform.getRotation().multLocal(transform.getRotation());

+					ownerLocalTransform.getScale().multLocal(transform.getScale());

+					break;

+				case CONSTRAINT_SPACE_WORLD:

+					Matrix4f m = this.getParentWorldTransformMatrix();

+					m.invertLocal();

+					Matrix4f matrix = this.toMatrix(transform);

+					m.multLocal(matrix);

+

+					float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20);

+					float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21);

+					float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22);

+

+					transform.setTranslation(m.toTranslationVector());

+					transform.setRotation(m.toRotationQuat());

+					transform.setScale(scaleX, scaleY, scaleZ);

+					spatial.setLocalTransform(transform);

+					break;

+				case CONSTRAINT_SPACE_PARLOCAL:

+				case CONSTRAINT_SPACE_POSE:

+					throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object.");

+				default:

+					throw new IllegalStateException("Invalid space type for target object: " + space.toString());

+			}

+		} else {// Bone

+			switch (space) {

+				case CONSTRAINT_SPACE_LOCAL:

+					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());

+					break;

+				case CONSTRAINT_SPACE_WORLD:

+					Matrix4f m = this.getParentWorldTransformMatrix();

+//					m.invertLocal();

+					transform.setTranslation(m.mult(transform.getTranslation()));

+					transform.setRotation(m.mult(transform.getRotation(), null));

+					transform.setScale(transform.getScale());

+					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());

+//					float x = FastMath.HALF_PI/2;

+//					float y = -FastMath.HALF_PI;

+//					float z = -FastMath.HALF_PI/2;

+//					bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1));

+					break;

+				case CONSTRAINT_SPACE_PARLOCAL:

+					Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation());

+					Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation());

+					bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale());

+					break;

+				case CONSTRAINT_SPACE_POSE:

+					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());

+					break;

+				default:

+					throw new IllegalStateException("Invalid space type for target object: " + space.toString());

+			}

+		}

+	}

+

+	/**

+	 * @return world transform matrix of the feature

+	 */

+	public Matrix4f getWorldTransformMatrix() {

+		if (spatial != null) {

+			Matrix4f result = new Matrix4f();

+			Transform t = spatial.getWorldTransform();

+			result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());

+			return result;

+		}

+		// Bone

+		Matrix4f result = new Matrix4f();

+		result.setTransform(bone.getWorldBindPosition(), bone.getWorldBindScale(), bone.getWorldBindRotation().toRotationMatrix());

+		return result;

+	}

+

+	/**

+	 * @return world transform matrix of the feature's parent or identity matrix

+	 *         if the feature has no parent

+	 */

+	public Matrix4f getParentWorldTransformMatrix() {

+		Matrix4f result = new Matrix4f();

+		if (spatial != null) {

+			if (spatial.getParent() != null) {

+				Transform t = spatial.getParent().getWorldTransform();

+				result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());

+			}

+		} else {// Bone

+			Bone parent = bone.getParent();

+			if (parent != null) {

+				result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix());

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * Converts given transform to the matrix.

+	 * 

+	 * @param transform

+	 *            the transform to be converted

+	 * @return 4x4 matri that represents the given transform

+	 */

+	protected Matrix4f toMatrix(Transform transform) {

+		Matrix4f result = Matrix4f.IDENTITY;

+		if (transform != null) {

+			result = new Matrix4f();

+			result.setTranslation(transform.getTranslation());

+			result.setRotationQuaternion(transform.getRotation());

+			result.setScale(transform.getScale());

+		}

+		return result;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java
new file mode 100644
index 0000000..ebf6096
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java
@@ -0,0 +1,136 @@
+package com.jme3.scene.plugins.blender.curves;

+

+import com.jme3.math.Vector3f;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize

+ * floating point operations errors.

+ * @author Marcin Roguski

+ */

+public class BezierCurve {

+

+    public static final int X_VALUE = 0;

+    public static final int Y_VALUE = 1;

+    public static final int Z_VALUE = 2;

+    /** 

+     * The type of the curve. Describes the data it modifies. 

+     * Used in ipos calculations.

+     */

+    private int type;

+    /** The dimension of the curve. */

+    private int dimension;

+    /** A table of the bezier points. */

+    private float[][][] bezierPoints;

+

+    @SuppressWarnings("unchecked")

+    public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {

+        if (dimension != 2 && dimension != 3) {

+            throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!");

+        }

+        this.type = type;

+        this.dimension = dimension;

+        //first index of the bezierPoints table has the length of triples amount

+        //the second index points to a table od three points of a bezier triple (handle, point, handle)

+        //the third index specifies the coordinates of the specific point in a bezier triple

+        bezierPoints = new float[bezTriples.size()][3][dimension];

+        int i = 0, j, k;

+        for (Structure bezTriple : bezTriples) {

+            DynamicArray<Number> vec = (DynamicArray<Number>) bezTriple.getFieldValue("vec");

+            for (j = 0; j < 3; ++j) {

+                for (k = 0; k < dimension; ++k) {

+                    bezierPoints[i][j][k] = vec.get(j, k).floatValue();

+                }

+            }

+            ++i;

+        }

+    }

+

+    /**

+     * This method evaluates the data for the specified frame. The Y value is returned.

+     * @param frame

+     *        the frame for which the value is being calculated

+     * @param valuePart

+     *        this param specifies wheather we should return the X, Y or Z part of the result value; it should have

+     *        one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result

+     *        Z_VALUE - the Z factor of the result

+     * @return the value of the curve

+     */

+    public float evaluate(int frame, int valuePart) {

+        for (int i = 0; i < bezierPoints.length - 1; ++i) {

+            if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) {

+                float t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]);

+                float oneMinusT = 1.0f - t;

+                float oneMinusT2 = oneMinusT * oneMinusT;

+                float t2 = t * t;

+                return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;

+            }

+        }

+        if (frame < bezierPoints[0][1][0]) {

+            return bezierPoints[0][1][1];

+        } else { //frame>bezierPoints[bezierPoints.length-1][1][0]

+            return bezierPoints[bezierPoints.length - 1][1][1];

+        }

+    }

+

+    /**

+     * This method returns the frame where last bezier triple center point of the bezier curve is located.

+     * @return the frame number of the last defined bezier triple point for the curve

+     */

+    public int getLastFrame() {

+        return (int) bezierPoints[bezierPoints.length - 1][1][0];

+    }

+

+    /**

+     * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies

+     * (ie. LocationX or rotationW of the feature).

+     * @return the type of the bezier curve

+     */

+    public int getType() {

+        return type;

+    }

+

+    /**

+     * This method returns a list of control points for this curve.

+     * @return a list of control points for this curve.

+     */

+    public List<Vector3f> getControlPoints() {

+        List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3);

+        for (int i = 0; i < bezierPoints.length; ++i) {

+            controlPoints.add(new Vector3f(bezierPoints[i][0][0], bezierPoints[i][0][1], bezierPoints[i][0][2]));

+            controlPoints.add(new Vector3f(bezierPoints[i][1][0], bezierPoints[i][1][1], bezierPoints[i][1][2]));

+            controlPoints.add(new Vector3f(bezierPoints[i][2][0], bezierPoints[i][2][1], bezierPoints[i][2][2]));

+        }

+        return controlPoints;

+    }

+

+    @Override

+    public String toString() {

+        StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n');

+        for (int i = 0; i < bezierPoints.length; ++i) {

+            sb.append(this.toStringBezTriple(i)).append('\n');

+        }

+        return sb.toString();

+    }

+

+    /**

+     * This method converts the bezier triple of a specified index into text.

+     * @param tripleIndex

+     *        index of the triple

+     * @return text representation of the triple

+     */

+    private String toStringBezTriple(int tripleIndex) {

+        if (this.dimension == 2) {

+            return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") ("

+                    + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") ("

+                    + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";

+        } else {

+            return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") ("

+                    + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") ("

+                    + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java
new file mode 100644
index 0000000..af30f3b
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java
@@ -0,0 +1,589 @@
+package com.jme3.scene.plugins.blender.curves;

+

+import com.jme3.material.Material;

+import com.jme3.material.RenderState.FaceCullMode;

+import com.jme3.math.Spline.SplineType;

+import com.jme3.math.*;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.*;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+import com.jme3.scene.plugins.blender.meshes.MeshHelper;

+import com.jme3.scene.plugins.blender.objects.Properties;

+import com.jme3.scene.shape.Curve;

+import com.jme3.scene.shape.Surface;

+import com.jme3.util.BufferUtils;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.logging.Logger;

+

+/**

+ * A class that is used in mesh calculations.

+ * @author Marcin Roguski

+ */

+public class CurvesHelper extends AbstractBlenderHelper {

+

+    private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName());

+    /** Minimum basis U function degree for NURBS curves and surfaces. */

+    protected int minimumBasisUFunctionDegree = 4;

+    /** Minimum basis V function degree for NURBS curves and surfaces. */

+    protected int minimumBasisVFunctionDegree = 4;

+

+    /**

+     * This constructor parses the given blender version and stores the result. Some functionalities may differ in

+     * different blender versions.

+     * @param blenderVersion

+     *        the version read from the blend file

+     * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+     */

+    public CurvesHelper(String blenderVersion, boolean fixUpAxis) {

+        super(blenderVersion, fixUpAxis);

+    }

+

+    /**

+     * This method converts given curve structure into a list of geometries representing the curve. The list is used here because on object

+     * can have several separate curves.

+     * @param curveStructure

+     *            the curve structure

+     * @param blenderContext

+     *            the blender context

+     * @return a list of geometries repreenting a single curve object

+     * @throws BlenderFileException

+     */

+    public List<Geometry> toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {

+        String name = curveStructure.getName();

+        int flag = ((Number) curveStructure.getFieldValue("flag")).intValue();

+        boolean is3D = (flag & 0x01) != 0;

+        boolean isFront = (flag & 0x02) != 0 && !is3D;

+        boolean isBack = (flag & 0x04) != 0 && !is3D;

+        if (isFront) {

+            LOGGER.warning("No front face in curve implemented yet!");//TODO: implement front face

+        }

+        if (isBack) {

+            LOGGER.warning("No back face in curve implemented yet!");//TODO: implement back face

+        }

+

+        //reading nurbs (and sorting them by material)

+        List<Structure> nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(blenderContext);

+        Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();

+        for (Structure nurb : nurbStructures) {

+            Number matNumber = (Number) nurb.getFieldValue("mat_nr");

+            List<Structure> nurbList = nurbs.get(matNumber);

+            if (nurbList == null) {

+                nurbList = new ArrayList<Structure>();

+                nurbs.put(matNumber, nurbList);

+            }

+            nurbList.add(nurb);

+        }

+

+        //getting materials

+        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+        Material[] materials = materialHelper.getMaterials(curveStructure, blenderContext);

+        if (materials == null) {

+            materials = new Material[]{blenderContext.getDefaultMaterial().clone()};

+        }

+        for (Material material : materials) {

+            material.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);

+        }

+

+        //getting or creating bevel object

+        List<Geometry> bevelObject = null;

+        Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj");

+        if (pBevelObject.isNotNull()) {

+            Pointer pBevelStructure = (Pointer) pBevelObject.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("data");

+            Structure bevelStructure = pBevelStructure.fetchData(blenderContext.getInputStream()).get(0);

+            bevelObject = this.toCurve(bevelStructure, blenderContext);

+        } else {

+            int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue();

+            float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue();

+            float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue();

+            if (bevelDepth > 0.0f) {

+                float handlerLength = bevelDepth / 2.0f;

+

+                List<Vector3f> conrtolPoints = new ArrayList<Vector3f>(extrude > 0.0f ? 19 : 13);

+                conrtolPoints.add(new Vector3f(-bevelDepth, extrude, 0));

+                conrtolPoints.add(new Vector3f(-bevelDepth, handlerLength + extrude, 0));

+

+                conrtolPoints.add(new Vector3f(-handlerLength, bevelDepth + extrude, 0));

+                conrtolPoints.add(new Vector3f(0, bevelDepth + extrude, 0));

+                conrtolPoints.add(new Vector3f(handlerLength, bevelDepth + extrude, 0));

+

+                conrtolPoints.add(new Vector3f(bevelDepth, extrude + handlerLength, 0));

+                conrtolPoints.add(new Vector3f(bevelDepth, extrude, 0));

+                conrtolPoints.add(new Vector3f(bevelDepth, extrude - handlerLength, 0));

+

+                if (extrude > 0.0f) {

+                    conrtolPoints.add(new Vector3f(bevelDepth, -extrude + handlerLength, 0));

+                    conrtolPoints.add(new Vector3f(bevelDepth, -extrude, 0));

+                    conrtolPoints.add(new Vector3f(bevelDepth, -extrude - handlerLength, 0));

+                }

+

+                conrtolPoints.add(new Vector3f(handlerLength, -bevelDepth - extrude, 0));

+                conrtolPoints.add(new Vector3f(0, -bevelDepth - extrude, 0));

+                conrtolPoints.add(new Vector3f(-handlerLength, -bevelDepth - extrude, 0));

+

+                conrtolPoints.add(new Vector3f(-bevelDepth, -handlerLength - extrude, 0));

+                conrtolPoints.add(new Vector3f(-bevelDepth, -extrude, 0));

+

+                if (extrude > 0.0f) {

+                    conrtolPoints.add(new Vector3f(-bevelDepth, handlerLength - extrude, 0));

+

+                    conrtolPoints.add(new Vector3f(-bevelDepth, -handlerLength + extrude, 0));

+                    conrtolPoints.add(new Vector3f(-bevelDepth, extrude, 0));

+                }

+

+                Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false);

+                Curve bevelCurve = new Curve(bevelSpline, bevResol);

+                bevelObject = new ArrayList<Geometry>(1);

+                bevelObject.add(new Geometry("", bevelCurve));

+            } else if (extrude > 0.0f) {

+                Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[]{

+                            new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0)

+                        }, 1, false);

+                Curve bevelCurve = new Curve(bevelSpline, bevResol);

+                bevelObject = new ArrayList<Geometry>(1);

+                bevelObject.add(new Geometry("", bevelCurve));

+            }

+        }

+

+        //getting taper object

+        Curve taperObject = null;

+        Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj");

+        if (bevelObject != null && pTaperObject.isNotNull()) {

+            Pointer pTaperStructure = (Pointer) pTaperObject.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("data");

+            Structure taperStructure = pTaperStructure.fetchData(blenderContext.getInputStream()).get(0);

+            taperObject = this.loadTaperObject(taperStructure, blenderContext);

+        }

+

+        Vector3f loc = this.getLoc(curveStructure);

+        //creating the result curves

+        List<Geometry> result = new ArrayList<Geometry>(nurbs.size());

+        for (Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) {

+            for (Structure nurb : nurbEntry.getValue()) {

+                int type = ((Number) nurb.getFieldValue("type")).intValue();

+                List<Geometry> nurbGeoms = null;

+                if ((type & 0x01) != 0) {//Bezier curve

+                    nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, blenderContext);

+                } else if ((type & 0x04) != 0) {//NURBS

+                    nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, blenderContext);

+                }

+                if (nurbGeoms != null) {//setting the name and assigning materials

+                    for (Geometry nurbGeom : nurbGeoms) {

+                        nurbGeom.setMaterial(materials[nurbEntry.getKey().intValue()]);

+                        nurbGeom.setName(name);

+                        result.add(nurbGeom);

+                    }

+                }

+            }

+        }

+        

+        //reading custom properties

+		Properties properties = this.loadProperties(curveStructure, blenderContext);

+		if(properties != null && properties.getValue() != null) {

+			for(Geometry geom : result) {

+				geom.setUserData("properties", properties);

+			}

+		}

+        

+        return result;

+    }

+

+    /**

+     * This method loads the bezier curve.

+     * @param loc

+     *            the translation of the curve

+     * @param nurb

+     *            the nurb structure

+     * @param bevelObject

+     *            the bevel object

+     * @param taperObject

+     *            the taper object

+     * @param blenderContext

+     *            the blender context

+     * @return a list of geometries representing the curves

+     * @throws BlenderFileException

+     *             an exception is thrown when there are problems with the blender file

+     */

+    protected List<Geometry> loadBezierCurve(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject,

+            BlenderContext blenderContext) throws BlenderFileException {

+        Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");

+        List<Geometry> result = new ArrayList<Geometry>();

+        if (pBezierTriple.isNotNull()) {

+            boolean smooth = (((Number) nurb.getFlatFieldValue("flag")).intValue() & 0x01) != 0;

+            int resolution = ((Number) nurb.getFieldValue("resolu")).intValue();

+            boolean cyclic = (((Number) nurb.getFieldValue("flagu")).intValue() & 0x01) != 0;

+

+            //creating the curve object

+            BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(blenderContext.getInputStream()), 3);

+            List<Vector3f> controlPoints = bezierCurve.getControlPoints();

+            if (cyclic) {

+                //copy the first three points at the end

+                for (int i = 0; i < 3; ++i) {

+                    controlPoints.add(controlPoints.get(i));

+                }

+            }

+            //removing the first and last handles

+            controlPoints.remove(0);

+            controlPoints.remove(controlPoints.size() - 1);

+

+            //creating curve

+            Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false);

+            Curve curve = new Curve(spline, resolution);

+            if (bevelObject == null) {//creating a normal curve

+                Geometry curveGeometry = new Geometry(null, curve);

+                result.add(curveGeometry);

+                //TODO: use front and back flags; surface excluding algorithm for bezier circles should be added

+            } else {//creating curve with bevel and taper shape

+                result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext);

+            }

+        }

+        return result;

+    }

+

+    /**

+     * This method loads the NURBS curve or surface.

+     * @param loc

+     *            object's location

+     * @param nurb

+     *            the NURBS data structure

+     * @param bevelObject

+     *            the bevel object to be applied

+     * @param taperObject

+     *            the taper object to be applied

+     * @param blenderContext

+     *            the blender context

+     * @return a list of geometries that represents the loaded NURBS curve or surface

+     * @throws BlenderFileException

+     *             an exception is throw when problems with blender loaded data occurs

+     */

+    @SuppressWarnings("unchecked")

+    protected List<Geometry> loadNurb(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject,

+            BlenderContext blenderContext) throws BlenderFileException {

+        //loading the knots

+        List<Float>[] knots = new List[2];

+        Pointer[] pKnots = new Pointer[]{(Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv")};

+        for (int i = 0; i < knots.length; ++i) {

+            if (pKnots[i].isNotNull()) {

+                FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress());

+                BlenderInputStream blenderInputStream = blenderContext.getInputStream();

+                blenderInputStream.setPosition(fileBlockHeader.getBlockPosition());

+                int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4;

+                knots[i] = new ArrayList<Float>(knotsAmount);

+                for (int j = 0; j < knotsAmount; ++j) {

+                    knots[i].add(Float.valueOf(blenderInputStream.readFloat()));

+                }

+            }

+        }

+

+        //loading the flags and orders (basis functions degrees)

+        int flagU = ((Number) nurb.getFieldValue("flagu")).intValue();

+        int flagV = ((Number) nurb.getFieldValue("flagv")).intValue();

+        int orderU = ((Number) nurb.getFieldValue("orderu")).intValue();

+        int orderV = ((Number) nurb.getFieldValue("orderv")).intValue();

+

+        //loading control points and their weights

+        int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue();

+        int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue();

+        List<Structure> bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(blenderContext.getInputStream());

+        List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);

+        for (int i = 0; i < pntsV; ++i) {

+            List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);

+            for (int j = 0; j < pntsU; ++j) {

+                DynamicArray<Float> vec = (DynamicArray<Float>) bPoints.get(j + i * pntsU).getFieldValue("vec");

+                if (fixUpAxis) {

+                    uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue()));

+                } else {

+                    uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue()));

+                }

+            }

+            if ((flagU & 0x01) != 0) {

+                for (int k = 0; k < orderU - 1; ++k) {

+                    uControlPoints.add(uControlPoints.get(k));

+                }

+            }

+            controlPoints.add(uControlPoints);

+        }

+        if ((flagV & 0x01) != 0) {

+            for (int k = 0; k < orderV - 1; ++k) {

+                controlPoints.add(controlPoints.get(k));

+            }

+        }

+

+        int resolu = ((Number) nurb.getFieldValue("resolu")).intValue() + 1;

+        List<Geometry> result;

+        if (knots[1] == null) {//creating the curve

+            Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]);

+            Curve nurbCurve = new Curve(nurbSpline, resolu);

+            if (bevelObject != null) {

+                result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, blenderContext);//TODO: smooth

+            } else {

+                result = new ArrayList<Geometry>(1);

+                Geometry nurbGeometry = new Geometry("", nurbCurve);

+                result.add(nurbGeometry);

+            }

+        } else {//creating the nurb surface

+            int resolv = ((Number) nurb.getFieldValue("resolv")).intValue() + 1;

+            Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV);

+            Geometry nurbGeometry = new Geometry("", nurbSurface);

+            result = new ArrayList<Geometry>(1);

+            result.add(nurbGeometry);

+        }

+        return result;

+    }

+

+    /**

+     * This method returns the taper scale that should be applied to the object.

+     * @param taperPoints

+     *            the taper points

+     * @param taperLength

+     *            the taper curve length

+     * @param percent

+     *            the percent of way along the whole taper curve

+     * @param store

+     *            the vector where the result will be stored

+     */

+    protected float getTaperScale(float[] taperPoints, float taperLength, float percent) {

+        float length = taperLength * percent;

+        float currentLength = 0;

+        Vector3f p = new Vector3f();

+        int i;

+        for (i = 0; i < taperPoints.length - 6 && currentLength < length; i += 3) {

+            p.set(taperPoints[i], taperPoints[i + 1], taperPoints[i + 2]);

+            p.subtractLocal(taperPoints[i + 3], taperPoints[i + 4], taperPoints[i + 5]);

+            currentLength += p.length();

+        }

+        currentLength -= p.length();

+        float leftLength = length - currentLength;

+        float percentOnSegment = p.length() == 0 ? 0 : leftLength / p.length();

+        Vector3f store = FastMath.interpolateLinear(percentOnSegment,

+                new Vector3f(taperPoints[i], taperPoints[i + 1], taperPoints[i + 2]),

+                new Vector3f(taperPoints[i + 3], taperPoints[i + 4], taperPoints[i + 5]));

+        return store.y;

+    }

+

+    /**

+     * This method applies bevel and taper objects to the curve.

+     * @param curve

+     *            the curve we apply the objects to

+     * @param bevelObject

+     *            the bevel object

+     * @param taperObject

+     *            the taper object

+     * @param smooth

+     * 			  the smooth flag

+     * @param blenderContext

+     *            the blender context

+     * @return a list of geometries representing the beveled and/or tapered curve

+     */

+    protected List<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> bevelObject, Curve taperObject,

+            boolean smooth, BlenderContext blenderContext) {

+        float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position));

+        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);

+        float curveLength = curve.getLength();

+        //TODO: use the smooth var

+

+        //taper data

+        float[] taperPoints = null;

+        float taperLength = 0;

+        if (taperObject != null) {

+            taperPoints = BufferUtils.getFloatArray(taperObject.getFloatBuffer(Type.Position));

+            taperLength = taperObject.getLength();

+        }

+

+        //several objects can be allocated only once

+        Vector3f p = new Vector3f();

+        Vector3f z = new Vector3f(0, 0, 1);

+        Vector3f negativeY = new Vector3f(0, -1, 0);

+        Matrix4f m = new Matrix4f();

+        float lengthAlongCurve = 0, taperScale = 1.0f;

+        Quaternion planeRotation = new Quaternion();

+        Quaternion zRotation = new Quaternion();

+        float[] temp = new float[]{0, 0, 0, 1};

+        Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>();//normalMap merges normals of faces that will be rendered smooth

+

+        FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()];

+        FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()];

+        IntBuffer[] indexBuffers = new IntBuffer[bevelObject.size()];

+        for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) {

+            Mesh mesh = bevelObject.get(geomIndex).getMesh();

+            FloatBuffer positions = mesh.getFloatBuffer(Type.Position);

+            float[] vertices = BufferUtils.getFloatArray(positions);

+

+            for (int i = 0; i < curvePoints.length; i += 3) {

+                p.set(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]);

+                Vector3f v;

+                if (i == 0) {

+                    v = new Vector3f(curvePoints[3] - p.x, curvePoints[4] - p.y, curvePoints[5] - p.z);

+                } else if (i + 3 >= curvePoints.length) {

+                    v = new Vector3f(p.x - curvePoints[i - 3], p.y - curvePoints[i - 2], p.z - curvePoints[i - 1]);

+                    lengthAlongCurve += v.length();

+                } else {

+                    v = new Vector3f(curvePoints[i + 3] - curvePoints[i - 3],

+                            curvePoints[i + 4] - curvePoints[i - 2],

+                            curvePoints[i + 5] - curvePoints[i - 1]);

+                    lengthAlongCurve += new Vector3f(curvePoints[i + 3] - p.x, curvePoints[i + 4] - p.y, curvePoints[i + 5] - p.z).length();

+                }

+                v.normalizeLocal();

+

+                float angle = FastMath.acos(v.dot(z));

+                v.crossLocal(z).normalizeLocal();//v is the rotation axis now

+                planeRotation.fromAngleAxis(angle, v);

+

+                Vector3f zAxisRotationVector = negativeY.cross(v).normalizeLocal();

+                float zAxisRotationAngle = FastMath.acos(negativeY.dot(v));

+                zRotation.fromAngleAxis(zAxisRotationAngle, zAxisRotationVector);

+

+                //point transformation matrix

+                if (taperPoints != null) {

+                    taperScale = this.getTaperScale(taperPoints, taperLength, lengthAlongCurve / curveLength);

+                }

+                m.set(Matrix4f.IDENTITY);

+                m.setRotationQuaternion(planeRotation.multLocal(zRotation));

+                m.setTranslation(p);

+

+                //these vertices need to be thrown on XY plane

+                //and moved to the origin of [p1.x, p1.y] on the plane

+                Vector3f[] verts = new Vector3f[vertices.length / 3];

+                for (int j = 0; j < verts.length; ++j) {

+                    temp[0] = vertices[j * 3] * taperScale;

+                    temp[1] = vertices[j * 3 + 1] * taperScale;

+                    temp[2] = 0;

+                    m.mult(temp);//the result is stored in the array

+                    if (fixUpAxis) {//TODO: not the other way ???

+                        verts[j] = new Vector3f(temp[0], temp[1], temp[2]);

+                    } else {

+                        verts[j] = new Vector3f(temp[0], temp[2], -temp[1]);

+                    }

+                }

+                if (vertexBuffers[geomIndex] == null) {

+                    vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(verts.length * curvePoints.length);

+                }

+                FloatBuffer buffer = BufferUtils.createFloatBuffer(verts);

+                vertexBuffers[geomIndex].put(buffer);

+

+                //adding indexes

+                IntBuffer indexBuffer = indexBuffers[geomIndex];

+                if (indexBuffer == null) {

+                    //the amount of faces in the final mesh is the amount of edges in the bevel curve

+                    //(which is less by 1 than its number of vertices)

+                    //multiplied by 2 (because each edge has two faces assigned on both sides)

+                    //and multiplied by the amount of bevel curve repeats which is equal to the amount of vertices on the target curve

+                    //finally we need to subtract the bevel edges amount 2 times because the border edges have only one face attached

+                    //and at last multiply everything by 3 because each face needs 3 indexes to be described

+                    int bevelCurveEdgesAmount = verts.length - 1;

+                    indexBuffer = BufferUtils.createIntBuffer(((bevelCurveEdgesAmount << 1) * curvePoints.length - bevelCurveEdgesAmount << 1) * 3);

+                    indexBuffers[geomIndex] = indexBuffer;

+                }

+                int pointOffset = i / 3 * verts.length;

+                if (i + 3 < curvePoints.length) {

+                    for (int index = 0; index < verts.length - 1; ++index) {

+                        indexBuffer.put(index + pointOffset);

+                        indexBuffer.put(index + pointOffset + 1);

+                        indexBuffer.put(verts.length + index + pointOffset);

+                        indexBuffer.put(verts.length + index + pointOffset);

+                        indexBuffer.put(index + pointOffset + 1);

+                        indexBuffer.put(verts.length + index + pointOffset + 1);

+                    }

+                }

+            }

+        }

+

+        //calculating the normals

+        for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) {

+            Vector3f[] allVerts = BufferUtils.getVector3Array(vertexBuffers[geomIndex]);

+            int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]);

+            for (int i = 0; i < allIndices.length - 3; i += 3) {

+                Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);

+                meshHelper.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);

+            }

+            if (normalBuffers[geomIndex] == null) {

+                normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3);

+            }

+            for (Vector3f v : allVerts) {

+                Vector3f n = normalMap.get(v);

+                normalBuffers[geomIndex].put(n.x);

+                normalBuffers[geomIndex].put(n.y);

+                normalBuffers[geomIndex].put(n.z);

+            }

+        }

+

+        List<Geometry> result = new ArrayList<Geometry>(vertexBuffers.length);

+        Float oneReferenceToCurveLength = new Float(curveLength);//its important for array modifier to use one reference here

+        for (int i = 0; i < vertexBuffers.length; ++i) {

+            Mesh mesh = new Mesh();

+            mesh.setBuffer(Type.Position, 3, vertexBuffers[i]);

+            mesh.setBuffer(Type.Index, 3, indexBuffers[i]);

+            mesh.setBuffer(Type.Normal, 3, normalBuffers[i]);

+            Geometry g = new Geometry("g" + i, mesh);

+            g.setUserData("curveLength", oneReferenceToCurveLength);

+            g.updateModelBound();

+            result.add(g);

+        }

+

+        return result;

+    }

+

+    /**

+     * This method loads the taper object.

+     * @param taperStructure

+     *            the taper structure

+     * @param blenderContext

+     *            the blender context

+     * @return the taper object

+     * @throws BlenderFileException

+     */

+    protected Curve loadTaperObject(Structure taperStructure, BlenderContext blenderContext) throws BlenderFileException {

+        //reading nurbs

+        List<Structure> nurbStructures = ((Structure) taperStructure.getFieldValue("nurb")).evaluateListBase(blenderContext);

+        for (Structure nurb : nurbStructures) {

+            Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");

+            if (pBezierTriple.isNotNull()) {

+                //creating the curve object

+                BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(blenderContext.getInputStream()), 3);

+                List<Vector3f> controlPoints = bezierCurve.getControlPoints();

+                //removing the first and last handles

+                controlPoints.remove(0);

+                controlPoints.remove(controlPoints.size() - 1);

+

+                //return the first taper curve that has more than 3 control points

+                if (controlPoints.size() > 3) {

+                    Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false);

+                    int resolution = ((Number) taperStructure.getFieldValue("resolu")).intValue();

+                    return new Curve(spline, resolution);

+                }

+            }

+        }

+        return null;

+    }

+

+    /**

+     * This method returns the translation of the curve. The UP axis is taken into account here.

+     * @param curveStructure

+     *            the curve structure

+     * @return curve translation

+     */

+    @SuppressWarnings("unchecked")

+    protected Vector3f getLoc(Structure curveStructure) {

+        DynamicArray<Number> locArray = (DynamicArray<Number>) curveStructure.getFieldValue("loc");

+        if (fixUpAxis) {

+            return new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), -locArray.get(2).floatValue());

+        } else {

+            return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue());

+        }

+    }

+    

+    @Override

+    public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+    	return true;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java b/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java
new file mode 100644
index 0000000..e0b40b3
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java
@@ -0,0 +1,76 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.exceptions;

+

+/**

+ * This exception is thrown when blend file data is somehow invalid.

+ * @author Marcin Roguski

+ */

+public class BlenderFileException extends Exception {

+

+    private static final long serialVersionUID = 7573482836437866767L;

+

+    /**

+     * Constructor. Creates an exception with no description.

+     */

+    public BlenderFileException() {

+    }

+

+    /**

+     * Constructor. Creates an exception containing the given message.

+     * @param message

+     *        the message describing the problem that occured

+     */

+    public BlenderFileException(String message) {

+        super(message);

+    }

+

+    /**

+     * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then.

+     * @param throwable

+     *        an exception/error that occured

+     */

+    public BlenderFileException(Throwable throwable) {

+        super(throwable);

+    }

+

+    /**

+     * Constructor. Creates an exception with both a message and stacktrace.

+     * @param message

+     *        the message describing the problem that occured

+     * @param throwable

+     *        an exception/error that occured

+     */

+    public BlenderFileException(String message, Throwable throwable) {

+        super(message, throwable);

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
new file mode 100644
index 0000000..0f58474
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
@@ -0,0 +1,383 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import java.io.BufferedInputStream;

+import java.io.ByteArrayInputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.logging.Logger;

+import java.util.zip.GZIPInputStream;

+

+/**

+ * An input stream with random access to data.

+ * @author Marcin Roguski

+ */

+public class BlenderInputStream extends InputStream {

+

+    private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName());

+    /** The default size of the blender buffer. */

+    private static final int DEFAULT_BUFFER_SIZE = 1048576;												//1MB

+    /** The application's asset manager. */

+    private AssetManager assetManager;

+    /**

+     * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.

+     */

+    private int pointerSize;

+    /**

+     * Type of byte ordering used; 'v' means little endian and 'V' means big endian.

+     */

+    private char endianess;

+    /** Version of Blender the file was created in; '248' means version 2.48. */

+    private String versionNumber;

+    /** The buffer we store the read data to. */

+    protected byte[] cachedBuffer;

+    /** The total size of the stored data. */

+    protected int size;

+    /** The current position of the read cursor. */

+    protected int position;

+	/** The input stream we read the data from. */

+	protected InputStream		inputStream;

+

+    /**

+     * Constructor. The input stream is stored and used to read data.

+     * @param inputStream

+     *        the stream we read data from

+     * @param assetManager

+     *        the application's asset manager

+     * @throws BlenderFileException

+     *         this exception is thrown if the file header has some invalid data

+     */

+    public BlenderInputStream(InputStream inputStream, AssetManager assetManager) throws BlenderFileException {

+        this.assetManager = assetManager;

+        this.inputStream = inputStream;

+        //the size value will canche while reading the file; the available() method cannot be counted on

+        try {

+            size = inputStream.available();

+        } catch (IOException e) {

+            size = 0;

+        }

+        if (size <= 0) {

+            size = BlenderInputStream.DEFAULT_BUFFER_SIZE;

+        }

+

+        //buffered input stream is used here for much faster file reading

+        BufferedInputStream bufferedInputStream;

+        if (inputStream instanceof BufferedInputStream) {

+            bufferedInputStream = (BufferedInputStream) inputStream;

+        } else {

+            bufferedInputStream = new BufferedInputStream(inputStream);

+        }

+

+        try {

+            this.readStreamToCache(bufferedInputStream);

+        } catch (IOException e) {

+            throw new BlenderFileException("Problems occured while caching the file!", e);

+        }

+

+        try {

+            this.readFileHeader();

+        } catch (BlenderFileException e) {//the file might be packed, don't panic, try one more time ;)

+            this.decompressFile();

+            this.position = 0;

+            this.readFileHeader();

+        }

+    }

+

+    /**

+     * This method reads the whole stream into a buffer.

+     * @param inputStream

+     *        the stream to read the file data from

+     * @throws IOException 

+     * 		   an exception is thrown when data read from the stream is invalid or there are problems with i/o

+     *         operations

+     */

+    private void readStreamToCache(InputStream inputStream) throws IOException {

+        int data = inputStream.read();

+        cachedBuffer = new byte[size];

+        size = 0;//this will count the actual size

+        while (data != -1) {

+            cachedBuffer[size++] = (byte) data;

+            if (size >= cachedBuffer.length) {//widen the cached array

+                byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];

+                System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);

+                cachedBuffer = newBuffer;

+            }

+            data = inputStream.read();

+        }

+    }

+

+    /**

+     * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the

+     * cachedBuffer field.

+     */

+    private void decompressFile() {

+        GZIPInputStream gis = null;

+        try {

+            gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));

+            this.readStreamToCache(gis);

+        } catch (IOException e) {

+            throw new IllegalStateException("IO errors occured where they should NOT! "

+                    + "The data is already buffered at this point!", e);

+        } finally {

+            try {

+                if (gis != null) {

+                    gis.close();

+                }

+            } catch (IOException e) {

+                LOGGER.warning(e.getMessage());

+            }

+        }

+    }

+

+    /**

+     * This method loads the header from the given stream during instance creation.

+     * @param inputStream

+     *        the stream we read the header from

+     * @throws BlenderFileException

+     *         this exception is thrown if the file header has some invalid data

+     */

+    private void readFileHeader() throws BlenderFileException {

+        byte[] identifier = new byte[7];

+        int bytesRead = this.readBytes(identifier);

+        if (bytesRead != 7) {

+            throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!");

+        }

+        String strIdentifier = new String(identifier);

+        if (!"BLENDER".equals(strIdentifier)) {

+            throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!");

+        }

+        char pointerSizeSign = (char) this.readByte();

+        if (pointerSizeSign == '-') {

+            pointerSize = 8;

+        } else if (pointerSizeSign == '_') {

+            pointerSize = 4;

+        } else {

+            throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign);

+        }

+        endianess = (char) this.readByte();

+        if (endianess != 'v' && endianess != 'V') {

+            throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess);

+        }

+        byte[] versionNumber = new byte[3];

+        bytesRead = this.readBytes(versionNumber);

+        if (bytesRead != 3) {

+            throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!");

+        }

+        this.versionNumber = new String(versionNumber);

+    }

+

+    @Override

+    public int read() throws IOException {

+        return this.readByte();

+    }

+

+    /**

+     * This method reads 1 byte from the stream.

+     * It works just in the way the read method does.

+     * It just not throw an exception because at this moment the whole file

+     * is loaded into buffer, so no need for IOException to be thrown.

+     * @return a byte from the stream (1 bytes read)

+     */

+    public int readByte() {

+        return cachedBuffer[position++] & 0xFF;

+    }

+

+    /**

+     * This method reads a bytes number big enough to fill the table. 

+     * It does not throw exceptions so it is for internal use only.

+     * @param bytes

+     *            an array to be filled with data

+     * @return number of read bytes (a length of array actually)

+     */

+    private int readBytes(byte[] bytes) {

+        for (int i = 0; i < bytes.length; ++i) {

+            bytes[i] = (byte) this.readByte();

+        }

+        return bytes.length;

+    }

+

+    /**

+     * This method reads 2-byte number from the stream.

+     * @return a number from the stream (2 bytes read)

+     */

+    public int readShort() {

+        int part1 = this.readByte();

+        int part2 = this.readByte();

+        if (endianess == 'v') {

+            return (part2 << 8) + part1;

+        } else {

+            return (part1 << 8) + part2;

+        }

+    }

+

+    /**

+     * This method reads 4-byte number from the stream.

+     * @return a number from the stream (4 bytes read)

+     */

+    public int readInt() {

+        int part1 = this.readByte();

+        int part2 = this.readByte();

+        int part3 = this.readByte();

+        int part4 = this.readByte();

+        if (endianess == 'v') {

+            return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1;

+        } else {

+            return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4;

+        }

+    }

+

+    /**

+     * This method reads 4-byte floating point number (float) from the stream.

+     * @return a number from the stream (4 bytes read)

+     */

+    public float readFloat() {

+        int intValue = this.readInt();

+        return Float.intBitsToFloat(intValue);

+    }

+

+    /**

+     * This method reads 8-byte number from the stream.

+     * @return a number from the stream (8 bytes read)

+     */

+    public long readLong() {

+        long part1 = this.readInt();

+        long part2 = this.readInt();

+        long result = -1;

+        if (endianess == 'v') {

+            result = part2 << 32 | part1;

+        } else {

+            result = part1 << 32 | part2;

+        }

+        return result;

+    }

+

+    /**

+     * This method reads 8-byte floating point number (double) from the stream.

+     * @return a number from the stream (8 bytes read)

+     */

+    public double readDouble() {

+        long longValue = this.readLong();

+        return Double.longBitsToDouble(longValue);

+    }

+

+    /**

+     * This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either

+     * 4 or 8 bytes of data.

+     * @return the pointer value

+     */

+    public long readPointer() {

+        if (pointerSize == 4) {

+            return this.readInt();

+        }

+        return this.readLong();

+    }

+

+    /**

+     * This method reads the string. It assumes the string is terminated with zero in the stream.

+     * @return the string read from the stream

+     */

+    public String readString() {

+        StringBuilder stringBuilder = new StringBuilder();

+        int data = this.readByte();

+        while (data != 0) {

+            stringBuilder.append((char) data);

+            data = this.readByte();

+        }

+        return stringBuilder.toString();

+    }

+

+    /**

+     * This method sets the current position of the read cursor.

+     * @param position

+     *        the position of the read cursor

+     */

+    public void setPosition(int position) {

+        this.position = position;

+    }

+

+    /**

+     * This method returns the position of the read cursor.

+     * @return the position of the read cursor

+     */

+    public int getPosition() {

+        return position;

+    }

+

+    /**

+     * This method returns the blender version number where the file was created.

+     * @return blender version number

+     */

+    public String getVersionNumber() {

+        return versionNumber;

+    }

+

+    /**

+     * This method returns the size of the pointer.

+     * @return the size of the pointer

+     */

+    public int getPointerSize() {

+        return pointerSize;

+    }

+

+    /**

+     * This method returns the application's asset manager.

+     * @return the application's asset manager

+     */

+    public AssetManager getAssetManager() {

+        return assetManager;

+    }

+

+    /**

+     * This method aligns cursor position forward to a given amount of bytes.

+     * @param bytesAmount

+     *        the byte amount to which we aligh the cursor

+     */

+    public void alignPosition(int bytesAmount) {

+        if (bytesAmount <= 0) {

+            throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!");

+        }

+        long move = position % bytesAmount;

+        if (move > 0) {

+            position += bytesAmount - move;

+        }

+    }

+

+    @Override

+    public void close() throws IOException {

+		inputStream.close();

+//		cachedBuffer = null;

+//		size = position = 0;

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java
new file mode 100644
index 0000000..30922be
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java
@@ -0,0 +1,208 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import java.util.HashMap;

+import java.util.Map;

+

+/**

+ * The data block containing the description of the file.

+ * @author Marcin Roguski

+ */

+public class DnaBlockData {

+

+    private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A';	//SDNA

+    private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E';	//NAME

+    private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E';	//TYPE

+    private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N';	//TLEN

+    private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C';	//STRC

+    /** Structures available inside the file. */

+    private final Structure[] structures;

+    /** A map that helps finding a structure by type. */

+    private final Map<String, Structure> structuresMap;

+

+    /**

+     * Constructor. Loads the block from the given stream during instance creation.

+     * @param inputStream

+     *        the stream we read the block from

+     * @param blenderContext

+     *        the blender context

+     * @throws BlenderFileException

+     *         this exception is throw if the blend file is invalid or somehow corrupted

+     */

+    public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {

+        int identifier;

+

+        //reading 'SDNA' identifier

+        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+

+        if (identifier != SDNA_ID) {

+            throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));

+        }

+

+        //reading names

+        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+        if (identifier != NAME_ID) {

+            throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));

+        }

+        int amount = inputStream.readInt();

+        if (amount <= 0) {

+            throw new BlenderFileException("The names amount number should be positive!");

+        }

+        String[] names = new String[amount];

+        for (int i = 0; i < amount; ++i) {

+            names[i] = inputStream.readString();

+        }

+

+        //reding types

+        inputStream.alignPosition(4);

+        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+        if (identifier != TYPE_ID) {

+            throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));

+        }

+        amount = inputStream.readInt();

+        if (amount <= 0) {

+            throw new BlenderFileException("The types amount number should be positive!");

+        }

+        String[] types = new String[amount];

+        for (int i = 0; i < amount; ++i) {

+            types[i] = inputStream.readString();

+        }

+

+        //reading lengths

+        inputStream.alignPosition(4);

+        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+        if (identifier != TLEN_ID) {

+            throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));

+        }

+        int[] lengths = new int[amount];//theamount is the same as int types

+        for (int i = 0; i < amount; ++i) {

+            lengths[i] = inputStream.readShort();

+        }

+

+        //reading structures

+        inputStream.alignPosition(4);

+        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+        if (identifier != STRC_ID) {

+            throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));

+        }

+        amount = inputStream.readInt();

+        if (amount <= 0) {

+            throw new BlenderFileException("The structures amount number should be positive!");

+        }

+        structures = new Structure[amount];

+        structuresMap = new HashMap<String, Structure>(amount);

+        for (int i = 0; i < amount; ++i) {

+            structures[i] = new Structure(inputStream, names, types, blenderContext);

+            if (structuresMap.containsKey(structures[i].getType())) {

+                throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!");

+            }

+            structuresMap.put(structures[i].getType(), structures[i]);

+        }

+    }

+

+    /**

+     * This method returns the amount of the structures.

+     * @return the amount of the structures

+     */

+    public int getStructuresCount() {

+        return structures.length;

+    }

+

+    /**

+     * This method returns the structure of the given index.

+     * @param index

+     *        the index of the structure

+     * @return the structure of the given index

+     */

+    public Structure getStructure(int index) {

+        try {

+            return (Structure) structures[index].clone();

+        } catch (CloneNotSupportedException e) {

+            throw new IllegalStateException("Structure should be clonable!!!", e);

+        }

+    }

+

+    /**

+     * This method returns a structure of the given name. If the name does not exists then null is returned.

+     * @param name

+     *        the name of the structure

+     * @return the required structure or null if the given name is inapropriate

+     */

+    public Structure getStructure(String name) {

+        try {

+            return (Structure) structuresMap.get(name).clone();

+        } catch (CloneNotSupportedException e) {

+            throw new IllegalStateException(e.getMessage(), e);

+        }

+    }

+

+    /**

+     * This method indicates if the structure of the given name exists.

+     * @param name

+     *        the name of the structure

+     * @return true if the structure exists and false otherwise

+     */

+    public boolean hasStructure(String name) {

+        return structuresMap.containsKey(name);

+    }

+

+    /**

+     * This method converts the given identifier code to string.

+     * @param code

+     *        the code taht is to be converted

+     * @return the string value of the identifier

+     */

+    private String toString(int code) {

+        char c1 = (char) ((code & 0xFF000000) >> 24);

+        char c2 = (char) ((code & 0xFF0000) >> 16);

+        char c3 = (char) ((code & 0xFF00) >> 8);

+        char c4 = (char) (code & 0xFF);

+        return String.valueOf(c1) + c2 + c3 + c4;

+    }

+

+    @Override

+    public String toString() {

+        StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n');

+        for (Structure structure : structures) {

+            stringBuilder.append(structure.toString()).append('\n');

+        }

+        return stringBuilder.append("===============").toString();

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java
new file mode 100644
index 0000000..3fff5a8
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java
@@ -0,0 +1,157 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+

+/**

+ * An array that can be dynamically modified/

+ * @author Marcin Roguski

+ * @param <T>

+ *        the type of stored data in the array

+ */

+public class DynamicArray<T> implements Cloneable {

+

+    /** An array object that holds the required data. */

+    private T[] array;

+    /**

+     * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a

+     * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths:

+     * dynTable[a][b][c], where a,b,c are stored in the tableSizes table.

+     */

+    private int[] tableSizes;

+

+    /**

+     * Constructor. Builds an empty array of the specified sizes.

+     * @param tableSizes

+     *        the sizes of the table

+     * @throws BlenderFileException

+     *         an exception is thrown if one of the sizes is not a positive number

+     */

+    @SuppressWarnings("unchecked")

+    public DynamicArray(int[] tableSizes) throws BlenderFileException {

+        this.tableSizes = tableSizes;

+        int totalSize = 1;

+        for (int size : tableSizes) {

+            if (size <= 0) {

+                throw new BlenderFileException("The size of the table must be positive!");

+            }

+            totalSize *= size;

+        }

+        this.array = (T[]) new Object[totalSize];

+    }

+

+    /**

+     * Constructor. Builds an empty array of the specified sizes.

+     * @param tableSizes

+     *        the sizes of the table

+     * @throws BlenderFileException

+     *         an exception is thrown if one of the sizes is not a positive number

+     */

+    public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException {

+        this.tableSizes = tableSizes;

+        int totalSize = 1;

+        for (int size : tableSizes) {

+            if (size <= 0) {

+                throw new BlenderFileException("The size of the table must be positive!");

+            }

+            totalSize *= size;

+        }

+        if (totalSize != data.length) {

+            throw new IllegalArgumentException("The size of the table does not match the size of the given data!");

+        }

+        this.array = data;

+    }

+

+    @Override

+    public Object clone() throws CloneNotSupportedException {

+        return super.clone();

+    }

+

+    /**

+     * This method returns a value on the specified position. The dimension of the table is not taken into

+     * consideration.

+     * @param position

+     *        the position of the data

+     * @return required data

+     */

+    public T get(int position) {

+        return array[position];

+    }

+

+    /**

+     * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the

+     * table boundaries. Check the table's dimension first.

+     * @param position

+     *        the position of the data indices of data position

+     * @return required data required data

+     */

+    public T get(int... position) {

+        if (position.length != tableSizes.length) {

+            throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!");

+        }

+        int index = 0;

+        for (int i = 0; i < position.length - 1; ++i) {

+            index += position[i] * tableSizes[i + 1];

+        }

+        index += position[position.length - 1];

+        return array[index];

+    }

+

+    /**

+     * This method returns the total amount of data stored in the array.

+     * @return the total amount of data stored in the array

+     */

+    public int getTotalSize() {

+        return array.length;

+    }

+

+    @Override

+    public String toString() {

+        StringBuilder result = new StringBuilder();

+        if (array instanceof Character[]) {//in case of character array we convert it to String

+            for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {//strings are terminater with '0'

+                result.append(array[i]);

+            }

+        } else {

+            result.append('[');

+            for (int i = 0; i < array.length; ++i) {

+                result.append(array[i].toString());

+                if (i + 1 < array.length) {

+                    result.append(',');

+                }

+            }

+            result.append(']');

+        }

+        return result.toString();

+    }

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java
new file mode 100644
index 0000000..ef7802d
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java
@@ -0,0 +1,316 @@
+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure.DataType;

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to

+ * another structure.

+ * @author Marcin Roguski

+ */

+/*package*/

+class Field implements Cloneable {

+

+    private static final int NAME_LENGTH = 24;

+    private static final int TYPE_LENGTH = 16;

+    /** The blender context. */

+    public BlenderContext blenderContext;

+    /** The type of the field. */

+    public String type;

+    /** The name of the field. */

+    public String name;

+    /** The value of the field. Filled during data reading. */

+    public Object value;

+    /** This variable indicates the level of the pointer. */

+    public int pointerLevel;

+    /**

+     * This variable determines the sizes of the array. If the value is null the n the field is not an array.

+     */

+    public int[] tableSizes;

+    /** This variable indicates if the field is a function pointer. */

+    public boolean function;

+

+    /**

+     * Constructor. Saves the field data and parses its name.

+     * @param name

+     *        the name of the field

+     * @param type

+     *        the type of the field

+     * @param blenderContext

+     *        the blender context

+     * @throws BlenderFileException

+     *         this exception is thrown if the names contain errors

+     */

+    public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException {

+        this.type = type;

+        this.blenderContext = blenderContext;

+        this.parseField(new StringBuilder(name));

+    }

+

+    /**

+     * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we

+     * have a clead empty copy of the filed to fill with data.

+     * @param field

+     *        the object that we copy

+     */

+    private Field(Field field) {

+        type = field.type;

+        name = field.name;

+        blenderContext = field.blenderContext;

+        pointerLevel = field.pointerLevel;

+        if (field.tableSizes != null) {

+            tableSizes = field.tableSizes.clone();

+        }

+        function = field.function;

+    }

+

+    @Override

+    public Object clone() throws CloneNotSupportedException {

+        return new Field(this);

+    }

+

+    /**

+     * This method fills the field wth data read from the input stream.

+     * @param blenderInputStream

+     *        the stream we read data from

+     * @throws BlenderFileException

+     *         an exception is thrown when the blend file is somehow invalid or corrupted

+     */

+    public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {

+        int dataToRead = 1;

+        if (tableSizes != null && tableSizes.length > 0) {

+            for (int size : tableSizes) {

+                if (size <= 0) {

+                    throw new BlenderFileException("The field " + name + " has invalid table size: " + size);

+                }

+                dataToRead *= size;

+            }

+        }

+        DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER;

+        switch (dataType) {

+            case POINTER:

+                if (dataToRead == 1) {

+                    Pointer pointer = new Pointer(pointerLevel, function, blenderContext);

+                    pointer.fill(blenderInputStream);

+                    value = pointer;

+                } else {

+                    Pointer[] data = new Pointer[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        Pointer pointer = new Pointer(pointerLevel, function, blenderContext);

+                        pointer.fill(blenderInputStream);

+                        data[i] = pointer;

+                    }

+                    value = new DynamicArray<Pointer>(tableSizes, data);

+                }

+                break;

+            case CHARACTER:

+                //character is also stored as a number, because sometimes the new blender version uses

+                //other number type instead of character as a field type

+                //and characters are very often used as byte number stores instead of real chars

+                if (dataToRead == 1) {

+                    value = Byte.valueOf((byte) blenderInputStream.readByte());

+                } else {

+                    Character[] data = new Character[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Character.valueOf((char) blenderInputStream.readByte());

+                    }

+                    value = new DynamicArray<Character>(tableSizes, data);

+                }

+                break;

+            case SHORT:

+                if (dataToRead == 1) {

+                    value = Integer.valueOf(blenderInputStream.readShort());

+                } else {

+                    Number[] data = new Number[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Integer.valueOf(blenderInputStream.readShort());

+                    }

+                    value = new DynamicArray<Number>(tableSizes, data);

+                }

+                break;

+            case INTEGER:

+                if (dataToRead == 1) {

+                    value = Integer.valueOf(blenderInputStream.readInt());

+                } else {

+                    Number[] data = new Number[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Integer.valueOf(blenderInputStream.readInt());

+                    }

+                    value = new DynamicArray<Number>(tableSizes, data);

+                }

+                break;

+            case LONG:

+                if (dataToRead == 1) {

+                    value = Long.valueOf(blenderInputStream.readLong());

+                } else {

+                    Number[] data = new Number[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Long.valueOf(blenderInputStream.readLong());

+                    }

+                    value = new DynamicArray<Number>(tableSizes, data);

+                }

+                break;

+            case FLOAT:

+                if (dataToRead == 1) {

+                    value = Float.valueOf(blenderInputStream.readFloat());

+                } else {

+                    Number[] data = new Number[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Float.valueOf(blenderInputStream.readFloat());

+                    }

+                    value = new DynamicArray<Number>(tableSizes, data);

+                }

+                break;

+            case DOUBLE:

+                if (dataToRead == 1) {

+                    value = Double.valueOf(blenderInputStream.readDouble());

+                } else {

+                    Number[] data = new Number[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        data[i] = Double.valueOf(blenderInputStream.readDouble());

+                    }

+                    value = new DynamicArray<Number>(tableSizes, data);

+                }

+                break;

+            case VOID:

+                break;

+            case STRUCTURE:

+                if (dataToRead == 1) {

+                    Structure structure = blenderContext.getDnaBlockData().getStructure(type);

+                    structure.fill(blenderInputStream);

+                    value = structure;

+                } else {

+                    Structure[] data = new Structure[dataToRead];

+                    for (int i = 0; i < dataToRead; ++i) {

+                        Structure structure = blenderContext.getDnaBlockData().getStructure(type);

+                        structure.fill(blenderInputStream);

+                        data[i] = structure;

+                    }

+                    value = new DynamicArray<Structure>(tableSizes, data);

+                }

+                break;

+            default:

+                throw new IllegalStateException("Unimplemented filling of type: " + type);

+        }

+    }

+

+    /**

+     * This method parses the field name to determine how the field should be used.

+     * @param nameBuilder

+     *        the name of the field (given as StringBuilder)

+     * @throws BlenderFileException

+     *         this exception is thrown if the names contain errors

+     */

+    private void parseField(StringBuilder nameBuilder) throws BlenderFileException {

+        this.removeWhitespaces(nameBuilder);

+        //veryfying if the name is a pointer

+        int pointerIndex = nameBuilder.indexOf("*");

+        while (pointerIndex >= 0) {

+            ++pointerLevel;

+            nameBuilder.deleteCharAt(pointerIndex);

+            pointerIndex = nameBuilder.indexOf("*");

+        }

+        //veryfying if the name is a function pointer

+        if (nameBuilder.indexOf("(") >= 0) {

+            function = true;

+            this.removeCharacter(nameBuilder, '(');

+            this.removeCharacter(nameBuilder, ')');

+        } else {

+            //veryfying if the name is a table

+            int tableStartIndex = 0;

+            List<Integer> lengths = new ArrayList<Integer>(3);//3 dimensions will be enough in most cases

+            do {

+                tableStartIndex = nameBuilder.indexOf("[");

+                if (tableStartIndex > 0) {

+                    int tableStopIndex = nameBuilder.indexOf("]");

+                    if (tableStopIndex < 0) {

+                        throw new BlenderFileException("Invalid structure name: " + name);

+                    }

+                    try {

+                        lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex)));

+                    } catch (NumberFormatException e) {

+                        throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e);

+                    }

+                    nameBuilder.delete(tableStartIndex, tableStopIndex + 1);

+                }

+            } while (tableStartIndex > 0);

+            if (!lengths.isEmpty()) {

+                tableSizes = new int[lengths.size()];

+                for (int i = 0; i < tableSizes.length; ++i) {

+                    tableSizes[i] = lengths.get(i).intValue();

+                }

+            }

+        }

+        name = nameBuilder.toString();

+    }

+

+    /**

+     * This method removes the required character from the text.

+     * @param text

+     *        the text we remove characters from

+     * @param toRemove

+     *        the character to be removed

+     */

+    private void removeCharacter(StringBuilder text, char toRemove) {

+        for (int i = 0; i < text.length(); ++i) {

+            if (text.charAt(i) == toRemove) {

+                text.deleteCharAt(i);

+                --i;

+            }

+        }

+    }

+

+    /**

+     * This method removes all whitespaces from the text.

+     * @param text

+     *        the text we remove whitespaces from

+     */

+    private void removeWhitespaces(StringBuilder text) {

+        for (int i = 0; i < text.length(); ++i) {

+            if (Character.isWhitespace(text.charAt(i))) {

+                text.deleteCharAt(i);

+                --i;

+            }

+        }

+    }

+

+    @Override

+    public String toString() {

+        StringBuilder result = new StringBuilder();

+        if (function) {

+            result.append('(');

+        }

+        for (int i = 0; i < pointerLevel; ++i) {

+            result.append('*');

+        }

+        result.append(name);

+        if (tableSizes != null) {

+            for (int i = 0; i < tableSizes.length; ++i) {

+                result.append('[').append(tableSizes[i]).append(']');

+            }

+        }

+        if (function) {

+            result.append(")()");

+        }

+        //insert appropriate amount of spaces to format the output corrently

+        int nameLength = result.length();

+        result.append(' ');//at least one space is a must

+        for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added

+            result.append(' ');

+        }

+        result.append(type);

+        nameLength = result.length();

+        for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) {

+            result.append(' ');

+        }

+        if (value instanceof Character) {

+            result.append(" = ").append((int) ((Character) value).charValue());

+        } else {

+            result.append(" = ").append(value != null ? value.toString() : "null");

+        }

+        return result.toString();

+    }

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java
new file mode 100644
index 0000000..c2ca6b0
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java
@@ -0,0 +1,199 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+

+/**

+ * A class that holds the header data of a file block. The file block itself is not implemented. This class holds its

+ * start position in the stream and using this the structure can fill itself with the proper data.

+ * @author Marcin Roguski

+ */

+public class FileBlockHeader {

+

+    public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16;					//TE00

+    public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16;					//ME00

+    public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16;					//SR00

+    public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16;					//CA00

+    public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16;					//LA00

+    public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16;					//OB00

+    public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16;					//MA00

+    public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16;					//SC00

+    public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16;					//WO00

+    public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16;					//TX00

+    public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16;					//IP00

+    public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16;					//AC00

+    public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B';	//GLOB

+    public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D';	//REND

+    public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A';	//DATA

+    public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1';	//DNA1

+    public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B';	//ENDB

+    /** Identifier of the file-block [4 bytes]. */

+    private int code;

+    /** Total length of the data after the file-block-header [4 bytes]. */

+    private int size;

+    /**

+     * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer

+     * size)].

+     */

+    private long oldMemoryAddress;

+    /** Index of the SDNA structure [4 bytes]. */

+    private int sdnaIndex;

+    /** Number of structure located in this file-block [4 bytes]. */

+    private int count;

+    /** Start position of the block's data in the stream. */

+    private int blockPosition;

+

+    /**

+     * Constructor. Loads the block header from the given stream during instance creation.

+     * @param inputStream

+     *        the stream we read the block header from

+     * @param blenderContext

+     *        the blender context

+     * @throws BlenderFileException

+     *         this exception is thrown when the pointer size is neither 4 nor 8

+     */

+    public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {

+        inputStream.alignPosition(4);

+        code = inputStream.readByte() << 24 | inputStream.readByte() << 16

+                | inputStream.readByte() << 8 | inputStream.readByte();

+        size = inputStream.readInt();

+        oldMemoryAddress = inputStream.readPointer();

+        sdnaIndex = inputStream.readInt();

+        count = inputStream.readInt();

+        blockPosition = inputStream.getPosition();

+        if (FileBlockHeader.BLOCK_DNA1 == code) {

+            blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));

+        } else {

+            inputStream.setPosition(blockPosition + size);

+            blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this);

+        }

+    }

+

+    /**

+     * This method returns the structure described by the header filled with appropriate data.

+     * @param blenderContext

+     *        the blender context

+     * @return structure filled with data

+     * @throws BlenderFileException

+     */

+    public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException {

+        blenderContext.getInputStream().setPosition(blockPosition);

+        Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex);

+        structure.fill(blenderContext.getInputStream());

+        return structure;

+    }

+

+    /**

+     * This method returns the code of this data block.

+     * @return the code of this data block

+     */

+    public int getCode() {

+        return code;

+    }

+

+    /**

+     * This method returns the size of the data stored in this block.

+     * @return the size of the data stored in this block

+     */

+    public int getSize() {

+        return size;

+    }

+

+    /**

+     * This method returns the memory address.

+     * @return the memory address

+     */

+    public long getOldMemoryAddress() {

+        return oldMemoryAddress;

+    }

+

+    /**

+     * This method returns the sdna index.

+     * @return the sdna index

+     */

+    public int getSdnaIndex() {

+        return sdnaIndex;

+    }

+

+    /**

+     * This data returns the number of structure stored in the data block after this header.

+     * @return the number of structure stored in the data block after this header

+     */

+    public int getCount() {

+        return count;

+    }

+

+    /**

+     * This method returns the start position of the data block in the blend file stream.

+     * @return the start position of the data block

+     */

+    public int getBlockPosition() {

+        return blockPosition;

+    }

+

+    /**

+     * This method indicates if the block is the last block in the file.

+     * @return true if this block is the last one in the file nad false otherwise

+     */

+    public boolean isLastBlock() {

+        return FileBlockHeader.BLOCK_ENDB == code;

+    }

+

+    /**

+     * This method indicates if the block is the SDNA block.

+     * @return true if this block is the SDNA block and false otherwise

+     */

+    public boolean isDnaBlock() {

+        return FileBlockHeader.BLOCK_DNA1 == code;

+    }

+

+    @Override

+    public String toString() {

+        return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";

+    }

+

+    /**

+     * This method transforms the coded bloch id into a string value.

+     * @param code

+     *        the id of the block

+     * @return the string value of the block id

+     */

+    protected String codeToString(int code) {

+        char c1 = (char) ((code & 0xFF000000) >> 24);

+        char c2 = (char) ((code & 0xFF0000) >> 16);

+        char c3 = (char) ((code & 0xFF00) >> 8);

+        char c4 = (char) (code & 0xFF);

+        return String.valueOf(c1) + c2 + c3 + c4;

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java
new file mode 100644
index 0000000..59f20e9
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java
@@ -0,0 +1,180 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.file;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * A class that represents a pointer of any level that can be stored in the file.

+ * @author Marcin Roguski

+ */

+public class Pointer {

+

+    /** The blender context. */

+    private BlenderContext blenderContext;

+    /** The level of the pointer. */

+    private int pointerLevel;

+    /** The address in file it points to. */

+    private long oldMemoryAddress;

+    /** This variable indicates if the field is a function pointer. */

+    public boolean function;

+

+    /**

+     * Constructr. Stores the basic data about the pointer.

+     * @param pointerLevel

+     *        the level of the pointer

+     * @param function

+     *        this variable indicates if the field is a function pointer

+     * @param blenderContext

+     *        the repository f data; used in fetching the value that the pointer points

+     */

+    public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) {

+        this.pointerLevel = pointerLevel;

+        this.function = function;

+        this.blenderContext = blenderContext;

+    }

+

+    /**

+     * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method

+     * for this.

+     * @param inputStream

+     *        the stream we read the pointer value from

+     */

+    public void fill(BlenderInputStream inputStream) {

+        oldMemoryAddress = inputStream.readPointer();

+    }

+

+    /**

+     * This method fetches the data stored under the given address.

+     * @param inputStream

+     *        the stream we read data from

+     * @return the data read from the file

+     * @throws BlenderFileException

+     *         this exception is thrown when the blend file structure is somehow invalid or corrupted

+     */

+    public List<Structure> fetchData(BlenderInputStream inputStream) throws BlenderFileException {

+        if (oldMemoryAddress == 0) {

+            throw new NullPointerException("The pointer points to nothing!");

+        }

+        List<Structure> structures = null;

+        FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress);

+        if (pointerLevel > 1) {

+            int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();

+            for (int i = 0; i < pointersAmount; ++i) {

+                inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i);

+                long oldMemoryAddress = inputStream.readPointer();

+                if (oldMemoryAddress != 0L) {

+                    Pointer p = new Pointer(pointerLevel - 1, this.function, blenderContext);

+                    p.oldMemoryAddress = oldMemoryAddress;

+                    if (structures == null) {

+                        structures = p.fetchData(inputStream);

+                    } else {

+                        structures.addAll(p.fetchData(inputStream));

+                    }

+                }

+            }

+        } else {

+            inputStream.setPosition(dataFileBlock.getBlockPosition());

+            structures = new ArrayList<Structure>(dataFileBlock.getCount());

+            for (int i = 0; i < dataFileBlock.getCount(); ++i) {

+                Structure structure = blenderContext.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex());

+                structure.fill(inputStream);

+                structures.add(structure);

+            }

+            return structures;

+        }

+        return structures;

+    }

+

+    /**

+     * This method indicates if this pointer points to a function.

+     * @return <b>true</b> if this is a function pointer and <b>false</b> otherwise

+     */

+    public boolean isFunction() {

+        return function;

+    }

+

+    /**

+     * This method indicates if this is a null-pointer or not.

+     * @return <b>true</b> if the pointer is null and <b>false</b> otherwise

+     */

+    public boolean isNull() {

+        return oldMemoryAddress == 0;

+    }

+    

+    /**

+     * This method indicates if this is a null-pointer or not.

+     * @return <b>true</b> if the pointer is not null and <b>false</b> otherwise

+     */

+    public boolean isNotNull() {

+        return oldMemoryAddress != 0;

+    }

+

+    /**

+     * This method returns the old memory address of the structure pointed by the pointer.

+     * @return the old memory address of the structure pointed by the pointer

+     */

+    public long getOldMemoryAddress() {

+        return oldMemoryAddress;

+    }

+

+    @Override

+    public String toString() {

+        return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}";

+    }

+

+    @Override

+    public int hashCode() {

+        return 31 + (int) (oldMemoryAddress ^ oldMemoryAddress >>> 32);

+    }

+

+    @Override

+    public boolean equals(Object obj) {

+        if (this == obj) {

+            return true;

+        }

+        if (obj == null) {

+            return false;

+        }

+        if (this.getClass() != obj.getClass()) {

+            return false;

+        }

+        Pointer other = (Pointer) obj;

+        if (oldMemoryAddress != other.oldMemoryAddress) {

+            return false;

+        }

+        return true;

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java
new file mode 100644
index 0000000..bf4afa2
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.file;
+
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class representing a single structure in the file.
+ * @author Marcin Roguski
+ */
+public class Structure implements Cloneable {
+
+    /** The blender context. */
+    private BlenderContext blenderContext;
+    /** The address of the block that fills the structure. */
+    private transient Long oldMemoryAddress;
+    /** The type of the structure. */
+    private String type;
+    /**
+     * The fields of the structure. Each field consists of a pair: name-type.
+     */
+    private Field[] fields;
+
+    /**
+     * Constructor that copies the data of the structure.
+     * @param structure
+     *        the structure to copy.
+     * @param blenderContext
+     *        the blender context of the structure
+     * @throws CloneNotSupportedException
+     *         this exception should never be thrown
+     */
+    private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException {
+        type = structure.type;
+        fields = new Field[structure.fields.length];
+        for (int i = 0; i < fields.length; ++i) {
+            fields[i] = (Field) structure.fields[i].clone();
+        }
+        this.blenderContext = blenderContext;
+        this.oldMemoryAddress = structure.oldMemoryAddress;
+    }
+
+    /**
+     * Constructor. Loads the structure from the given stream during instance creation.
+     * @param inputStream
+     *        the stream we read the structure from
+     * @param names
+     *        the names from which the name of structure and its fields will be taken
+     * @param types
+     *        the names of types for the structure
+     * @param blenderContext
+     *        the blender context
+     * @throws BlenderFileException
+     *         this exception occurs if the amount of fields, defined in the file, is negative
+     */
+    public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException {
+        int nameIndex = inputStream.readShort();
+        type = types[nameIndex];
+        this.blenderContext = blenderContext;
+        int fieldsAmount = inputStream.readShort();
+        if (fieldsAmount < 0) {
+            throw new BlenderFileException("The amount of fields of " + this.type + " structure cannot be negative!");
+        }
+        if (fieldsAmount > 0) {
+            fields = new Field[fieldsAmount];
+            for (int i = 0; i < fieldsAmount; ++i) {
+                int typeIndex = inputStream.readShort();
+                nameIndex = inputStream.readShort();
+                fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext);
+            }
+        }
+        this.oldMemoryAddress = Long.valueOf(-1L);
+    }
+
+    /**
+     * This method fills the structure with data.
+     * @param inputStream
+     *        the stream we read data from, its read cursor should be placed at the start position of the data for the
+     *        structure
+     * @throws BlenderFileException
+     *         an exception is thrown when the blend file is somehow invalid or corrupted
+     */
+    public void fill(BlenderInputStream inputStream) throws BlenderFileException {
+        int position = inputStream.getPosition();
+        inputStream.setPosition(position - 8 - inputStream.getPointerSize());
+        this.oldMemoryAddress = Long.valueOf(inputStream.readPointer());
+        inputStream.setPosition(position);
+        for (Field field : fields) {
+            field.fill(inputStream);
+        }
+    }
+
+    /**
+     * This method returns the value of the filed with a given name.
+     * @param fieldName
+     *        the name of the field
+     * @return the value of the field or null if no field with a given name is found
+     */
+    public Object getFieldValue(String fieldName) {
+        for (Field field : fields) {
+            if (field.name.equalsIgnoreCase(fieldName)) {
+                return field.value;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This method returns the value of the filed with a given name. The structure is considered to have flat fields
+     * only (no substructures).
+     * @param fieldName
+     *        the name of the field
+     * @return the value of the field or null if no field with a given name is found
+     */
+    public Object getFlatFieldValue(String fieldName) {
+        for (Field field : fields) {
+            Object value = field.value;
+            if (field.name.equalsIgnoreCase(fieldName)) {
+                return value;
+            } else if (value instanceof Structure) {
+                value = ((Structure) value).getFlatFieldValue(fieldName);
+                if (value != null) {//we can compare references here, since we use one static object as a NULL field value
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are
+     * held by this structure within the blend file.
+     * @param blenderContext
+     *        the blender context
+     * @return a list of filled structures
+     * @throws BlenderFileException
+     *         this exception is thrown when the blend file structure is somehow invalid or corrupted
+     * @throws IllegalArgumentException
+     *         this exception is thrown if the type of the structure is not 'ListBase'
+     */
+    public List<Structure> evaluateListBase(BlenderContext blenderContext) throws BlenderFileException {
+        if (!"ListBase".equals(this.type)) {
+            throw new IllegalStateException("This structure is not of type: 'ListBase'");
+        }
+        Pointer first = (Pointer) this.getFieldValue("first");
+        Pointer last = (Pointer) this.getFieldValue("last");
+        long currentAddress = 0;
+        long lastAddress = last.getOldMemoryAddress();
+        List<Structure> result = new LinkedList<Structure>();
+        while (currentAddress != lastAddress) {
+            currentAddress = first.getOldMemoryAddress();
+            Structure structure = first.fetchData(blenderContext.getInputStream()).get(0);
+            result.add(structure);
+            first = (Pointer) structure.getFlatFieldValue("next");
+        }
+        return result;
+    }
+
+    /**
+     * This method returns the type of the structure.
+     * @return the type of the structure
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * This method returns the amount of fields for the current structure.
+     * @return the amount of fields for the current structure
+     */
+    public int getFieldsAmount() {
+        return fields.length;
+    }
+
+    /**
+     * This method returns the field name of the given index.
+     * @param fieldIndex
+     *        the index of the field
+     * @return the field name of the given index
+     */
+    public String getFieldName(int fieldIndex) {
+        return fields[fieldIndex].name;
+    }
+
+    /**
+     * This method returns the field type of the given index.
+     * @param fieldIndex
+     *        the index of the field
+     * @return the field type of the given index
+     */
+    public String getFieldType(int fieldIndex) {
+        return fields[fieldIndex].type;
+    }
+
+    /**
+     * This method returns the address of the structure. The strucutre should be filled with data otherwise an exception
+     * is thrown.
+     * @return the address of the feature stored in this structure
+     */
+    public Long getOldMemoryAddress() {
+        if (oldMemoryAddress.longValue() == -1L) {
+            throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!");
+        }
+        return oldMemoryAddress;
+    }
+
+/**
+	 * This method returns the name of the structure. If the structure has an ID field then the name is returned.
+	 * Otherwise the name does not exists and the method returns null.
+	 * @return the name of the structure read from the ID field or null
+	 */
+	public String getName() {
+		Object fieldValue = this.getFieldValue("ID");
+		if(fieldValue instanceof Structure) {
+			Structure id = (Structure)fieldValue;
+			return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix
+		}
+		return null;
+	}
+	
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n");
+        for (int i = 0; i < fields.length; ++i) {
+            result.append(fields[i].toString()).append('\n');
+        }
+        return result.append('}').toString();
+    }
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        return new Structure(this, blenderContext);
+    }
+
+    /**
+     * This enum enumerates all known data types that can be found in the blend file.
+     * @author Marcin Roguski
+     */
+    /*package*/
+    static enum DataType {
+
+        CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER;
+        /** The map containing the known primary types. */
+        private static final Map<String, DataType> PRIMARY_TYPES = new HashMap<String, DataType>(10);
+
+        static {
+            PRIMARY_TYPES.put("char", CHARACTER);
+            PRIMARY_TYPES.put("uchar", CHARACTER);
+            PRIMARY_TYPES.put("short", SHORT);
+            PRIMARY_TYPES.put("ushort", SHORT);
+            PRIMARY_TYPES.put("int", INTEGER);
+            PRIMARY_TYPES.put("long", LONG);
+            PRIMARY_TYPES.put("ulong", LONG);
+            PRIMARY_TYPES.put("uint64_t", LONG);
+            PRIMARY_TYPES.put("float", FLOAT);
+            PRIMARY_TYPES.put("double", DOUBLE);
+            PRIMARY_TYPES.put("void", VOID);
+        }
+
+        /**
+         * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition
+         * is case sensitive!
+         * @param type
+         *        the type name of the data
+         * @param blenderContext
+         *        the blender context
+         * @return appropriate enum value to the given type name
+         * @throws BlenderFileException
+         *         this exception is thrown if the given type name does not exist in the blend file
+         */
+        public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException {
+            DataType result = PRIMARY_TYPES.get(type);
+            if (result != null) {
+                return result;
+            }
+            if (blenderContext.getDnaBlockData().hasStructure(type)) {
+                return STRUCTURE;
+            }
+            throw new BlenderFileException("Unknown data type: " + type);
+        }
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java
new file mode 100644
index 0000000..add3e21
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java
@@ -0,0 +1,120 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.lights;

+

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.light.DirectionalLight;

+import com.jme3.light.Light;

+import com.jme3.light.PointLight;

+import com.jme3.light.SpotLight;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.FastMath;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * A class that is used in light calculations.

+ * @author Marcin Roguski

+ */

+public class LightHelper extends AbstractBlenderHelper {

+

+    private static final Logger LOGGER = Logger.getLogger(LightHelper.class.getName());

+

+    /**

+     * This constructor parses the given blender version and stores the result. Some functionalities may differ in

+     * different blender versions.

+     * @param blenderVersion

+     *        the version read from the blend file

+     * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+     */

+    public LightHelper(String blenderVersion, boolean fixUpAxis) {

+        super(blenderVersion, fixUpAxis);

+    }

+

+    public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {

+        Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+        if (result != null) {

+            return result;

+        }

+        int type = ((Number) structure.getFieldValue("type")).intValue();

+        switch (type) {

+            case 0://Lamp

+                result = new PointLight();

+                float distance = ((Number) structure.getFieldValue("dist")).floatValue();

+                ((PointLight) result).setRadius(distance);

+                break;

+            case 1://Sun

+                LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine.");

+                break;

+            case 2://Spot

+            	result = new SpotLight();

+            	//range

+            	((SpotLight)result).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue());

+            	//outer angle

+            	float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue()*FastMath.DEG_TO_RAD * 0.5f;

+            	((SpotLight)result).setSpotOuterAngle(outerAngle);

+            	

+            	//inner angle

+            	float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue();

+                spotblend = FastMath.clamp(spotblend, 0, 1);

+                float innerAngle = outerAngle * (1 - spotblend);

+            	((SpotLight)result).setSpotInnerAngle(innerAngle);

+                break;

+            case 3://Hemi

+                LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine.");

+                break;

+            case 4://Area

+                result = new DirectionalLight();

+                break;

+            default:

+                throw new BlenderFileException("Unknown light source type: " + type);

+        }

+        if (result != null) {

+            float r = ((Number) structure.getFieldValue("r")).floatValue();

+            float g = ((Number) structure.getFieldValue("g")).floatValue();

+            float b = ((Number) structure.getFieldValue("b")).floatValue();

+            result.setColor(new ColorRGBA(r, g, b, 1.0f));

+        }

+        return result;

+    }

+    

+    @Override

+    public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+    	return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0;

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java
new file mode 100644
index 0000000..45e6f35
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java
@@ -0,0 +1,26 @@
+package com.jme3.scene.plugins.blender.materials;
+
+/**
+ * An interface used in calculating alpha mask during particles' texture calculations.
+ * @author Marcin Roguski (Kaelthas)
+ */
+/*package*/ interface IAlphaMask {
+	/**
+	 * This method sets the size of the texture's image.
+	 * @param width
+	 *        the width of the image
+	 * @param height
+	 *        the height of the image
+	 */
+	void setImageSize(int width, int height);
+
+	/**
+	 * This method returns the alpha value for the specified texture position.
+	 * @param x
+	 *        the X coordinate of the texture position
+	 * @param y
+	 *        the Y coordinate of the texture position
+	 * @return the alpha value for the specified texture position
+	 */
+	byte getAlpha(float x, float y);
+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
new file mode 100644
index 0000000..2e6b0cf
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
@@ -0,0 +1,483 @@
+package com.jme3.scene.plugins.blender.materials;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.DynamicArray;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
+import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
+import com.jme3.scene.plugins.blender.textures.TextureHelper;
+import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
+import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.Type;
+import com.jme3.texture.Texture.WrapMode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class holds the data about the material.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class MaterialContext {
+	private static final Logger			LOGGER				= Logger.getLogger(MaterialContext.class.getName());
+
+	//texture mapping types
+	public static final int				MTEX_COL = 0x01;
+	public static final int				MTEX_NOR = 0x02;
+	public static final int				MTEX_SPEC = 0x04;
+	public static final int				MTEX_EMIT = 0x40;
+	public static final int				MTEX_ALPHA = 0x80;
+	
+	/* package */final String			name;
+	/* package */final List<Structure>	mTexs;
+	/* package */final List<Structure>	textures;
+	/* package */final Map<Number, Texture> loadedTextures;
+	/* package */final Map<Texture, Structure> textureToMTexMap;
+	/* package */final int				texturesCount;
+	/* package */final Type				textureType;
+
+	/* package */final ColorRGBA		diffuseColor;
+	/* package */final DiffuseShader 	diffuseShader;
+	/* package */final SpecularShader 	specularShader;
+	/* package */final ColorRGBA		specularColor;
+	/* package */final ColorRGBA		ambientColor;
+	/* package */final float 			shininess;
+	/* package */final boolean			shadeless;
+	/* package */final boolean			vertexColor;
+	/* package */final boolean			transparent;
+	/* package */final boolean			vTangent;
+
+	/* package */int					uvCoordinatesType	= -1;
+	/* package */int					projectionType;
+
+	@SuppressWarnings("unchecked")
+	/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
+		name = structure.getName();
+
+		int mode = ((Number) structure.getFieldValue("mode")).intValue();
+		shadeless = (mode & 0x4) != 0;
+		vertexColor = (mode & 0x80) != 0;
+		vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents
+
+		int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
+		diffuseShader = DiffuseShader.values()[diff_shader];
+		
+		if(this.shadeless) {
+            float r = ((Number) structure.getFieldValue("r")).floatValue();
+            float g = ((Number) structure.getFieldValue("g")).floatValue();
+            float b = ((Number) structure.getFieldValue("b")).floatValue();
+            float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
+
+			diffuseColor = new ColorRGBA(r, g, b, alpha);
+			specularShader = null;
+			specularColor = ambientColor = null;
+			shininess = 0.0f;
+		} else {
+			diffuseColor = this.readDiffuseColor(structure, diffuseShader);
+			
+			int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue();
+			specularShader = SpecularShader.values()[spec_shader];
+			specularColor = this.readSpecularColor(structure, specularShader);
+			
+			float r = ((Number) structure.getFieldValue("ambr")).floatValue();
+			float g = ((Number) structure.getFieldValue("ambg")).floatValue();
+			float b = ((Number) structure.getFieldValue("ambb")).floatValue();
+			float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
+			ambientColor = new ColorRGBA(r, g, b, alpha);
+			
+			float shininess = ((Number) structure.getFieldValue("emit")).floatValue();
+			this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
+		}
+		
+		mTexs = new ArrayList<Structure>();
+		textures = new ArrayList<Structure>();
+		
+		DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
+		int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
+		Type firstTextureType = null;
+		for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
+			Pointer p = mtexsArray.get(i);
+			if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
+				Structure mtex = p.fetchData(blenderContext.getInputStream()).get(0);
+
+				// the first texture determines the texture coordinates type
+				if (uvCoordinatesType == -1) {
+					uvCoordinatesType = ((Number) mtex.getFieldValue("texco")).intValue();
+					projectionType = ((Number) mtex.getFieldValue("mapping")).intValue();
+				} else if (uvCoordinatesType != ((Number) mtex.getFieldValue("texco")).intValue()) {
+					LOGGER.log(Level.WARNING, "The texture with index: {0} has different UV coordinates type than the first texture! This texture will NOT be loaded!", i + 1);
+					continue;
+				}
+
+				Pointer pTex = (Pointer) mtex.getFieldValue("tex");
+				if (pTex.isNotNull()) {
+					Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
+					int type = ((Number) tex.getFieldValue("type")).intValue();
+					Type textureType = this.getType(type);
+					if (textureType != null) {
+						if (firstTextureType == null) {
+							firstTextureType = textureType;
+							mTexs.add(mtex);
+							textures.add(tex);
+						} else if (firstTextureType == textureType) {
+							mTexs.add(mtex);
+							textures.add(tex);
+						} else {
+							LOGGER.log(Level.WARNING, "The texture with index: {0} is of different dimension than the first one! This texture will NOT be loaded!", i + 1);
+						}
+					}
+				}
+			}
+		}
+		
+		//loading the textures and merging them
+		Map<Number, List<Structure[]>> sortedTextures = this.sortAndFilterTextures();
+		loadedTextures = new HashMap<Number, Texture>(sortedTextures.size());
+		textureToMTexMap = new HashMap<Texture, Structure>();
+		float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a};
+		TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
+		for(Entry<Number, List<Structure[]>> entry : sortedTextures.entrySet()) {
+			if(entry.getValue().size()>0) {
+				List<Texture> textures = new ArrayList<Texture>(entry.getValue().size());
+				for(Structure[] mtexAndTex : entry.getValue()) {
+					int texflag = ((Number) mtexAndTex[0].getFieldValue("texflag")).intValue();
+					boolean negateTexture = (texflag & 0x04) != 0;
+					Texture texture = textureHelper.getTexture(mtexAndTex[1], blenderContext);
+					int blendType = ((Number) mtexAndTex[0].getFieldValue("blendtype")).intValue();
+					float[] color = new float[] { ((Number) mtexAndTex[0].getFieldValue("r")).floatValue(), 
+												  ((Number) mtexAndTex[0].getFieldValue("g")).floatValue(), 
+												  ((Number) mtexAndTex[0].getFieldValue("b")).floatValue() };
+					float colfac = ((Number) mtexAndTex[0].getFieldValue("colfac")).floatValue();
+					TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat());
+					texture = textureBlender.blend(diffuseColorArray, texture, color, colfac, blendType, negateTexture, blenderContext);
+					texture.setWrap(WrapMode.Repeat);
+					textures.add(texture);
+					textureToMTexMap.put(texture, mtexAndTex[0]);
+				}
+				loadedTextures.put(entry.getKey(), textureHelper.mergeTextures(textures, this));
+			}
+		}
+
+		this.texturesCount = mTexs.size();
+		this.textureType = firstTextureType;
+		
+		//veryfying if  the transparency is present
+		//(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
+		//it is not required
+		boolean transparent = false;
+		if(diffuseColor != null) {
+			transparent = diffuseColor.a < 1.0f;
+			if(sortedTextures.size() > 0) {//texutre covers the material color
+				diffuseColor.set(1, 1, 1, 1);
+			}
+		}
+		if(specularColor != null) {
+			transparent = transparent || specularColor.a < 1.0f;
+		}
+		if(ambientColor != null) {
+			transparent = transparent || ambientColor.a < 1.0f;
+		}
+		this.transparent = transparent;
+	}
+	
+	/**
+	 * This method sorts the textures by their mapping type.
+	 * In each group only textures of one type are put (either two- or three-dimensional).
+	 * If the mapping type is MTEX_COL then if the texture has no alpha channel then all textures before it are
+	 * discarded and will not be loaded and merged because texture with no alpha will cover them anyway.
+	 * @return a map with sorted and filtered textures
+	 */
+	private Map<Number, List<Structure[]>> sortAndFilterTextures() {
+		Map<Number, List<Structure[]>> result = new HashMap<Number, List<Structure[]>>();
+		for (int i = 0; i < mTexs.size(); ++i) {
+			Structure mTex = mTexs.get(i);
+			Structure texture  = textures.get(i);
+			Number mapto = (Number) mTex.getFieldValue("mapto");
+			List<Structure[]> mtexs = result.get(mapto);
+			if(mtexs==null) {
+				mtexs = new ArrayList<Structure[]>();
+				result.put(mapto, mtexs);
+			}
+			if(mapto.intValue() == MTEX_COL && this.isWithoutAlpha(textures.get(i))) {
+				mtexs.clear();//remove previous textures, they will be covered anyway
+			}
+			mtexs.add(new Structure[] {mTex, texture});
+		}
+		return result;
+	}
+	
+	/**
+	 * This method determines if the given texture has no alpha channel.
+	 * 
+	 * @param texture
+	 *            the texture to check for alpha channel
+	 * @return <b>true</b> if the texture has no alpha channel and <b>false</b>
+	 *         otherwise
+	 */
+	private boolean isWithoutAlpha(Structure texture) {
+		int flag = ((Number) texture.getFieldValue("flag")).intValue();
+		if((flag & 0x01) == 0) {//the texture has no colorband
+			int type = ((Number) texture.getFieldValue("type")).intValue();
+			if(type==TextureHelper.TEX_MAGIC) {
+				return true;
+			}
+			if(type==TextureHelper.TEX_VORONOI) {
+				int voronoiColorType = ((Number) texture.getFieldValue("vn_coltype")).intValue();
+				return voronoiColorType != 0;//voronoiColorType == 0: intensity, voronoiColorType != 0: col1, col2 or col3
+			}
+			if(type==TextureHelper.TEX_CLOUDS) {
+				int sType = ((Number) texture.getFieldValue("stype")).intValue();
+				return sType == 1;//sType==0: without colors, sType==1: with colors
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * This method returns the current material's texture UV coordinates type.
+	 * @return uv coordinates type
+	 */
+	public int getUvCoordinatesType() {
+		return uvCoordinatesType;
+	}
+
+	/**
+	 * This method returns the proper projection type for the material's texture.
+	 * This applies only to 2D textures.
+	 * @return texture's projection type
+	 */
+	public int getProjectionType() {
+		return projectionType;
+	}
+
+	/**
+	 * This method returns current material's texture dimension.
+	 * @return the material's texture dimension
+	 */
+	public int getTextureDimension() {
+		return this.textureType == Type.TwoDimensional ? 2 : 3;
+	}
+
+	/**
+	 * This method returns the amount of textures applied for the current
+	 * material.
+	 * 
+	 * @return the amount of textures applied for the current material
+	 */
+	public int getTexturesCount() {
+		return textures == null ? 0 : textures.size();
+	}
+
+	/**
+	 * This method returns the projection array that indicates where the current coordinate factor X, Y or Z (represented
+	 * by the index in the array) will be used where (indicated by the value in the array).
+	 * For example the configuration: [1,2,3] means that X - coordinate will be used as X, Y as Y and Z as Z.
+	 * The configuration [2,1,0] means that Z will be used instead of X coordinate, Y will be used as Y and
+	 * Z will not be used at all (0 will be in its place).
+	 * @param textureIndex
+	 *        the index of the texture
+	 * @return texture projection array
+	 */
+	public int[] getProjection(int textureIndex) {
+		Structure mtex = mTexs.get(textureIndex);
+		return new int[] { ((Number) mtex.getFieldValue("projx")).intValue(), ((Number) mtex.getFieldValue("projy")).intValue(), ((Number) mtex.getFieldValue("projz")).intValue() };
+	}
+	
+	/**
+	 * This method returns the diffuse color.
+	 * 
+	 * @param materialStructure the material structure
+	 * @param diffuseShader the diffuse shader
+	 * @return the diffuse color
+	 */
+	private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
+		// bitwise 'or' of all textures mappings
+		int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
+
+		// diffuse color
+		float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
+		float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
+		float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
+		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
+		if ((commonMapto & 0x01) == 0x01) {// Col
+			return new ColorRGBA(r, g, b, alpha);
+		} else {
+			switch (diffuseShader) {
+				case FRESNEL:
+				case ORENNAYAR:
+				case TOON:
+					break;// TODO: find what is the proper modification
+				case MINNAERT:
+				case LAMBERT:// TODO: check if that is correct
+					float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
+					r *= ref;
+					g *= ref;
+					b *= ref;
+					break;
+				default:
+					throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
+			}
+			return new ColorRGBA(r, g, b, alpha);
+		}
+	}
+
+	/**
+	 * This method returns a specular color used by the material.
+	 * 
+	 * @param materialStructure
+	 *        the material structure filled with data
+	 * @return a specular color used by the material
+	 */
+	private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) {
+		float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();
+		float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();
+		float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();
+		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
+		switch (specularShader) {
+			case BLINN:
+			case COOKTORRENCE:
+			case TOON:
+			case WARDISO:// TODO: find what is the proper modification
+				break;
+			case PHONG:// TODO: check if that is correct
+				float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();
+				r *= spec * 0.5f;
+				g *= spec * 0.5f;
+				b *= spec * 0.5f;
+				break;
+			default:
+				throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());
+		}
+		return new ColorRGBA(r, g, b, alpha);
+	}
+
+	/**
+	 * This method determines the type of the texture.
+	 * @param texType
+	 *        texture type (from blender)
+	 * @return texture type (used by jme)
+	 */
+	private Type getType(int texType) {
+		switch (texType) {
+			case TextureHelper.TEX_IMAGE:// (it is first because probably this will be most commonly used)
+				return Type.TwoDimensional;
+			case TextureHelper.TEX_CLOUDS:
+			case TextureHelper.TEX_WOOD:
+			case TextureHelper.TEX_MARBLE:
+			case TextureHelper.TEX_MAGIC:
+			case TextureHelper.TEX_BLEND:
+			case TextureHelper.TEX_STUCCI:
+			case TextureHelper.TEX_NOISE:
+			case TextureHelper.TEX_MUSGRAVE:
+			case TextureHelper.TEX_VORONOI:
+			case TextureHelper.TEX_DISTNOISE:
+				return Type.ThreeDimensional;
+			case TextureHelper.TEX_NONE:// No texture, do nothing
+				return null;
+			case TextureHelper.TEX_POINTDENSITY:
+			case TextureHelper.TEX_VOXELDATA:
+			case TextureHelper.TEX_PLUGIN:
+			case TextureHelper.TEX_ENVMAP:
+				LOGGER.log(Level.WARNING, "Texture type NOT supported: {0}", texType);
+				return null;
+			default:
+				throw new IllegalStateException("Unknown texture type: " + texType);
+		}
+	}
+	
+	/**
+	 * @return he material's name
+	 */
+	public String getName() {
+		return name;
+	}
+	
+	/**
+	 * @return a copy of diffuse color
+	 */
+	public ColorRGBA getDiffuseColor() {
+		return diffuseColor.clone();
+	}
+	
+	/**
+	 * @return an enum describing the type of a diffuse shader used by this material
+	 */
+	public DiffuseShader getDiffuseShader() {
+		return diffuseShader;
+	}
+	
+	/**
+	 * @return a copy of specular color
+	 */
+	public ColorRGBA getSpecularColor() {
+		return specularColor.clone();
+	}
+	
+	/**
+	 * @return an enum describing the type of a specular shader used by this material
+	 */
+	public SpecularShader getSpecularShader() {
+		return specularShader;
+	}
+	
+	/**
+	 * @return an ambient color used by the material
+	 */
+	public ColorRGBA getAmbientColor() {
+		return ambientColor;
+	}
+	
+	/**
+	 * @return the sihiness of this material
+	 */
+	public float getShininess() {
+		return shininess;
+	}
+	
+	/**
+	 * @return <b>true</b> if the material is shadeless and <b>false</b> otherwise
+	 */
+	public boolean isShadeless() {
+		return shadeless;
+	}
+	
+	/**
+	 * @return <b>true</b> if the material uses vertex color and <b>false</b> otherwise
+	 */
+	public boolean isVertexColor() {
+		return vertexColor;
+	}
+	
+	/**
+	 * @return <b>true</b> if the material is transparent and <b>false</b> otherwise
+	 */
+	public boolean isTransparent() {
+		return transparent;
+	}
+	
+	/**
+	 * @return <b>true</b> if the material uses tangents and <b>false</b> otherwise
+	 */
+	public boolean isvTangent() {
+		return vTangent;
+	}
+	
+	/**
+	 * @param texture
+	 *            the texture for which its mtex structure definition will be
+	 *            fetched
+	 * @return mtex structure of the current texture or <b>null</b> if none
+	 *         exists
+	 */
+	public Structure getMTex(Texture texture) {
+		return textureToMTexMap.get(texture);
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
new file mode 100644
index 0000000..a25b727
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
@@ -0,0 +1,588 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.materials;

+

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.material.MatParam;

+import com.jme3.material.MatParamTexture;

+import com.jme3.material.Material;

+import com.jme3.material.RenderState.BlendMode;

+import com.jme3.material.RenderState.FaceCullMode;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.FastMath;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.shader.VarType;

+import com.jme3.texture.Image;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.Type;

+import com.jme3.util.BufferUtils;

+import java.nio.ByteBuffer;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class MaterialHelper extends AbstractBlenderHelper {

+	private static final Logger					LOGGER					= Logger.getLogger(MaterialHelper.class.getName());

+	protected static final float				DEFAULT_SHININESS		= 20.0f;

+

+	public static final String					TEXTURE_TYPE_3D			= "Texture";

+	public static final String					TEXTURE_TYPE_COLOR		= "ColorMap";

+	public static final String					TEXTURE_TYPE_DIFFUSE	= "DiffuseMap";

+	public static final String					TEXTURE_TYPE_NORMAL		= "NormalMap";

+	public static final String					TEXTURE_TYPE_SPECULAR	= "SpecularMap";

+	public static final String					TEXTURE_TYPE_GLOW		= "GlowMap";

+	public static final String					TEXTURE_TYPE_ALPHA		= "AlphaMap";

+

+	public static final Integer					ALPHA_MASK_NONE			= Integer.valueOf(0);

+	public static final Integer					ALPHA_MASK_CIRCLE		= Integer.valueOf(1);

+	public static final Integer					ALPHA_MASK_CONE			= Integer.valueOf(2);

+	public static final Integer					ALPHA_MASK_HYPERBOLE	= Integer.valueOf(3);

+	protected final Map<Integer, IAlphaMask>	alphaMasks				= new HashMap<Integer, IAlphaMask>();

+

+	/**

+	 * The type of the material's diffuse shader.

+	 */

+	public static enum DiffuseShader {

+		LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL

+	}

+

+	/**

+	 * The type of the material's specular shader.

+	 */

+	public static enum SpecularShader {

+		COOKTORRENCE, PHONG, BLINN, TOON, WARDISO

+	}

+

+	/** Face cull mode. Should be excplicitly set before this helper is used. */

+	protected FaceCullMode	faceCullMode;

+

+	/**

+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender

+	 * versions.

+	 * 

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public MaterialHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, false);

+		// setting alpha masks

+		alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {

+			@Override

+			public void setImageSize(int width, int height) {}

+

+			@Override

+			public byte getAlpha(float x, float y) {

+				return (byte) 255;

+			}

+		});

+		alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {

+			private float	r;

+			private float[]	center;

+

+			@Override

+			public void setImageSize(int width, int height) {

+				r = Math.min(width, height) * 0.5f;

+				center = new float[] { width * 0.5f, height * 0.5f };

+			}

+

+			@Override

+			public byte getAlpha(float x, float y) {

+				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));

+				return (byte) (d >= r ? 0 : 255);

+			}

+		});

+		alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {

+			private float	r;

+			private float[]	center;

+

+			@Override

+			public void setImageSize(int width, int height) {

+				r = Math.min(width, height) * 0.5f;

+				center = new float[] { width * 0.5f, height * 0.5f };

+			}

+

+			@Override

+			public byte getAlpha(float x, float y) {

+				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));

+				return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);

+			}

+		});

+		alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {

+			private float	r;

+			private float[]	center;

+

+			@Override

+			public void setImageSize(int width, int height) {

+				r = Math.min(width, height) * 0.5f;

+				center = new float[] { width * 0.5f, height * 0.5f };

+			}

+

+			@Override

+			public byte getAlpha(float x, float y) {

+				float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;

+				return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);

+			}

+		});

+	}

+

+	/**

+	 * This method sets the face cull mode to be used with every loaded material.

+	 * 

+	 * @param faceCullMode

+	 *        the face cull mode

+	 */

+	public void setFaceCullMode(FaceCullMode faceCullMode) {

+		this.faceCullMode = faceCullMode;

+	}

+

+	/**

+	 * This method converts the material structure to jme Material.

+	 * @param structure

+	 *        structure with material data

+	 * @param blenderContext

+	 *        the blender context

+	 * @return jme material

+	 * @throws BlenderFileException

+	 *         an exception is throw when problems with blend file occur

+	 */

+	public Material toMaterial(Structure structure, BlenderContext blenderContext) throws BlenderFileException {

+		LOGGER.log(Level.INFO, "Loading material.");

+		if (structure == null) {

+			return blenderContext.getDefaultMaterial();

+		}

+		Material result = (Material) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+		if (result != null) {

+			return result;

+		}

+		

+		MaterialContext materialContext = new MaterialContext(structure, blenderContext);

+		LOGGER.log(Level.INFO, "Material's name: {0}", materialContext.name);

+		

+		if(materialContext.textures.size() > 1) {

+			LOGGER.log(Level.WARNING, "Attetion! Many textures found for material: {0}. Only the first of each supported mapping types will be used!", materialContext.name);

+		}

+		

+		// texture

+		Type colorTextureType = null;

+		Map<String, Texture> texturesMap = new HashMap<String, Texture>();

+		for(Entry<Number, Texture> textureEntry : materialContext.loadedTextures.entrySet()) {

+			int mapto = textureEntry.getKey().intValue();

+			Texture texture = textureEntry.getValue();

+			if ((mapto & MaterialContext.MTEX_COL) != 0) {

+				colorTextureType = texture.getType();

+				if (materialContext.shadeless) {

+					texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_COLOR, texture);

+				} else {

+					texturesMap.put(colorTextureType==Type.ThreeDimensional ? TEXTURE_TYPE_3D : TEXTURE_TYPE_DIFFUSE, texture);

+				}

+			}

+			if(texture.getType()==Type.TwoDimensional) {//so far only 2D textures can be mapped in other way than color

+				if ((mapto & MaterialContext.MTEX_NOR) != 0 && !materialContext.shadeless) {

+					//Structure mTex = materialContext.getMTex(texture);

+					//Texture normalMapTexture = textureHelper.convertToNormalMapTexture(texture, ((Number) mTex.getFieldValue("norfac")).floatValue());

+					//texturesMap.put(TEXTURE_TYPE_NORMAL, normalMapTexture);

+                                        texturesMap.put(TEXTURE_TYPE_NORMAL, texture);

+				}

+				if ((mapto & MaterialContext.MTEX_EMIT) != 0) {

+					texturesMap.put(TEXTURE_TYPE_GLOW, texture);

+				}

+				if ((mapto & MaterialContext.MTEX_SPEC) != 0 && !materialContext.shadeless) {

+					texturesMap.put(TEXTURE_TYPE_SPECULAR, texture);

+				}

+				if ((mapto & MaterialContext.MTEX_ALPHA) != 0 && !materialContext.shadeless) {

+					texturesMap.put(TEXTURE_TYPE_ALPHA, texture);

+				}

+			}

+		}

+		

+		//creating the material

+		if(colorTextureType==Type.ThreeDimensional) {

+			result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Texture3D/tex3D.j3md");

+		} else {

+			if (materialContext.shadeless) {

+				result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");

+                

+                if (!materialContext.transparent) {

+                    materialContext.diffuseColor.a = 1;

+                }

+                

+                result.setColor("Color", materialContext.diffuseColor);

+			} else {

+				result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");

+				result.setBoolean("UseMaterialColors", Boolean.TRUE);

+

+				// setting the colors

+				result.setBoolean("Minnaert", materialContext.diffuseShader == DiffuseShader.MINNAERT);

+				if (!materialContext.transparent) {

+					materialContext.diffuseColor.a = 1;

+				}

+				result.setColor("Diffuse", materialContext.diffuseColor);

+

+				result.setBoolean("WardIso", materialContext.specularShader == SpecularShader.WARDISO);

+				result.setColor("Specular", materialContext.specularColor);

+

+				result.setColor("Ambient", materialContext.ambientColor);

+				result.setFloat("Shininess", materialContext.shininess);

+			}

+			

+			if (materialContext.vertexColor) {

+				result.setBoolean(materialContext.shadeless ? "VertexColor" : "UseVertexColor", true);

+			}

+		}

+		

+		//applying textures

+		for(Entry<String, Texture> textureEntry : texturesMap.entrySet()) {

+			result.setTexture(textureEntry.getKey(), textureEntry.getValue());

+		}

+		

+		//applying other data

+		result.getAdditionalRenderState().setFaceCullMode(faceCullMode);

+		if (materialContext.transparent) {

+			result.setTransparent(true);

+			result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

+		}

+		

+		result.setName(materialContext.getName());

+		blenderContext.setMaterialContext(result, materialContext);

+		blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);

+		return result;

+	}

+	

+	/**

+	 * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but

+	 * returned itself.

+	 * 

+	 * @param material

+	 *        a material to be cloned without textures

+	 * @param imageType

+	 *        type of image defined by blender; the constants are defined in TextureHelper

+	 * @return material without textures of a specified type

+	 */

+	public Material getNonTexturedMaterial(Material material, int imageType) {

+		String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA };

+		Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);

+		for (String textureParamName : textureParamNames) {

+			MatParamTexture matParamTexture = material.getTextureParam(textureParamName);

+			if (matParamTexture != null) {

+				textures.put(textureParamName, matParamTexture.getTextureValue());

+			}

+		}

+		if (textures.isEmpty()) {

+			return material;

+		} else {

+			// clear all textures first so that wo de not waste resources cloning them

+			for (Entry<String, Texture> textureParamName : textures.entrySet()) {

+				String name = textureParamName.getValue().getName();

+				try {

+					int type = Integer.parseInt(name);

+					if (type == imageType) {

+						material.clearParam(textureParamName.getKey());

+					}

+				} catch (NumberFormatException e) {

+					LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name);

+				}

+			}

+			Material result = material.clone();

+			// put the textures back in place

+			for (Entry<String, Texture> textureEntry : textures.entrySet()) {

+				material.setTexture(textureEntry.getKey(), textureEntry.getValue());

+			}

+			return result;

+		}

+	}

+

+	/**

+	 * This method converts the given material into particles-usable material.

+	 * The texture and glow color are being copied.

+	 * The method assumes it receives the Lighting type of material.

+	 * @param material

+	 *        the source material

+	 * @param blenderContext

+	 *        the blender context

+	 * @return material converted into particles-usable material

+	 */

+	public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) {

+		Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");

+

+		// copying texture

+		MatParam diffuseMap = material.getParam("DiffuseMap");

+		if (diffuseMap != null) {

+			Texture texture = ((Texture) diffuseMap.getValue()).clone();

+

+			// applying alpha mask to the texture

+			Image image = texture.getImage();

+			ByteBuffer sourceBB = image.getData(0);

+			sourceBB.rewind();

+			int w = image.getWidth();

+			int h = image.getHeight();

+			ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);

+			IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);

+			iAlphaMask.setImageSize(w, h);

+

+			for (int x = 0; x < w; ++x) {

+				for (int y = 0; y < h; ++y) {

+					bb.put(sourceBB.get());

+					bb.put(sourceBB.get());

+					bb.put(sourceBB.get());

+					bb.put(iAlphaMask.getAlpha(x, y));

+				}

+			}

+

+			image = new Image(Format.RGBA8, w, h, bb);

+			texture.setImage(image);

+

+			result.setTextureParam("Texture", VarType.Texture2D, texture);

+		}

+

+		// copying glow color

+		MatParam glowColor = material.getParam("GlowColor");

+		if (glowColor != null) {

+			ColorRGBA color = (ColorRGBA) glowColor.getValue();

+			result.setParam("GlowColor", VarType.Vector3, color);

+		}

+		return result;

+	}

+

+	/**

+	 * This method indicates if the material has any kind of texture.

+	 * 

+	 * @param material

+	 *        the material

+	 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise

+	 */

+	public boolean hasTexture(Material material) {

+		if (material != null) {

+			if (material.getTextureParam(TEXTURE_TYPE_3D) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) {

+				return true;

+			}

+			if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) {

+				return true;

+			}

+		}

+		return false;

+	}

+

+	/**

+	 * This method indicates if the material has a texture of a specified type.

+	 * 

+	 * @param material

+	 *        the material

+	 * @param textureType

+	 *        the type of the texture

+	 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise

+	 */

+	public boolean hasTexture(Material material, String textureType) {

+		if (material != null) {

+			return material.getTextureParam(textureType) != null;

+		}

+		return false;

+	}

+

+	/**

+	 * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or

+	 * curve) but needs to have 'mat' field/

+	 * 

+	 * @param structureWithMaterials

+	 *        the structure containing the mesh data

+	 * @param blenderContext

+	 *        the blender context

+	 * @return a list of vertices colors, each color belongs to a single vertex

+	 * @throws BlenderFileException

+	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted

+	 */

+	public Material[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException {

+		Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");

+		Material[] materials = null;

+		if (ppMaterials.isNotNull()) {

+			List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());

+			if (materialStructures != null && materialStructures.size() > 0) {

+				MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+				materials = new Material[materialStructures.size()];

+				int i = 0;

+				for (Structure s : materialStructures) {

+					Material material = (Material) blenderContext.getLoadedFeature(s.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+					if (material == null) {

+						material = materialHelper.toMaterial(s, blenderContext);

+					}

+					materials[i++] = material;

+				}

+			}

+		}

+		return materials;

+	}

+

+	/**

+	 * This method converts rgb values to hsv values.

+	 * 

+	 * @param r

+	 *        red value of the color

+         * @param g

+         *        green value of the color

+         * @param b

+         *        blue value of the color

+	 * @param hsv

+	 *        hsv values of a color (this table contains the result of the transformation)

+	 */

+	public void rgbToHsv(float r, float g, float b, float[] hsv) {

+		float cmax = r;

+		float cmin = r;

+		cmax = g > cmax ? g : cmax;

+		cmin = g < cmin ? g : cmin;

+		cmax = b > cmax ? b : cmax;

+		cmin = b < cmin ? b : cmin;

+

+		hsv[2] = cmax; /* value */

+		if (cmax != 0.0) {

+			hsv[1] = (cmax - cmin) / cmax;

+		} else {

+			hsv[1] = 0.0f;

+			hsv[0] = 0.0f;

+		}

+		if (hsv[1] == 0.0) {

+			hsv[0] = -1.0f;

+		} else {

+			float cdelta = cmax - cmin;

+			float rc = (cmax - r) / cdelta;

+			float gc = (cmax - g) / cdelta;

+			float bc = (cmax - b) / cdelta;

+			if (r == cmax) {

+				hsv[0] = bc - gc;

+			} else if (g == cmax) {

+				hsv[0] = 2.0f + rc - bc;

+			} else {

+				hsv[0] = 4.0f + gc - rc;

+			}

+			hsv[0] *= 60.0f;

+			if (hsv[0] < 0.0f) {

+				hsv[0] += 360.0f;

+			}

+		}

+

+		hsv[0] /= 360.0f;

+		if (hsv[0] < 0.0f) {

+			hsv[0] = 0.0f;

+		}

+	}

+

+	/**

+	 * This method converts rgb values to hsv values.

+	 * 

+	 * @param h

+	 *        hue

+	 * @param s

+	 *        saturation

+	 * @param v

+	 *        value

+	 * @param rgb

+	 *        rgb result vector (should have 3 elements)

+	 */

+	public void hsvToRgb(float h, float s, float v, float[] rgb) {

+		h *= 360.0f;

+		if (s == 0.0) {

+			rgb[0] = rgb[1] = rgb[2] = v;

+		} else {

+			if (h == 360) {

+				h = 0;

+			} else {

+				h /= 60;

+			}

+			int i = (int) Math.floor(h);

+			float f = h - i;

+			float p = v * (1.0f - s);

+			float q = v * (1.0f - s * f);

+			float t = v * (1.0f - s * (1.0f - f));

+			switch (i) {

+				case 0:

+					rgb[0] = v;

+					rgb[1] = t;

+					rgb[2] = p;

+					break;

+				case 1:

+					rgb[0] = q;

+					rgb[1] = v;

+					rgb[2] = p;

+					break;

+				case 2:

+					rgb[0] = p;

+					rgb[1] = v;

+					rgb[2] = t;

+					break;

+				case 3:

+					rgb[0] = p;

+					rgb[1] = q;

+					rgb[2] = v;

+					break;

+				case 4:

+					rgb[0] = t;

+					rgb[1] = p;

+					rgb[2] = v;

+					break;

+				case 5:

+					rgb[0] = v;

+					rgb[1] = p;

+					rgb[2] = q;

+					break;

+			}

+		}

+	}

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java
new file mode 100644
index 0000000..de43db7
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java
@@ -0,0 +1,105 @@
+package com.jme3.scene.plugins.blender.meshes;

+

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.VertexBuffer;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+/**

+ * Class that holds information about the mesh.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class MeshContext {

+	/** The mesh stored here as a list of geometries. */

+	private List<Geometry> mesh;

+	/** Vertex list that is referenced by all the geometries. */

+	private List<Vector3f> vertexList;

+	/** The vertex reference map. */

+	private Map<Integer, List<Integer>> vertexReferenceMap;

+	/** The UV-coordinates for each of the geometries. */

+	private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>();

+

+	/**

+	 * This method returns the referenced mesh.

+	 * 

+	 * @return the referenced mesh

+	 */

+	public List<Geometry> getMesh() {

+		return mesh;

+	}

+

+	/**

+	 * This method sets the referenced mesh.

+	 * 

+	 * @param mesh

+	 *            the referenced mesh

+	 */

+	public void setMesh(List<Geometry> mesh) {

+		this.mesh = mesh;

+	}

+

+	/**

+	 * This method returns the vertex list.

+	 * 

+	 * @return the vertex list

+	 */

+	public List<Vector3f> getVertexList() {

+		return vertexList;

+	}

+

+	/**

+	 * This method sets the vertex list.

+	 * 

+	 * @param vertexList

+	 *            the vertex list

+	 */

+	public void setVertexList(List<Vector3f> vertexList) {

+		this.vertexList = vertexList;

+	}

+

+	/**

+	 * This method returns the vertex reference map.

+	 * 

+	 * @return the vertex reference map

+	 */

+	public Map<Integer, List<Integer>> getVertexReferenceMap() {

+		return vertexReferenceMap;

+	}

+

+	/**

+	 * This method sets the vertex reference map.

+	 * 

+	 * @param vertexReferenceMap

+	 *            the vertex reference map

+	 */

+	public void setVertexReferenceMap(

+			Map<Integer, List<Integer>> vertexReferenceMap) {

+		this.vertexReferenceMap = vertexReferenceMap;

+	}

+

+	/**

+	 * This method adds the mesh's UV-coordinates.

+	 * 

+	 * @param geometry

+	 *            the mesh that has the UV-coordinates

+	 * @param vertexBuffer

+	 *            the mesh's UV-coordinates

+	 */

+	public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) {

+		uvCoordinates.put(geometry, vertexBuffer);

+	}

+

+	/**

+	 * This method returns the mesh's UV-coordinates.

+	 * 

+	 * @param geometry

+	 *            the mesh

+	 * @return the mesh's UV-coordinates

+	 */

+	public VertexBuffer getUVCoordinates(Geometry geometry) {

+		return uvCoordinates.get(geometry);

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
new file mode 100644
index 0000000..484e942
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
@@ -0,0 +1,535 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.meshes;

+

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.material.Material;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.queue.RenderQueue.Bucket;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Format;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.VertexBuffer.Usage;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.materials.MaterialContext;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+import com.jme3.scene.plugins.blender.objects.Properties;

+import com.jme3.scene.plugins.blender.textures.TextureHelper;

+import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator;

+import com.jme3.texture.Texture;

+import com.jme3.util.BufferUtils;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.*;

+import java.util.Map.Entry;

+

+/**

+ * A class that is used in mesh calculations.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class MeshHelper extends AbstractBlenderHelper {

+

+    /**

+     * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender

+     * versions.

+     * 

+     * @param blenderVersion

+     *            the version read from the blend file

+     * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+     */

+    public MeshHelper(String blenderVersion, boolean fixUpAxis) {

+        super(blenderVersion,fixUpAxis);

+    }

+

+    /**

+     * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.

+     * 

+     * @param structure

+     *            the structure we read the mesh from

+     * @return the mesh feature

+     * @throws BlenderFileException

+     */

+    @SuppressWarnings("unchecked")

+    public List<Geometry> toMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException {

+        List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(),

+                LoadedFeatureDataType.LOADED_FEATURE);

+        if (geometries != null) {

+            List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());

+            for (Geometry geometry : geometries) {

+                copiedGeometries.add(geometry.clone());

+            }

+            return copiedGeometries;

+        }

+

+        // helpers

+        TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);

+

+        // reading mesh data

+        String name = structure.getName();

+        MeshContext meshContext = new MeshContext();

+

+        // reading vertices

+        Vector3f[] vertices = this.getVertices(structure, blenderContext);

+        int verticesAmount = vertices.length;

+

+        // vertices Colors

+        List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext);

+

+        // reading faces

+        // the following map sorts faces by material number (because in jme Mesh can have only one material)

+        Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>();

+        Pointer pMFace = (Pointer) structure.getFieldValue("mface");

+        List<Structure> mFaces = null;

+        if (pMFace.isNotNull()) {

+            mFaces = pMFace.fetchData(blenderContext.getInputStream());

+            if (mFaces == null || mFaces.size() == 0) {

+                return new ArrayList<Geometry>(0);

+            }

+        } else{

+        	mFaces = new ArrayList<Structure>(0);

+        }

+

+        Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");

+        List<Vector2f> uvCoordinates = null;

+        List<Structure> mtFaces = null;

+

+        if (pMTFace.isNotNull()) {

+            mtFaces = pMTFace.fetchData(blenderContext.getInputStream());

+            int facesAmount = ((Number) structure.getFieldValue("totface")).intValue();

+            if (mtFaces.size() != facesAmount) {

+                throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");

+            }

+            uvCoordinates = new ArrayList<Vector2f>();

+        }

+

+        // normalMap merges normals of faces that will be rendered smooth

+        Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount);

+

+        List<Vector3f> normalList = new ArrayList<Vector3f>();

+        List<Vector3f> vertexList = new ArrayList<Vector3f>();

+        // indicates if the material with the specified number should have a texture attached

+        Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();

+        // this map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'

+        // positions (it simply tells which vertex is referenced where in the result list)

+        Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount);

+        int vertexColorIndex = 0;

+        for (int i = 0; i < mFaces.size(); ++i) {

+            Structure mFace = mFaces.get(i);

+            boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;

+            DynamicArray<Number> uvs = null;

+            boolean materialWithoutTextures = false;

+            Pointer pImage = null;

+            if (mtFaces != null) {

+                Structure mtFace = mtFaces.get(i);

+                pImage = (Pointer) mtFace.getFieldValue("tpage");

+                materialWithoutTextures = pImage.isNull();

+                // uvs always must be added wheater we have texture or not

+                uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");

+                uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));

+                uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()));

+                uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));

+            }

+            int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();

+            Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);

+            List<Integer> indexList = meshesMap.get(materialNumber);

+            if (indexList == null) {

+                indexList = new ArrayList<Integer>();

+                meshesMap.put(materialNumber, indexList);

+            }

+

+            // attaching image to texture (face can have UV's and image whlie its material may have no texture attached)

+            if (pImage != null && pImage.isNotNull() && !materialNumberToTexture.containsKey(materialNumber)) {

+                Texture texture = textureHelper.getTextureFromImage(pImage.fetchData(blenderContext.getInputStream()).get(0),

+                        blenderContext);

+                if (texture != null) {

+                    materialNumberToTexture.put(materialNumber, texture);

+                }

+            }

+

+            int v1 = ((Number) mFace.getFieldValue("v1")).intValue();

+            int v2 = ((Number) mFace.getFieldValue("v2")).intValue();

+            int v3 = ((Number) mFace.getFieldValue("v3")).intValue();

+            int v4 = ((Number) mFace.getFieldValue("v4")).intValue();

+

+            Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]);

+            this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);

+            normalList.add(normalMap.get(vertices[v1]));

+            normalList.add(normalMap.get(vertices[v2]));

+            normalList.add(normalMap.get(vertices[v3]));

+

+            this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);

+            indexList.add(vertexList.size());

+            vertexList.add(vertices[v1]);

+

+            this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap);

+            indexList.add(vertexList.size());

+            vertexList.add(vertices[v2]);

+

+            this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);

+            indexList.add(vertexList.size());

+            vertexList.add(vertices[v3]);

+

+            if (v4 > 0) {

+                if (uvs != null) {

+                    uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));

+                    uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));

+                    uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()));

+                }

+                this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);

+                indexList.add(vertexList.size());

+                vertexList.add(vertices[v1]);

+

+                this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);

+                indexList.add(vertexList.size());

+                vertexList.add(vertices[v3]);

+

+                this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap);

+                indexList.add(vertexList.size());

+                vertexList.add(vertices[v4]);

+

+                this.addNormal(n, normalMap, smooth, vertices[v4]);

+                normalList.add(normalMap.get(vertices[v1]));

+                normalList.add(normalMap.get(vertices[v3]));

+                normalList.add(normalMap.get(vertices[v4]));

+

+                if (verticesColors != null) {

+                    verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex));

+                    verticesColors.add(vertexColorIndex + 4, verticesColors.get(vertexColorIndex + 2));

+                }

+                vertexColorIndex += 6;

+            } else {

+                if (verticesColors != null) {

+                    verticesColors.remove(vertexColorIndex + 3);

+                    vertexColorIndex += 3;

+                }

+            }

+        }

+        meshContext.setVertexList(vertexList);

+        meshContext.setVertexReferenceMap(vertexReferenceMap);

+

+        Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);

+

+        // reading vertices groups (from the parent)

+        Structure parent = blenderContext.peekParent();

+        Structure defbase = (Structure) parent.getFieldValue("defbase");

+        List<Structure> defs = defbase.evaluateListBase(blenderContext);

+        String[] verticesGroups = new String[defs.size()];

+        int defIndex = 0;

+        for (Structure def : defs) {

+            verticesGroups[defIndex++] = def.getFieldValue("name").toString();

+        }

+

+        // reading materials

+        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+        Material[] materials = null;

+        Material[] nonTexturedMaterials = null;

+        if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {

+            materials = materialHelper.getMaterials(structure, blenderContext);

+            nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed

+        }

+

+        // creating the result meshes

+        geometries = new ArrayList<Geometry>(meshesMap.size());

+

+        VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);

+        verticesBuffer.setupData(Usage.Stream, 3, Format.Float,

+                BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()])));

+

+        // initial vertex position (used with animation)

+        VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);

+        verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));

+

+        VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);

+        normalsBuffer.setupData(Usage.Stream, 3, Format.Float, BufferUtils.createFloatBuffer(normals));

+

+        // initial normals position (used with animation)

+        VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);

+        normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));

+

+        VertexBuffer uvCoordsBuffer = null;

+        if (uvCoordinates != null) {

+            uvCoordsBuffer = new VertexBuffer(Type.TexCoord);

+            uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,

+                    BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));

+        }

+

+        //reading custom properties

+        Properties properties = this.loadProperties(structure, blenderContext);

+

+        // generating meshes

+        //FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);

+        ByteBuffer verticesColorsBuffer = createByteBuffer(verticesColors);

+        for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {

+            Mesh mesh = new Mesh();

+

+            // creating vertices indices for this mesh

+            List<Integer> indexList = meshEntry.getValue();

+            if(verticesAmount < Short.MAX_VALUE * 2) {

+            	short[] indices = new short[indexList.size()];

+                for (int i = 0; i < indexList.size(); ++i) {

+                    indices[i] = indexList.get(i).shortValue();

+                }

+                mesh.setBuffer(Type.Index, 1, indices);

+            } else {

+            	int[] indices = new int[indexList.size()];

+                for (int i = 0; i < indexList.size(); ++i) {

+                    indices[i] = indexList.get(i).intValue();

+                }

+                mesh.setBuffer(Type.Index, 1, indices);

+            }

+            

+            mesh.setBuffer(verticesBuffer);

+            mesh.setBuffer(verticesBind);

+

+            // setting vertices colors

+            if (verticesColorsBuffer != null) {

+                mesh.setBuffer(Type.Color, 4, verticesColorsBuffer);

+                mesh.getBuffer(Type.Color).setNormalized(true);

+            }

+

+            // setting faces' normals

+            mesh.setBuffer(normalsBuffer);

+            mesh.setBuffer(normalsBind);

+

+            // creating the result

+            Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);

+            if (materials != null) {

+                int materialNumber = meshEntry.getKey().intValue();

+                Material material;

+                if (materialNumber >= 0) {

+                    material = materials[materialNumber];

+                    if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) {

+                        if (material.getMaterialDef().getAssetName().contains("Lighting")) {

+                            if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) {

+                                material = material.clone();

+                                material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE,

+                                        materialNumberToTexture.get(Integer.valueOf(materialNumber)));

+                            }

+                        } else {

+                            if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) {

+                                material = material.clone();

+                                material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR,

+                                        materialNumberToTexture.get(Integer.valueOf(materialNumber)));

+                            }

+                        }

+                    }

+                } else {

+                    materialNumber = -1 * (materialNumber + 1);

+                    if (nonTexturedMaterials[materialNumber] == null) {

+                        nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber],

+                                TextureHelper.TEX_IMAGE);

+                    }

+                    material = nonTexturedMaterials[materialNumber];

+                }

+                geometry.setMaterial(material);

+                if (material.isTransparent()) {

+                    geometry.setQueueBucket(Bucket.Transparent);

+                }

+            } else {

+                geometry.setMaterial(blenderContext.getDefaultMaterial());

+            }

+            if (properties != null && properties.getValue() != null) {

+                geometry.setUserData("properties", properties);

+            }

+            geometries.add(geometry);

+        }

+

+        //applying uvCoordinates for all the meshes

+        if (uvCoordsBuffer != null) {

+            for (Geometry geom : geometries) {

+                geom.getMesh().setBuffer(uvCoordsBuffer);

+            }

+        } else {

+            Map<Material, List<Geometry>> materialMap = new HashMap<Material, List<Geometry>>();

+            for (Geometry geom : geometries) {

+                Material material = geom.getMaterial();

+                List<Geometry> geomsWithCommonMaterial = materialMap.get(material);

+                if (geomsWithCommonMaterial == null) {

+                    geomsWithCommonMaterial = new ArrayList<Geometry>();

+                    materialMap.put(material, geomsWithCommonMaterial);

+                }

+                geomsWithCommonMaterial.add(geom);

+

+            }

+            for (Entry<Material, List<Geometry>> entry : materialMap.entrySet()) {

+                MaterialContext materialContext = blenderContext.getMaterialContext(entry.getKey());

+                if (materialContext != null && materialContext.getTexturesCount() > 0) {

+                    VertexBuffer coords = UVCoordinatesGenerator.generateUVCoordinates(materialContext.getUvCoordinatesType(),

+                            materialContext.getProjectionType(), materialContext.getTextureDimension(),

+                            materialContext.getProjection(0), entry.getValue());

+                    //setting the coordinates inside the mesh context

+                    for (Geometry geometry : entry.getValue()) {

+                        meshContext.addUVCoordinates(geometry, coords);

+                    }

+                }

+            }

+        }

+        

+        // if there are multiple materials used, extract the shared

+        // vertex data

+        if (geometries.size() > 1){

+            // extract from itself

+            for (Geometry geom : geometries){

+                geom.getMesh().extractVertexData(geom.getMesh());

+            }

+        }

+

+        blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);

+        blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);

+        return geometries;

+    }

+

+    /**

+     * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.

+     * 

+     * @param normalToAdd

+     *            a normal to be added

+     * @param normalMap

+     *            merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector

+     * @param smooth

+     *            the variable that indicates wheather to merge normals (creating the smooth mesh) or not

+     * @param vertices

+     *            a list of vertices read from the blender file

+     */

+    public void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {

+        for (Vector3f v : vertices) {

+            Vector3f n = normalMap.get(v);

+            if (!smooth || n == null) {

+                normalMap.put(v, normalToAdd.clone());

+            } else {

+                n.addLocal(normalToAdd).normalizeLocal();

+            }

+        }

+    }

+

+    /**

+     * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created

+     * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key

+     * - the reference indices list.

+     * 

+     * @param basicVertexIndex

+     *            the index of the vertex from its basic table

+     * @param resultIndex

+     *            the index of the vertex in its result vertex list

+     * @param vertexReferenceMap

+     *            the reference map

+     */

+    protected void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {

+        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));

+        if (referenceList == null) {

+            referenceList = new ArrayList<Integer>();

+            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);

+        }

+        referenceList.add(Integer.valueOf(resultIndex));

+    }

+

+    /**

+     * This method returns the vertices colors. Each vertex is stored in byte[4] array.

+     * 

+     * @param meshStructure

+     *            the structure containing the mesh data

+     * @param blenderContext

+     *            the blender context

+     * @return a list of vertices colors, each color belongs to a single vertex

+     * @throws BlenderFileException

+     *             this exception is thrown when the blend file structure is somehow invalid or corrupted

+     */

+    public List<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {

+        Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol");

+        List<byte[]> verticesColors = null;

+        List<Structure> mCol = null;

+        if (pMCol.isNotNull()) {

+            verticesColors = new LinkedList<byte[]>();

+            mCol = pMCol.fetchData(blenderContext.getInputStream());

+            for (Structure color : mCol) {

+                byte r = ((Number)color.getFieldValue("r")).byteValue();

+                byte g = ((Number)color.getFieldValue("g")).byteValue();

+                byte b = ((Number)color.getFieldValue("b")).byteValue();

+                byte a = ((Number)color.getFieldValue("a")).byteValue();

+                verticesColors.add(new byte[]{b, g, r, a});

+            }

+        }

+        return verticesColors;

+    }

+

+    /**

+     * This method returns the vertices.

+     * 

+     * @param meshStructure

+     *            the structure containing the mesh data

+     * @param blenderContext

+     *            the blender context

+     * @return a list of vertices colors, each color belongs to a single vertex

+     * @throws BlenderFileException

+     *             this exception is thrown when the blend file structure is somehow invalid or corrupted

+     */

+    @SuppressWarnings("unchecked")

+    private Vector3f[] getVertices(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {

+        int verticesAmount = ((Number) meshStructure.getFieldValue("totvert")).intValue();

+        Vector3f[] vertices = new Vector3f[verticesAmount];

+        if (verticesAmount == 0) {

+            return vertices;

+        }

+

+        Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");

+        List<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream());

+        if(this.fixUpAxis) {

+        	for (int i = 0; i < verticesAmount; ++i) {

+                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");

+                vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());

+            }

+        } else {

+        	for (int i = 0; i < verticesAmount; ++i) {

+                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");

+                vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());

+            }

+        }

+        return vertices;

+    }

+

+    @Override

+    public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+        return true;

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
new file mode 100644
index 0000000..c15e91f
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
@@ -0,0 +1,393 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.Animation;

+import com.jme3.animation.Bone;

+import com.jme3.animation.BoneTrack;

+import com.jme3.animation.Skeleton;

+import com.jme3.animation.SkeletonControl;

+import com.jme3.math.Matrix4f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Node;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Format;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.VertexBuffer.Usage;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.animations.ArmatureHelper;

+import com.jme3.scene.plugins.blender.constraints.Constraint;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.meshes.MeshContext;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+import com.jme3.scene.plugins.ogre.AnimData;

+import com.jme3.util.BufferUtils;

+

+/**

+ * This modifier allows to add bone animation to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ArmatureModifier extends Modifier {

+	private static final Logger	LOGGER						= Logger.getLogger(ArmatureModifier.class.getName());

+	private static final int	MAXIMUM_WEIGHTS_PER_VERTEX	= 4;

+	// @Marcin it was an Ogre limitation, but as long as we use a MaxNumWeight

+	// variable in mesh,

+	// i guess this limitation has no sense for the blender loader...so i guess

+	// it's up to you. You'll have to deternine the max weight according to the

+	// provided blend file

+	// I added a check to avoid crash when loading a model that has more than 4

+	// weight per vertex on line 258

+	// If you decide to remove this limitation, remove this code.

+	// Rémy

+

+	/** Loaded animation data. */

+	private AnimData			animData;

+	/** Old memory address of the mesh that will have the skeleton applied. */

+	private Long				meshOMA;

+	/**

+	 * The maxiumum amount of bone groups applied to a single vertex (max =

+	 * MAXIMUM_WEIGHTS_PER_VERTEX).

+	 */

+	private int					boneGroups;

+	/** The weights of vertices. */

+	private VertexBuffer		verticesWeights;

+	/** The indexes of bones applied to vertices. */

+	private VertexBuffer		verticesWeightsIndices;

+

+	/**

+	 * This constructor reads animation data from the object structore. The

+	 * stored data is the AnimData and additional data is armature's OMA.

+	 * 

+	 * @param objectStructure

+	 *            the structure of the object

+	 * @param modifierStructure

+	 *            the structure of the modifier

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);

+		Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert

+																		// =

+																		// DeformVERTices

+

+		// if pDvert==null then there are not vertex groups and no need to load

+		// skeleton (untill bone envelopes are supported)

+		if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) {

+			Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");

+			if (pArmatureObject.isNotNull()) {

+				ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);

+

+				Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0);

+

+				// load skeleton

+				Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);

+

+				Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0);

+				List<Structure> chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext);

+

+				Map<Long, Structure> bonesPoseChannels = new HashMap<Long, Structure>(chanbase.size());

+				for (Structure poseChannel : chanbase) {

+					Pointer pBone = (Pointer) poseChannel.getFieldValue("bone");

+					bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel);

+				}

+

+				ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+				Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true);

+				Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal();

+				Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix);

+

+				List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext);

+				List<Bone> bonesList = new ArrayList<Bone>();

+				for (int i = 0; i < bonebase.size(); ++i) {

+					armatureHelper.buildBones(bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext);

+				}

+				bonesList.add(0, new Bone(""));

+				Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);

+				Skeleton skeleton = new Skeleton(bones);

+

+				// read mesh indexes

+				this.meshOMA = meshStructure.getOldMemoryAddress();

+				this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, blenderContext);

+

+				// read animations

+				ArrayList<Animation> animations = new ArrayList<Animation>();

+				List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));

+				if (actionHeaders != null) {// it may happen that the model has

+											// armature with no actions

+					for (FileBlockHeader header : actionHeaders) {

+						Structure actionStructure = header.getStructure(blenderContext);

+						String actionName = actionStructure.getName();

+

+						BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);

+						if(tracks != null && tracks.length > 0) {

+							// determining the animation time

+							float maximumTrackLength = 0;

+							for (BoneTrack track : tracks) {

+								float length = track.getLength();

+								if (length > maximumTrackLength) {

+									maximumTrackLength = length;

+								}

+							}

+

+							Animation boneAnimation = new Animation(actionName, maximumTrackLength);

+							boneAnimation.setTracks(tracks);

+							animations.add(boneAnimation);

+						}

+					}

+				}

+				animData = new AnimData(skeleton, animations);

+

+				// store the animation data for each bone

+				for (Bone bone : bones) {

+					Long boneOma = armatureHelper.getBoneOMA(bone);

+					if (boneOma != null) {

+						blenderContext.setAnimData(boneOma, animData);

+					}

+				}

+			}

+		}

+	}

+

+	@Override

+	@SuppressWarnings("unchecked")

+	public Node apply(Node node, BlenderContext blenderContext) {

+		if (invalid) {

+			LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());

+		}// if invalid, animData will be null

+		if (animData == null) {

+			return node;

+		}

+

+		// setting weights for bones

+		List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(this.meshOMA, LoadedFeatureDataType.LOADED_FEATURE);

+		for (Geometry geom : geomList) {

+			Mesh mesh = geom.getMesh();

+			if (this.verticesWeights != null) {

+				mesh.setMaxNumWeights(this.boneGroups);

+				mesh.setBuffer(this.verticesWeights);

+				mesh.setBuffer(this.verticesWeightsIndices);

+			}

+		}

+

+		// applying constraints to Bones

+		ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);

+		for (int i = 0; i < animData.skeleton.getBoneCount(); ++i) {

+			Long boneOMA = armatureHelper.getBoneOMA(animData.skeleton.getBone(i));

+			List<Constraint> constraints = blenderContext.getConstraints(boneOMA);

+			if (constraints != null && constraints.size() > 0) {

+				for (Constraint constraint : constraints) {

+					constraint.bake();

+				}

+			}

+		}

+

+		// applying animations

+		AnimControl control = new AnimControl(animData.skeleton);

+		ArrayList<Animation> animList = animData.anims;

+		if (animList != null && animList.size() > 0) {

+			HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size());

+			for (int i = 0; i < animList.size(); ++i) {

+				Animation animation = animList.get(i);

+				anims.put(animation.getName(), animation);

+			}

+			control.setAnimations(anims);

+		}

+		node.addControl(control);

+		node.addControl(new SkeletonControl(animData.skeleton));

+

+		return node;

+	}

+

+	/**

+	 * This method reads mesh indexes

+	 * 

+	 * @param objectStructure

+	 *            structure of the object that has the armature modifier applied

+	 * @param meshStructure

+	 *            the structure of the object's mesh

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blend file structure is

+	 *             somehow invalid or corrupted

+	 */

+	private void readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {

+		ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);

+		Structure defBase = (Structure) objectStructure.getFieldValue("defbase");

+		Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);

+

+		int[] bonesGroups = new int[] { 0 };

+		MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());

+

+		VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexList().size(), bonesGroups, meshContext.getVertexReferenceMap(), groupToBoneIndexMap, blenderContext);

+		this.verticesWeights = boneWeightsAndIndex[0];

+		this.verticesWeightsIndices = boneWeightsAndIndex[1];

+		this.boneGroups = bonesGroups[0];

+	}

+

+	/**

+	 * This method returns an array of size 2. The first element is a vertex

+	 * buffer holding bone weights for every vertex in the model. The second

+	 * element is a vertex buffer holding bone indices for vertices (the indices

+	 * of bones the vertices are assigned to).

+	 * 

+	 * @param meshStructure

+	 *            the mesh structure object

+	 * @param vertexListSize

+	 *            a number of vertices in the model

+	 * @param bonesGroups

+	 *            this is an output parameter, it should be a one-sized array;

+	 *            the maximum amount of weights per vertex (up to

+	 *            MAXIMUM_WEIGHTS_PER_VERTEX) is stored there

+	 * @param vertexReferenceMap

+	 *            this reference map allows to map the original vertices read

+	 *            from blender to vertices that are really in the model; one

+	 *            vertex may appear several times in the result model

+	 * @param groupToBoneIndexMap

+	 *            this object maps the group index (to which a vertices in

+	 *            blender belong) to bone index of the model

+	 * @param blenderContext

+	 *            the blender context

+	 * @return arrays of vertices weights and their bone indices and (as an

+	 *         output parameter) the maximum amount of weights for a vertex

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blend file structure is

+	 *             somehow invalid or corrupted

+	 */

+	private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)

+			throws BlenderFileException {

+		Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices

+		FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);

+		ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);

+		if (pDvert.isNotNull()) {// assigning weights and bone indices

+			List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per

+																						// vertex in blender)

+			int vertexIndex = 0;

+			for (Structure dvert : dverts) {

+				int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex

+																						// (max. 4 in JME)

+				Pointer pDW = (Pointer) dvert.getFieldValue("dw");

+				List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here

+				if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :)

+					int weightIndex = 0;

+					List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());

+					for (Structure deformWeight : dw) {

+						Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());

+

+						// Remove this code if 4 weights limitation is removed

+						if (weightIndex == 4) {

+							LOGGER.log(Level.WARNING, "{0} has more than 4 weight on bone index {1}", new Object[] { meshStructure.getName(), boneIndex });

+							break;

+						}

+

+						// null here means that we came accross group that has no bone attached to

+						if (boneIndex != null) {

+							float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();

+							if (weight == 0.0f) {

+								weight = 1;

+								boneIndex = Integer.valueOf(0);

+							}

+							// we apply the weight to all referenced vertices

+							for (Integer index : vertexIndices) {

+								weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);

+								indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());

+							}

+						}

+						++weightIndex;

+					}

+				} else {

+					for (Integer index : vertexIndices) {

+						weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);

+						indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);

+					}

+				}

+				++vertexIndex;

+			}

+		} else {

+			// always bind all vertices to 0-indexed bone

+			// this bone makes the model look normally if vertices have no bone

+			// assigned

+			// and it is used in object animation, so if we come accross object

+			// animation

+			// we can use the 0-indexed bone for this

+			for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {

+				// we apply the weight to all referenced vertices

+				for (Integer index : vertexIndexList) {

+					weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);

+					indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);

+				}

+			}

+		}

+

+		bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);

+		VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);

+		verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);

+

+		VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);

+		verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);

+		return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };

+	}

+

+	/**

+	 * Normalizes weights if needed and finds largest amount of weights used for

+	 * all vertices in the buffer.

+	 * 

+	 * @param vertCount

+	 *            amount of vertices

+	 * @param weightsFloatData

+	 *            weights for vertices

+	 */

+	private int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {

+		int maxWeightsPerVert = 0;

+		weightsFloatData.rewind();

+		for (int v = 0; v < vertCount; ++v) {

+			float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();

+

+			if (w3 != 0) {

+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);

+			} else if (w2 != 0) {

+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);

+			} else if (w1 != 0) {

+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);

+			} else if (w0 != 0) {

+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);

+			}

+

+			float sum = w0 + w1 + w2 + w3;

+			if (sum != 1f && sum != 0.0f) {

+				weightsFloatData.position(weightsFloatData.position() - 4);

+				// compute new vals based on sum

+				float sumToB = 1f / sum;

+				weightsFloatData.put(w0 * sumToB);

+				weightsFloatData.put(w1 * sumToB);

+				weightsFloatData.put(w2 * sumToB);

+				weightsFloatData.put(w3 * sumToB);

+			}

+		}

+		weightsFloatData.rewind();

+		return maxWeightsPerVert;

+	}

+

+	@Override

+	public String getType() {

+		return Modifier.ARMATURE_MODIFIER_DATA;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java
new file mode 100644
index 0000000..4997fdc
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java
@@ -0,0 +1,247 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import com.jme3.bounding.BoundingBox;

+import com.jme3.bounding.BoundingSphere;

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+import com.jme3.scene.shape.Curve;

+import java.util.HashMap;

+import java.util.HashSet;

+import java.util.Map;

+import java.util.Set;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This modifier allows to array modifier to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class ArrayModifier extends Modifier {

+	private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());

+	

+	/** Parameters of the modifier. */

+	private Map<String, Object> modifierData = new HashMap<String, Object>();

+	

+	/**

+	 * This constructor reads array data from the modifier structure. The

+	 * stored data is a map of parameters for array modifier. No additional data

+	 * is loaded.

+	 * 

+	 * @param objectStructure

+	 *            the structure of the object

+	 * @param modifierStructure

+	 *            the structure of the modifier

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	@SuppressWarnings("unchecked")

+	public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {

+		if(this.validate(modifierStructure, blenderContext)) {

+	        Number fittype = (Number) modifierStructure.getFieldValue("fit_type");

+	        modifierData.put("fittype", fittype);

+	        switch (fittype.intValue()) {

+	            case 0:// FIXED COUNT

+	            	modifierData.put("count", modifierStructure.getFieldValue("count"));

+	                break;

+	            case 1:// FIXED LENGTH

+	            	modifierData.put("length", modifierStructure.getFieldValue("length"));

+	                break;

+	            case 2:// FITCURVE

+	                Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");

+	                float length = 0;

+	                if (pCurveOb.isNotNull()) {

+	                    Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0);

+	                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+	                    Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext);

+	                    Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());

+	                    for (Spatial spatial : curveObject.getChildren()) {

+	                        if (spatial instanceof Geometry) {

+	                            Mesh mesh = ((Geometry) spatial).getMesh();

+	                            if (mesh instanceof Curve) {

+	                                length += ((Curve) mesh).getLength();

+	                            } else {

+	                                //if bevel object has several parts then each mesh will have the same reference

+	                                //to length value (and we should use only one)

+	                                Number curveLength = spatial.getUserData("curveLength");

+	                                if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) {

+	                                    length += curveLength.floatValue();

+	                                    referencesToCurveLengths.add(curveLength);

+	                                }

+	                            }

+	                        }

+	                    }

+	                }

+	                modifierData.put("length", Float.valueOf(length));

+	                modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH

+	                break;

+	            default:

+	                assert false : "Unknown array modifier fit type: " + fittype;

+	        }

+	

+	        // offset parameters

+	        int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();

+	        if ((offsettype & 0x01) != 0) {// Constant offset

+	            DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");

+	            float[] offset = new float[]{offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()};

+	            modifierData.put("offset", offset);

+	        }

+	        if ((offsettype & 0x02) != 0) {// Relative offset

+	            DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");

+	            float[] scale = new float[]{scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()};

+	            modifierData.put("scale", scale);

+	        }

+	        if ((offsettype & 0x04) != 0) {// Object offset

+	            Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");

+	            if (pOffsetObject.isNotNull()) {

+	            	modifierData.put("offsetob", pOffsetObject);

+	            }

+	        }

+	

+	        // start cap and end cap

+	        Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");

+	        if (pStartCap.isNotNull()) {

+	        	modifierData.put("startcap", pStartCap);

+	        }

+	        Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");

+	        if (pEndCap.isNotNull()) {

+	        	modifierData.put("endcap", pEndCap);

+	        }

+		}

+	}

+	

+	@Override

+	public Node apply(Node node, BlenderContext blenderContext) {

+		if(invalid) {

+			LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());

+			return node;

+		}

+        int fittype = ((Number) modifierData.get("fittype")).intValue();

+        float[] offset = (float[]) modifierData.get("offset");

+        if (offset == null) {// the node will be repeated several times in the same place

+            offset = new float[]{0.0f, 0.0f, 0.0f};

+        }

+        float[] scale = (float[]) modifierData.get("scale");

+        if (scale == null) {// the node will be repeated several times in the same place

+            scale = new float[]{0.0f, 0.0f, 0.0f};

+        } else {

+            // getting bounding box

+            node.updateModelBound();

+            BoundingVolume boundingVolume = node.getWorldBound();

+            if (boundingVolume instanceof BoundingBox) {

+                scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;

+                scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;

+                scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;

+            } else if (boundingVolume instanceof BoundingSphere) {

+                float radius = ((BoundingSphere) boundingVolume).getRadius();

+                scale[0] *= radius * 2.0f;

+                scale[1] *= radius * 2.0f;

+                scale[2] *= radius * 2.0f;

+            } else {

+                throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());

+            }

+        }

+

+        // adding object's offset

+        float[] objectOffset = new float[]{0.0f, 0.0f, 0.0f};

+        Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");

+        if (pOffsetObject != null) {

+            FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());

+            ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+            try {// we take the structure in case the object was not yet loaded

+                Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);

+                Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();

+                objectOffset[0] = translation.x;

+                objectOffset[1] = translation.y;

+                objectOffset[2] = translation.z;

+            } catch (BlenderFileException e) {

+                LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());

+            }

+        }

+

+        // getting start and end caps

+        Node[] caps = new Node[]{null, null};

+        Pointer[] pCaps = new Pointer[]{(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")};

+        for (int i = 0; i < pCaps.length; ++i) {

+            if (pCaps[i] != null) {

+                caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+                if (caps[i] != null) {

+                    caps[i] = (Node) caps[i].clone();

+                } else {

+                    FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());

+                    try {// we take the structure in case the object was not yet loaded

+                        Structure capStructure = capBlock.getStructure(blenderContext);

+                        ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+                        caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext);

+                        if (caps[i] == null) {

+                            LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName());

+                        }

+                    } catch (BlenderFileException e) {

+                        LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());

+                    }

+                }

+            }

+        }

+

+        Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);

+

+        // getting/calculating repeats amount

+        int count = 0;

+        if (fittype == 0) {// Fixed count

+            count = ((Number) modifierData.get("count")).intValue() - 1;

+        } else if (fittype == 1) {// Fixed length

+            float length = ((Number) modifierData.get("length")).floatValue();

+            if (translationVector.length() > 0.0f) {

+                count = (int) (length / translationVector.length()) - 1;

+            }

+        } else if (fittype == 2) {// Fit curve

+            throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");

+        } else {

+            throw new IllegalStateException("Unknown fit type: " + fittype);

+        }

+

+        // adding translated nodes and caps

+        if (count > 0) {

+            Node[] arrayNodes = new Node[count];

+            Vector3f newTranslation = new Vector3f();

+            for (int i = 0; i < count; ++i) {

+                newTranslation.addLocal(translationVector);

+                Node nodeClone = (Node) node.clone();

+                nodeClone.setLocalTranslation(newTranslation);

+                arrayNodes[i] = nodeClone;

+            }

+            for (Node nodeClone : arrayNodes) {

+                node.attachChild(nodeClone);

+            }

+            if (caps[0] != null) {

+                caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);

+                node.attachChild(caps[0]);

+            }

+            if (caps[1] != null) {

+                caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);

+                node.attachChild(caps[1]);

+            }

+        }

+        return node;

+	}

+	

+	@Override

+	public String getType() {

+		return ARRAY_MODIFIER_DATA;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java
new file mode 100644
index 0000000..d441476
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java
@@ -0,0 +1,174 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.objects.ObjectHelper;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This modifier allows to array modifier to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class MirrorModifier extends Modifier {

+	private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());

+	

+	/** Parameters of the modifier. */

+	private Map<String, Object> modifierData = new HashMap<String, Object>();

+	

+	/**

+	 * This constructor reads mirror data from the modifier structure. The

+	 * stored data is a map of parameters for mirror modifier. No additional data

+	 * is loaded.

+	 * When the modifier is applied it is necessary to get the newly created node.

+	 * 

+	 * @param objectStructure

+	 *            the structure of the object

+	 * @param modifierStructure

+	 *            the structure of the modifier

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) {

+		if(this.validate(modifierStructure, blenderContext)) {

+			modifierData.put("flag", modifierStructure.getFieldValue("flag"));

+			modifierData.put("tolerance", modifierStructure.getFieldValue("tolerance"));

+	        Pointer pMirrorOb = (Pointer) modifierStructure.getFieldValue("mirror_ob");

+	        if (pMirrorOb.isNotNull()) {

+	        	modifierData.put("mirrorob", pMirrorOb);

+	        }

+		}

+	}

+	

+	@Override

+	public Node apply(Node node, BlenderContext blenderContext) {

+		if(invalid) {

+			LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());

+			return node;

+		}

+		

+        int flag = ((Number) modifierData.get("flag")).intValue();

+        float[] mirrorFactor = new float[]{

+            (flag & 0x08) != 0 ? -1.0f : 1.0f,

+            (flag & 0x10) != 0 ? -1.0f : 1.0f,

+            (flag & 0x20) != 0 ? -1.0f : 1.0f

+        };

+        float[] center = new float[]{0.0f, 0.0f, 0.0f};

+        Pointer pObject = (Pointer) modifierData.get("mirrorob");

+        if (pObject != null) {

+            Structure objectStructure;

+            try {

+                objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0);

+                ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);

+                Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);

+                if (object != null) {

+                    Vector3f translation = object.getWorldTranslation();

+                    center[0] = translation.x;

+                    center[1] = translation.y;

+                    center[2] = translation.z;

+                }

+            } catch (BlenderFileException e) {

+                LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());

+            }

+        }

+        float tolerance = ((Number) modifierData.get("tolerance")).floatValue();

+        boolean mirrorU = (flag & 0x01) != 0;

+        boolean mirrorV = (flag & 0x02) != 0;

+//		boolean mirrorVGroup = (flag & 0x20) != 0;

+

+        List<Geometry> geometriesToAdd = new ArrayList<Geometry>();

+        for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {

+            if (mirrorFactor[mirrorIndex] == -1.0f) {

+                for (Spatial spatial : node.getChildren()) {

+                    if (spatial instanceof Geometry) {

+                        Mesh mesh = ((Geometry) spatial).getMesh();

+                        Mesh clone = mesh.deepClone();

+

+                        // getting buffers

+                        FloatBuffer position = mesh.getFloatBuffer(Type.Position);

+                        FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);

+

+                        FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);

+                        FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);

+                        FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);

+                        FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);

+                        IntBuffer cloneIndexes = (IntBuffer) clone.getBuffer(Type.Index).getData();

+

+                        // modyfying data

+                        for (int i = mirrorIndex; i < clonePosition.limit(); i += 3) {

+                            float value = clonePosition.get(i);

+                            float d = center[mirrorIndex] - value;

+

+                            if (Math.abs(d) <= tolerance) {

+                                clonePosition.put(i, center[mirrorIndex]);

+                                cloneBindPosePosition.put(i, center[mirrorIndex]);

+                                position.put(i, center[mirrorIndex]);

+                                bindPosePosition.put(i, center[mirrorIndex]);

+                            } else {

+                                clonePosition.put(i, value + 2.0f * d);

+                                cloneBindPosePosition.put(i, value + 2.0f * d);

+                            }

+                            cloneNormals.put(i, -cloneNormals.get(i));

+                            cloneBindPoseNormals.put(i, -cloneNormals.get(i));

+

+                            //modifying clone indexes

+                            int vertexIndex = (i - mirrorIndex) / 3;

+                            if (vertexIndex % 3 == 0 && vertexIndex<cloneIndexes.limit()) {

+                                int index = cloneIndexes.get(vertexIndex + 2);

+                                cloneIndexes.put(vertexIndex + 2, cloneIndexes.get(vertexIndex + 1));

+                                cloneIndexes.put(vertexIndex + 1, index);

+                            }

+                        }

+

+                        if (mirrorU) {

+                            FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();

+                            for (int i = 0; i < cloneUVs.limit(); i += 2) {

+                                cloneUVs.put(i, 1.0f - cloneUVs.get(i));

+                            }

+                        }

+                        if (mirrorV) {

+                            FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();

+                            for (int i = 1; i < cloneUVs.limit(); i += 2) {

+                                cloneUVs.put(i, 1.0f - cloneUVs.get(i));

+                            }

+                        }

+

+                        Geometry geometry = new Geometry(null, clone);

+                        geometry.setMaterial(((Geometry) spatial).getMaterial());

+                        geometriesToAdd.add(geometry);

+                    }

+                }

+

+                // adding meshes to node

+                for (Geometry geometry : geometriesToAdd) {

+                    node.attachChild(geometry);

+                }

+                geometriesToAdd.clear();

+            }

+        }

+        return node;

+	}

+	

+	@Override

+	public String getType() {

+		return Modifier.MIRROR_MODIFIER_DATA;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java
new file mode 100644
index 0000000..12d9ed4
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java
@@ -0,0 +1,52 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import com.jme3.scene.Node;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * This class represents an object's modifier. The modifier object can be varied

+ * and the user needs to know what is the type of it for the specified type

+ * name. For example "ArmatureModifierData" type specified in blender is

+ * represented by AnimData object from jMonkeyEngine.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public abstract class Modifier {

+

+	public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";

+	public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";

+	public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";

+	public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";

+	public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData";

+	public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData";

+

+	/** This variable indicates if the modifier is invalid (<b>true</b>) or not (<b>false</b>). */

+	protected boolean invalid;

+	

+	/**

+	 * This method applies the modifier to the given node.

+	 * 

+	 * @param node

+	 *            the node that will have modifier applied

+	 * @param blenderContext

+	 *            the blender context

+	 * @return the node with applied modifier

+	 */

+	public abstract Node apply(Node node, BlenderContext blenderContext);

+

+	/**

+	 * This method returns blender's type of modifier.

+	 * 

+	 * @return blender's type of modifier

+	 */

+	public abstract String getType();

+	

+	protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) {

+		Structure modifierData = (Structure)modifierStructure.getFieldValue("modifier");

+		Pointer pError = (Pointer) modifierData.getFieldValue("error");

+		invalid = pError.isNotNull();

+		return !invalid;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
new file mode 100644
index 0000000..652fd57
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
@@ -0,0 +1,195 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.modifiers;

+

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.animations.IpoHelper;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+

+/**

+ * A class that is used in modifiers calculations.

+ * 

+ * @author Marcin Roguski

+ */

+public class ModifierHelper extends AbstractBlenderHelper {

+

+	private static final Logger	LOGGER	= Logger.getLogger(ModifierHelper.class.getName());

+

+	/**

+	 * This constructor parses the given blender version and stores the result.

+	 * Some functionalities may differ in different blender versions.

+	 * 

+	 * @param blenderVersion

+	 *            the version read from the blend file

+	 * @param fixUpAxis

+	 *            a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public ModifierHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	/**

+	 * This method reads the given object's modifiers.

+	 * 

+	 * @param objectStructure

+	 *            the object structure

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Collection<Modifier> result = new ArrayList<Modifier>();

+		Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");

+		List<Structure> modifiers = modifiersListBase.evaluateListBase(blenderContext);

+		for (Structure modifierStructure : modifiers) {

+			Modifier modifier = null;

+			if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) {

+				modifier = new ArrayModifier(modifierStructure, blenderContext);

+			} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) {

+				modifier = new MirrorModifier(modifierStructure, blenderContext);

+			} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) {

+				modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);

+			} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {

+				modifier = new ParticlesModifier(modifierStructure, blenderContext);

+			}

+

+			if (modifier != null) {

+				result.add(modifier);

+				blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier);

+			} else {

+				LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType());

+			}

+		}

+

+		// at the end read object's animation modifier (object animation is

+		// either described by action or by ipo of the object)

+		Modifier modifier;

+		if (blenderVersion <= 249) {

+			modifier = this.readAnimationModifier249(objectStructure, blenderContext);

+		} else {

+			modifier = this.readAnimationModifier250(objectStructure, blenderContext);

+		}

+		if (modifier != null) {

+			result.add(modifier);

+		}

+		return result;

+	}

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return true;

+	}

+

+	/**

+	 * This method reads the object's animation modifier for blender version

+	 * 2.49 and lower.

+	 * 

+	 * @param objectStructure

+	 *            the object's structure

+	 * @param blenderContext

+	 *            the blender context

+	 * @return loaded modifier

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Modifier result = null;

+		Pointer pAction = (Pointer) objectStructure.getFieldValue("action");

+		IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);

+		if (pAction.isNotNull()) {

+			Structure action = pAction.fetchData(blenderContext.getInputStream()).get(0);

+			List<Structure> actionChannels = ((Structure) action.getFieldValue("chanbase")).evaluateListBase(blenderContext);

+			if (actionChannels.size() == 1) {// object's animtion action has

+												// only one channel

+				Pointer pChannelIpo = (Pointer) actionChannels.get(0).getFieldValue("ipo");

+				Structure ipoStructure = pChannelIpo.fetchData(blenderContext.getInputStream()).get(0);

+				Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);

+				result = new ObjectAnimationModifier(ipo, action.getName(), objectStructure.getOldMemoryAddress(), blenderContext);

+				blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);

+			} else {

+				throw new IllegalStateException("Object's action cannot have more than one channel!");

+			}

+		} else {

+			Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo");

+			if (pIpo.isNotNull()) {

+				Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0);

+				Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);

+				result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);

+				blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method reads the object's animation modifier for blender version

+	 * 2.50 and higher.

+	 * 

+	 * @param objectStructure

+	 *            the object's structure

+	 * @param blenderContext

+	 *            the blender context

+	 * @return loaded modifier

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Modifier result = null;

+		Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt");

+		if (pAnimData.isNotNull()) {

+			Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0);

+			Pointer pAction = (Pointer) animData.getFieldValue("action");

+			if (pAction.isNotNull()) {

+				Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);

+				IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);

+				Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext);

+				result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);

+				blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);

+			}

+		}

+		return result;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java
new file mode 100644
index 0000000..a41395c
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java
@@ -0,0 +1,91 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.Animation;

+import com.jme3.animation.SpatialTrack;

+import com.jme3.scene.Node;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.animations.Ipo;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.ogre.AnimData;

+

+/**

+ * This modifier allows to add animation to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ObjectAnimationModifier extends Modifier {

+	private static final Logger	LOGGER	= Logger.getLogger(ObjectAnimationModifier.class.getName());

+

+	/** Loaded animation data. */

+	private AnimData			animData;

+

+	/**

+	 * This constructor reads animation of the object itself (without bones) and

+	 * stores it as an ArmatureModifierData modifier. The animation is returned

+	 * as a modifier. It should be later applied regardless other modifiers. The

+	 * reason for this is that object may not have modifiers added but it's

+	 * animation should be working. The stored modifier is an anim data and

+	 * additional data is given object's OMA.

+	 * 

+	 * @param ipo

+	 *            the object's interpolation curves

+	 * @param objectAnimationName

+	 *            the name of object's animation

+	 * @param objectOMA

+	 *            the OMA of the object

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             this exception is thrown when the blender file is somehow

+	 *             corrupted

+	 */

+	public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException {

+		int fps = blenderContext.getBlenderKey().getFps();

+

+		// calculating track

+		SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, 0, ipo.getLastFrame(), fps, true);

+

+		Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / fps);

+		animation.setTracks(new SpatialTrack[] { track });

+		ArrayList<Animation> animations = new ArrayList<Animation>(1);

+		animations.add(animation);

+

+		animData = new AnimData(null, animations);

+		blenderContext.setAnimData(objectOMA, animData);

+	}

+

+	@Override

+	public Node apply(Node node, BlenderContext blenderContext) {

+		if (invalid) {

+			LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());

+		}// if invalid, animData will be null

+		if (animData != null) {

+			// INFO: constraints for this modifier are applied in the

+			// ObjectHelper when the whole object is loaded

+			ArrayList<Animation> animList = animData.anims;

+			if (animList != null && animList.size() > 0) {

+				HashMap<String, Animation> anims = new HashMap<String, Animation>();

+				for (int i = 0; i < animList.size(); ++i) {

+					Animation animation = animList.get(i);

+					anims.put(animation.getName(), animation);

+				}

+

+				AnimControl control = new AnimControl(null);

+				control.setAnimations(anims);

+				node.addControl(control);

+			}

+		}

+		return node;

+	}

+

+	@Override

+	public String getType() {

+		return Modifier.OBJECT_ANIMATION_MODIFIER_DATA;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java
new file mode 100644
index 0000000..5a61eb6
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java
@@ -0,0 +1,101 @@
+package com.jme3.scene.plugins.blender.modifiers;

+

+import com.jme3.effect.ParticleEmitter;

+import com.jme3.effect.shapes.EmitterMeshVertexShape;

+import com.jme3.effect.shapes.EmitterShape;

+import com.jme3.material.Material;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+import com.jme3.scene.plugins.blender.particles.ParticlesHelper;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This modifier allows to add particles to the object.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */class ParticlesModifier extends Modifier {

+	private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());

+	

+	/** Loaded particles emitter. */

+	private ParticleEmitter particleEmitter;

+	

+	/**

+	 * This constructor reads the particles system structure and stores it in

+	 * order to apply it later to the node.

+	 * 

+	 * @param modifierStructure

+	 *            the structure of the modifier

+	 * @param blenderContext

+	 *            the blender context

+	 * @throws BlenderFileException

+	 *             an exception is throw wneh there are problems with the

+	 *             blender file

+	 */

+	public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {

+		if(this.validate(modifierStructure, blenderContext)) {

+			Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys");

+			if (pParticleSystem.isNotNull()) {

+				ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class);

+				Structure particleSystem = pParticleSystem.fetchData(blenderContext.getInputStream()).get(0);

+				particleEmitter = particlesHelper.toParticleEmitter(particleSystem, blenderContext);

+			}

+		}

+	}

+

+	@Override

+	public Node apply(Node node, BlenderContext blenderContext) {

+		if(invalid) {

+			LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());

+			return node;

+		}

+		

+		MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+		ParticleEmitter emitter = particleEmitter.clone();

+

+		// veryfying the alpha function for particles' texture

+		Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;

+		char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);

+		if (nameSuffix == 'B' || nameSuffix == 'N') {

+			alphaFunction = MaterialHelper.ALPHA_MASK_NONE;

+		}

+		// removing the type suffix from the name

+		emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));

+

+		// applying emitter shape

+		EmitterShape emitterShape = emitter.getShape();

+		List<Mesh> meshes = new ArrayList<Mesh>();

+		for (Spatial spatial : node.getChildren()) {

+			if (spatial instanceof Geometry) {

+				Mesh mesh = ((Geometry) spatial).getMesh();

+				if (mesh != null) {

+					meshes.add(mesh);

+					Material material = materialHelper.getParticlesMaterial(

+							((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);

+					emitter.setMaterial(material);// TODO: divide into several pieces

+				}

+			}

+		}

+		if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {

+			((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);

+		}

+

+		node.attachChild(emitter);

+		return node;

+	}

+

+	@Override

+	public String getType() {

+		return Modifier.PARTICLE_MODIFIER_DATA;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
new file mode 100644
index 0000000..ed14a11
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
@@ -0,0 +1,415 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.objects;

+

+import java.util.Collection;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.light.DirectionalLight;

+import com.jme3.light.Light;

+import com.jme3.light.PointLight;

+import com.jme3.light.SpotLight;

+import com.jme3.math.Matrix4f;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.Spatial.CullHint;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.cameras.CameraHelper;

+import com.jme3.scene.plugins.blender.constraints.Constraint;

+import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;

+import com.jme3.scene.plugins.blender.curves.CurvesHelper;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.lights.LightHelper;

+import com.jme3.scene.plugins.blender.meshes.MeshHelper;

+import com.jme3.scene.plugins.blender.modifiers.Modifier;

+import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;

+

+/**

+ * A class that is used in object calculations.

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class ObjectHelper extends AbstractBlenderHelper {

+	private static final Logger			LOGGER		= Logger.getLogger(ObjectHelper.class.getName());

+

+	protected static final int		OBJECT_TYPE_EMPTY			= 0;

+	protected static final int		OBJECT_TYPE_MESH			= 1;

+	protected static final int		OBJECT_TYPE_CURVE			= 2;

+	protected static final int		OBJECT_TYPE_SURF			= 3;

+	protected static final int		OBJECT_TYPE_TEXT			= 4;

+	protected static final int		OBJECT_TYPE_METABALL		= 5;

+	protected static final int		OBJECT_TYPE_LAMP			= 10;

+	protected static final int		OBJECT_TYPE_CAMERA			= 11;

+	protected static final int		OBJECT_TYPE_WAVE			= 21;

+	protected static final int		OBJECT_TYPE_LATTICE			= 22;

+	protected static final int		OBJECT_TYPE_ARMATURE		= 25;

+	

+	/**

+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in

+	 * different blender versions.

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public ObjectHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	/**

+	 * This method reads the given structure and createn an object that represents the data.

+	 * @param objectStructure

+	 *            the object's structure

+	 * @param blenderContext

+	 *            the blender context

+	 * @return blener's object representation

+	 * @throws BlenderFileException

+	 *             an exception is thrown when the given data is inapropriate

+	 */

+	public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {

+		Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+		if(loadedResult != null) {

+			return loadedResult;

+		}

+

+		blenderContext.pushParent(objectStructure);

+

+		//get object data

+		int type = ((Number)objectStructure.getFieldValue("type")).intValue();

+		String name = objectStructure.getName();

+		LOGGER.log(Level.INFO, "Loading obejct: {0}", name);

+

+		int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue();

+		boolean visible = (restrictflag & 0x01) != 0;

+		Object result = null;

+

+		Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");

+		Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+		if(parent == null && pParent.isNotNull()) {

+			Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);

+			parent = this.toObject(parentStructure, blenderContext);

+		}

+

+		Transform t = this.getTransformation(objectStructure, blenderContext);

+		

+		try {

+			switch(type) {

+				case OBJECT_TYPE_EMPTY:

+					LOGGER.log(Level.INFO, "Importing empty.");

+					Node empty = new Node(name);

+					empty.setLocalTransform(t);

+					if(parent instanceof Node) {

+						((Node) parent).attachChild(empty);

+					}

+					empty.updateModelBound();

+					result = empty;

+					break;

+				case OBJECT_TYPE_MESH:

+					LOGGER.log(Level.INFO, "Importing mesh.");

+					Node node = new Node(name);

+					node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);

+

+					//reading mesh

+					MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);

+					Pointer pMesh = (Pointer)objectStructure.getFieldValue("data");

+					List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());

+					List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);

+					if (geometries != null){

+                        for(Geometry geometry : geometries) {

+                            node.attachChild(geometry);

+                        }

+                    }

+					node.setLocalTransform(t);

+

+					//reading and applying all modifiers

+					ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);

+					Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);

+					for(Modifier modifier : modifiers) {

+						modifier.apply(node, blenderContext);

+					}

+

+					//setting the parent

+					if(parent instanceof Node) {

+						((Node)parent).attachChild(node);

+					}

+					node.updateModelBound();//I prefer do calculate bounding box here than read it from the file

+					result = node;

+					break;

+				case OBJECT_TYPE_SURF:

+				case OBJECT_TYPE_CURVE:

+					LOGGER.log(Level.INFO, "Importing curve/nurb.");

+					Pointer pCurve = (Pointer)objectStructure.getFieldValue("data");

+					if(pCurve.isNotNull()) {

+						CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);

+						Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);

+						List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);

+						result = new Node(name);

+						for(Geometry curve : curves) {

+							((Node)result).attachChild(curve);

+						}

+						((Node)result).setLocalTransform(t);

+					}

+					break;

+				case OBJECT_TYPE_LAMP:

+					LOGGER.log(Level.INFO, "Importing lamp.");

+					Pointer pLamp = (Pointer)objectStructure.getFieldValue("data");

+					if(pLamp.isNotNull()) {

+						LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);

+						List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());

+						Light light = lightHelper.toLight(lampsArray.get(0), blenderContext);

+						if(light!=null) {

+							light.setName(name);

+						}

+						if(light instanceof PointLight) {

+							((PointLight)light).setPosition(t.getTranslation());

+						} else if(light instanceof DirectionalLight) {

+							Quaternion quaternion = t.getRotation();

+							Vector3f[] axes = new Vector3f[3];

+							quaternion.toAxes(axes);

+							if(fixUpAxis) {

+								((DirectionalLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender

+							} else {

+								((DirectionalLight)light).setDirection(axes[2].negate());

+							}

+						} else if(light instanceof SpotLight) {

+							((SpotLight)light).setPosition(t.getTranslation());

+							

+							Quaternion quaternion = t.getRotation();

+							Vector3f[] axes = new Vector3f[3];

+							quaternion.toAxes(axes);

+							if(fixUpAxis) {

+								((SpotLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender

+							} else {

+								((SpotLight)light).setDirection(axes[2].negate());

+							}

+						} else {

+							LOGGER.log(Level.WARNING, "Unknown type of light: {0}", light);

+						}

+						result = light;

+					}

+					break;

+				case OBJECT_TYPE_CAMERA:

+					Pointer pCamera = (Pointer)objectStructure.getFieldValue("data");

+					if(pCamera.isNotNull()) {

+						CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);

+						List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());

+						Camera camera = cameraHelper.toCamera(camerasArray.get(0));

+						camera.setLocation(t.getTranslation());

+						camera.setRotation(t.getRotation());

+						result = camera;

+					}

+					break;

+				case OBJECT_TYPE_ARMATURE:

+					//need to create an empty node to properly create parent-children relationships between nodes

+					Node armature = new Node(name);

+					armature.setLocalTransform(t);

+					//TODO: modifiers for armature ????

+					if(parent instanceof Node) {

+						((Node)parent).attachChild(armature);

+					}

+					armature.updateModelBound();//I prefer do calculate bounding box here than read it from the file

+					result = armature;

+					break;

+				default:

+					LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);

+			}

+		} finally {

+			blenderContext.popParent();

+		}

+		

+		if(result != null) {

+			blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);

+			

+			//loading constraints connected with this object

+			ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);

+			constraintHelper.loadConstraints(objectStructure, blenderContext);

+			

+			//baking constraints

+			List<Constraint> objectConstraints = blenderContext.getConstraints(objectStructure.getOldMemoryAddress());

+			if(objectConstraints!=null) {

+				for(Constraint objectConstraint : objectConstraints) {

+					objectConstraint.bake();

+				}

+			}

+			

+			//reading custom properties

+			Properties properties = this.loadProperties(objectStructure, blenderContext);

+			if(result instanceof Spatial && properties != null && properties.getValue() != null) {

+				((Spatial)result).setUserData("properties", properties);

+			}

+		}

+		return result;

+	}

+	

+	/**

+	 * This method calculates local transformation for the object. Parentage is taken under consideration.

+	 * @param objectStructure

+	 *        the object's structure

+	 * @return objects transformation relative to its parent

+	 */

+	@SuppressWarnings("unchecked")

+	public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {

+		//these are transformations in global space

+		DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc");

+		DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size");

+		DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot");

+

+		//load parent inverse matrix

+		Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");

+		Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");

+		

+		//create the global matrix (without the scale)

+		Matrix4f globalMatrix = new Matrix4f();

+		globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());

+		globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue()));

+		//compute local matrix

+		Matrix4f localMatrix = parentInv.mult(globalMatrix);

+

+		Vector3f translation = localMatrix.toTranslationVector();

+		Quaternion rotation = localMatrix.toRotationQuat();

+		Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());

+		

+		if(fixUpAxis) {

+			float y = translation.y;

+			translation.y = translation.z;

+			translation.z = -y;

+			

+			y = rotation.getY();

+			float z = rotation.getZ();

+			rotation.set(rotation.getX(), z, -y, rotation.getW());

+			

+			y=scale.y;

+			scale.y = scale.z;

+			scale.z = y;

+		}

+		

+		//create the result

+		Transform t = new Transform(translation, rotation);

+		t.setScale(scale);

+		return t;

+	}

+

+	/**

+	 * This method returns the matrix of a given name for the given structure.

+	 * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file.

+	 * @param structure

+	 *        the structure with matrix data

+	 * @param matrixName

+	 * 		  the name of the matrix

+	 * @return the required matrix

+	 */

+	public Matrix4f getMatrix(Structure structure, String matrixName) {

+		return this.getMatrix(structure, matrixName, false);

+	}

+	

+	/**

+	 * This method returns the matrix of a given name for the given structure.

+	 * It takes up axis into consideration.

+	 * @param structure

+	 *        the structure with matrix data

+	 * @param matrixName

+	 * 		  the name of the matrix

+	 * @return the required matrix

+	 */

+	@SuppressWarnings("unchecked")

+	public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {

+		Matrix4f result = new Matrix4f();

+		DynamicArray<Number> obmat = (DynamicArray<Number>)structure.getFieldValue(matrixName);

+		int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square

+		for(int i = 0; i < rowAndColumnSize; ++i) {

+			for(int j = 0; j < rowAndColumnSize; ++j) {

+				result.set(i, j, obmat.get(j, i).floatValue());

+			}

+		}

+		if(applyFixUpAxis && fixUpAxis) {

+        	Vector3f translation = result.toTranslationVector();

+            Quaternion rotation = result.toRotationQuat();

+            Vector3f scale = this.getScale(result);

+            

+			float y = translation.y;

+			translation.y = translation.z;

+			translation.z = -y;

+			

+			y = rotation.getY();

+			float z = rotation.getZ();

+			rotation.set(rotation.getX(), z, -y, rotation.getW());

+			

+			y=scale.y;

+			scale.y = scale.z;

+			scale.z = y;

+			

+			result.loadIdentity();

+			result.setTranslation(translation);

+			result.setRotationQuaternion(rotation);

+			result.setScale(scale);

+        }

+		return result;

+	}

+

+	/**

+	 * This method returns the scale from the given matrix.

+	 * 

+	 * @param matrix

+	 *            the transformation matrix

+	 * @return the scale from the given matrix

+	 */

+	public Vector3f getScale(Matrix4f matrix) {

+		float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20);

+		float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21);

+		float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);

+		return new Vector3f(scaleX, scaleY, scaleZ);

+	}

+	

+	@Override

+	public void clearState() {

+		fixUpAxis = false;

+	}

+	

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		int lay = ((Number) structure.getFieldValue("lay")).intValue();

+        return ((lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0

+                && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0);

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java
new file mode 100644
index 0000000..9c4a2d6
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java
@@ -0,0 +1,467 @@
+package com.jme3.scene.plugins.blender.objects;
+
+import com.jme3.export.*;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.BlenderInputStream;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * The blender object's custom properties.
+ * This class is valid for all versions of blender.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class Properties implements Cloneable, Savable {
+	private static final Logger		LOGGER				= Logger.getLogger(Properties.class.getName());
+
+	// property type
+	public static final int			IDP_STRING			= 0;
+	public static final int			IDP_INT				= 1;
+	public static final int			IDP_FLOAT			= 2;
+	public static final int			IDP_ARRAY			= 5;
+	public static final int			IDP_GROUP			= 6;
+	// public static final int IDP_ID = 7;//this is not implemented in blender (yet)
+	public static final int			IDP_DOUBLE			= 8;
+	// the following are valid for blender 2.5x+
+	public static final int			IDP_IDPARRAY		= 9;
+	public static final int			IDP_NUMTYPES		= 10;
+
+	protected static final String	RNA_PROPERTY_NAME	= "_RNA_UI";
+	/** Default name of the property (used if the name is not specified in blender file). */
+	protected static final String	DEFAULT_NAME		= "Unnamed property";
+
+	/** The name of the property. */
+	private String					name;
+	/** The type of the property. */
+	private int						type;
+	/** The subtype of the property. Defines the type of array's elements. */
+	private int						subType;
+	/** The value of the property. */
+	private Object					value;
+	/** The description of the property. */
+	private String					description;
+
+	/**
+	 * This method loads the property from the belnder file.
+	 * @param idPropertyStructure
+	 *        the ID structure constining the property
+	 * @param blenderContext
+	 *        the blender context
+	 * @throws BlenderFileException
+	 *         an exception is thrown when the belnder file is somehow invalid
+	 */
+	public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException {
+		name = idPropertyStructure.getFieldValue("name").toString();
+		if (name == null || name.length() == 0) {
+			name = DEFAULT_NAME;
+		}
+		subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue();
+		type = ((Number) idPropertyStructure.getFieldValue("type")).intValue();
+
+		// reading the data
+		Structure data = (Structure) idPropertyStructure.getFieldValue("data");
+		int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue();
+		switch (type) {
+			case IDP_STRING: {
+				Pointer pointer = (Pointer) data.getFieldValue("pointer");
+				BlenderInputStream bis = blenderContext.getInputStream();
+				FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
+				bis.setPosition(dataFileBlock.getBlockPosition());
+				value = bis.readString();
+				break;
+			}
+			case IDP_INT:
+				int intValue = ((Number) data.getFieldValue("val")).intValue();
+				value = Integer.valueOf(intValue);
+				break;
+			case IDP_FLOAT:
+				int floatValue = ((Number) data.getFieldValue("val")).intValue();
+				value = Float.valueOf(Float.intBitsToFloat(floatValue));
+				break;
+			case IDP_ARRAY: {
+				Pointer pointer = (Pointer) data.getFieldValue("pointer");
+				BlenderInputStream bis = blenderContext.getInputStream();
+				FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
+				bis.setPosition(dataFileBlock.getBlockPosition());
+				int elementAmount = dataFileBlock.getSize();
+				switch (subType) {
+					case IDP_INT:
+						elementAmount /= 4;
+						int[] intList = new int[elementAmount];
+						for (int i = 0; i < elementAmount; ++i) {
+							intList[i] = bis.readInt();
+						}
+						value = intList;
+						break;
+					case IDP_FLOAT:
+						elementAmount /= 4;
+						float[] floatList = new float[elementAmount];
+						for (int i = 0; i < elementAmount; ++i) {
+							floatList[i] = bis.readFloat();
+						}
+						value = floatList;
+						break;
+					case IDP_DOUBLE:
+						elementAmount /= 8;
+						double[] doubleList = new double[elementAmount];
+						for (int i = 0; i < elementAmount; ++i) {
+							doubleList[i] = bis.readDouble();
+						}
+						value = doubleList;
+						break;
+					default:
+						throw new IllegalStateException("Invalid array subtype: " + subType);
+				}
+			}
+			case IDP_GROUP:
+				Structure group = (Structure) data.getFieldValue("group");
+				List<Structure> dataList = group.evaluateListBase(blenderContext);
+				List<Properties> subProperties = new ArrayList<Properties>(len);
+				for (Structure d : dataList) {
+					Properties properties = new Properties();
+					properties.load(d, blenderContext);
+					subProperties.add(properties);
+				}
+				value = subProperties;
+				break;
+			case IDP_DOUBLE:
+				int doublePart1 = ((Number) data.getFieldValue("val")).intValue();
+				int doublePart2 = ((Number) data.getFieldValue("val2")).intValue();
+				long doubleVal = (long) doublePart2 << 32 | doublePart1;
+				value = Double.valueOf(Double.longBitsToDouble(doubleVal));
+				break;
+			case IDP_IDPARRAY: {
+				Pointer pointer = (Pointer) data.getFieldValue("pointer");
+				List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream());
+				List<Object> result = new ArrayList<Object>(arrays.size());
+				Properties temp = new Properties();
+				for (Structure array : arrays) {
+					temp.load(array, blenderContext);
+					result.add(temp.value);
+				}
+				this.value = result;
+				break;
+			}
+			case IDP_NUMTYPES:
+				throw new UnsupportedOperationException();
+				// case IDP_ID://not yet implemented in blender
+				// return null;
+			default:
+				throw new IllegalStateException("Unknown custom property type: " + type);
+		}
+		this.completeLoading();
+	}
+
+	/**
+	 * This method returns the name of the property.
+	 * @return the name of the property
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * This method returns the description of the property.
+	 * @return the description of the property
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * This method returns the type of the property.
+	 * @return the type of the property
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * This method returns the value of the property.
+	 * The type of the value depends on the type of the property.
+	 * @return the value of the property
+	 */
+	public Object getValue() {
+		return value;
+	}
+	
+	/**
+	 * This method returns the same as getValue if the current property is of
+	 * other type than IDP_GROUP and its name matches 'propertyName' param. If
+	 * this property is a group property the method tries to find subproperty
+	 * value of the given name. The first found value is returnes os <b>use this
+	 * method wisely</b>. If no property of a given name is foung - <b>null</b>
+	 * is returned.
+	 * 
+	 * @param propertyName
+	 *            the name of the property
+	 * @return found property value or <b>null</b>
+	 */
+	@SuppressWarnings("unchecked")
+	public Object findValue(String propertyName) {
+		if (name.equals(propertyName)) {
+			return value;
+		} else {
+			if (type == IDP_GROUP) {
+				List<Properties> props = (List<Properties>) value;
+				for (Properties p : props) {
+					Object v = p.findValue(propertyName);
+					if (v != null) {
+						return v;
+					}
+				}
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		this.append(sb, new StringBuilder());
+		return sb.toString();
+	}
+
+	/**
+	 * This method appends the data of the property to the given string buffer.
+	 * @param sb
+	 *        string buffer
+	 * @param indent
+	 *        indent buffer
+	 */
+	@SuppressWarnings("unchecked")
+	private void append(StringBuilder sb, StringBuilder indent) {
+		sb.append(indent).append("name: ").append(name).append("\n\r");
+		sb.append(indent).append("type: ").append(type).append("\n\r");
+		sb.append(indent).append("subType: ").append(subType).append("\n\r");
+		sb.append(indent).append("description: ").append(description).append("\n\r");
+		indent.append('\t');
+		sb.append(indent).append("value: ");
+		if (value instanceof Properties) {
+			((Properties) value).append(sb, indent);
+		} else if (value instanceof List) {
+			for (Object v : (List<Object>) value) {
+				if (v instanceof Properties) {
+					sb.append(indent).append("{\n\r");
+					indent.append('\t');
+					((Properties) v).append(sb, indent);
+					indent.deleteCharAt(indent.length() - 1);
+					sb.append(indent).append("}\n\r");
+				} else {
+					sb.append(v);
+				}
+			}
+		} else {
+			sb.append(value);
+		}
+		sb.append("\n\r");
+		indent.deleteCharAt(indent.length() - 1);
+	}
+
+	/**
+	 * This method should be called after the properties loading.
+	 * It loads the properties from the _RNA_UI property and removes this property from the
+	 * result list.
+	 */
+	@SuppressWarnings("unchecked")
+	protected void completeLoading() {
+		if (this.type == IDP_GROUP) {
+			List<Properties> groupProperties = (List<Properties>) this.value;
+			Properties rnaUI = null;
+			for (Properties properties : groupProperties) {
+				if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) {
+					rnaUI = properties;
+					break;
+				}
+			}
+			if (rnaUI != null) {
+				// removing the RNA from the result list
+				groupProperties.remove(rnaUI);
+
+				// loading the descriptions
+				Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size());
+				List<Properties> propertiesRNA = (List<Properties>) rnaUI.value;
+				for (Properties properties : propertiesRNA) {
+					String name = properties.name;
+					String description = null;
+					List<Properties> rnaData = (List<Properties>) properties.value;
+					for (Properties rna : rnaData) {
+						if ("description".equalsIgnoreCase(rna.name)) {
+							description = (String) rna.value;
+							break;
+						}
+					}
+					descriptions.put(name, description);
+				}
+
+				// applying the descriptions
+				for (Properties properties : groupProperties) {
+					properties.description = descriptions.get(properties.name);
+				}
+			}
+		}
+	}
+
+	@Override
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public void write(JmeExporter ex) throws IOException {
+		OutputCapsule oc = ex.getCapsule(this);
+		oc.write(name, "name", DEFAULT_NAME);
+		oc.write(type, "type", 0);
+		oc.write(subType, "subtype", 0);
+		oc.write(description, "description", null);
+		switch (type) {
+			case IDP_STRING:
+				oc.write((String) value, "value", null);
+				break;
+			case IDP_INT:
+				oc.write((Integer) value, "value", 0);
+				break;
+			case IDP_FLOAT:
+				oc.write((Float) value, "value", 0);
+				break;
+			case IDP_ARRAY:
+				switch (subType) {
+					case IDP_INT:
+						oc.write((int[]) value, "value", null);
+						break;
+					case IDP_FLOAT:
+						oc.write((float[]) value, "value", null);
+						break;
+					case IDP_DOUBLE:
+						oc.write((double[]) value, "value", null);
+						break;
+					default:
+						LOGGER.warning("Cannot save the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType);
+				}
+			case IDP_GROUP:
+				oc.writeSavableArrayList((ArrayList<Properties>) value, "value", null);
+				break;
+			case IDP_DOUBLE:
+				oc.write((Double) value, "value", 0);
+				break;
+			case IDP_IDPARRAY:
+				oc.writeSavableArrayList((ArrayList) value, "value", null);
+				break;
+			case IDP_NUMTYPES:
+				LOGGER.warning("Numtypes value not supported! Cannot write it!");
+				break;
+			// case IDP_ID://not yet implemented in blender
+			// break;
+			default:
+				LOGGER.warning("Cannot save the property's value! Invalid type! Property: name: " + name + "; type: " + type);
+		}
+	}
+
+	@Override
+	public void read(JmeImporter im) throws IOException {
+		InputCapsule ic = im.getCapsule(this);
+		name = ic.readString("name", DEFAULT_NAME);
+		type = ic.readInt("type", 0);
+		subType = ic.readInt("subtype", 0);
+		description = ic.readString("description", null);
+		switch (type) {
+			case IDP_STRING:
+				value = ic.readString("value", null);
+				break;
+			case IDP_INT:
+				value = Integer.valueOf(ic.readInt("value", 0));
+				break;
+			case IDP_FLOAT:
+				value = Float.valueOf(ic.readFloat("value", 0.0f));
+				break;
+			case IDP_ARRAY:
+				switch (subType) {
+					case IDP_INT:
+						value = ic.readIntArray("value", null);
+						break;
+					case IDP_FLOAT:
+						value = ic.readFloatArray("value", null);
+						break;
+					case IDP_DOUBLE:
+						value = ic.readDoubleArray("value", null);
+						break;
+					default:
+						LOGGER.warning("Cannot read the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType);
+				}
+			case IDP_GROUP:
+				value = ic.readSavable("value", null);
+				break;
+			case IDP_DOUBLE:
+				value = Double.valueOf(ic.readDouble("value", 0.0));
+				break;
+			case IDP_IDPARRAY:
+				value = ic.readSavableArrayList("value", null);
+				break;
+			case IDP_NUMTYPES:
+				LOGGER.warning("Numtypes value not supported! Cannot read it!");
+				break;
+			// case IDP_ID://not yet implemented in blender
+			// break;
+			default:
+				LOGGER.warning("Cannot read the property's value! Invalid type! Property: name: " + name + "; type: " + type);
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (description == null ? 0 : description.hashCode());
+		result = prime * result + (name == null ? 0 : name.hashCode());
+		result = prime * result + subType;
+		result = prime * result + type;
+		result = prime * result + (value == null ? 0 : value.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (this.getClass() != obj.getClass()) {
+			return false;
+		}
+		Properties other = (Properties) obj;
+		if (description == null) {
+			if (other.description != null) {
+				return false;
+			}
+		} else if (!description.equals(other.description)) {
+			return false;
+		}
+		if (name == null) {
+			if (other.name != null) {
+				return false;
+			}
+		} else if (!name.equals(other.name)) {
+			return false;
+		}
+		if (subType != other.subType) {
+			return false;
+		}
+		if (type != other.type) {
+			return false;
+		}
+		if (value == null) {
+			if (other.value != null) {
+				return false;
+			}
+		} else if (!value.equals(other.value)) {
+			return false;
+		}
+		return true;
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java
new file mode 100644
index 0000000..de1252a
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java
@@ -0,0 +1,196 @@
+package com.jme3.scene.plugins.blender.particles;

+

+import com.jme3.effect.ParticleEmitter;

+import com.jme3.effect.ParticleMesh.Type;

+import com.jme3.effect.influencers.EmptyParticleInfluencer;

+import com.jme3.effect.influencers.NewtonianParticleInfluencer;

+import com.jme3.effect.influencers.ParticleInfluencer;

+import com.jme3.effect.shapes.EmitterMeshConvexHullShape;

+import com.jme3.effect.shapes.EmitterMeshFaceShape;

+import com.jme3.effect.shapes.EmitterMeshVertexShape;

+import com.jme3.math.ColorRGBA;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.DynamicArray;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import java.util.logging.Logger;

+

+public class ParticlesHelper extends AbstractBlenderHelper {

+	private static final Logger			LOGGER		= Logger.getLogger(ParticlesHelper.class.getName());

+	

+	// part->type

+	public static final int PART_EMITTER	=	0;

+	public static final int PART_REACTOR	=	1;

+	public static final int PART_HAIR		=	2;

+	public static final int PART_FLUID		=	3;

+	

+	// part->flag

+	public static final int PART_REACT_STA_END	=1;

+	public static final int PART_REACT_MULTIPLE	=2;

+	public static final int PART_LOOP			=4;

+	//public static final int PART_LOOP_INSTANT	=8;

+	public static final int PART_HAIR_GEOMETRY	=16;

+	public static final int PART_UNBORN			=32;		//show unborn particles

+	public static final int PART_DIED			=64;		//show died particles

+	public static final int PART_TRAND			=128;	

+	public static final int PART_EDISTR			=256;		// particle/face from face areas

+	public static final int PART_STICKY			=512;		//collided particles can stick to collider

+	public static final int PART_DIE_ON_COL		=1<<12;

+	public static final int PART_SIZE_DEFL		=1<<13; 	// swept sphere deflections

+	public static final int PART_ROT_DYN		=1<<14;	// dynamic rotation

+	public static final int PART_SIZEMASS		=1<<16;

+	public static final int PART_ABS_LENGTH		=1<<15;

+	public static final int PART_ABS_TIME		=1<<17;

+	public static final int PART_GLOB_TIME		=1<<18;

+	public static final int PART_BOIDS_2D		=1<<19;

+	public static final int PART_BRANCHING		=1<<20;

+	public static final int PART_ANIM_BRANCHING	=1<<21;

+	public static final int PART_SELF_EFFECT	=1<<22;

+	public static final int PART_SYMM_BRANCHING	=1<<24;

+	public static final int PART_HAIR_BSPLINE	=1024;

+	public static final int PART_GRID_INVERT	=1<<26;

+	public static final int PART_CHILD_EFFECT	=1<<27;

+	public static final int PART_CHILD_SEAMS	=1<<28;

+	public static final int PART_CHILD_RENDER	=1<<29;

+	public static final int PART_CHILD_GUIDE	=1<<30;

+	

+	// part->from

+	public static final int PART_FROM_VERT		=0;

+	public static final int PART_FROM_FACE		=1;

+	public static final int PART_FROM_VOLUME	=2;

+	public static final int PART_FROM_PARTICLE	=3;

+	public static final int PART_FROM_CHILD		=4;

+	

+	// part->phystype

+	public static final int PART_PHYS_NO	=	0;

+	public static final int PART_PHYS_NEWTON=	1;

+	public static final int PART_PHYS_KEYED	=	2;

+	public static final int PART_PHYS_BOIDS	=	3;

+	

+	// part->draw_as

+	public static final int PART_DRAW_NOT	=	0;

+	public static final int PART_DRAW_DOT	=	1;

+	public static final int PART_DRAW_CIRC	=	2;

+	public static final int PART_DRAW_CROSS	=	3;

+	public static final int PART_DRAW_AXIS	=	4;

+	public static final int PART_DRAW_LINE	=	5;

+	public static final int PART_DRAW_PATH	=	6;

+	public static final int PART_DRAW_OB	=	7;

+	public static final int PART_DRAW_GR	=	8;

+	public static final int PART_DRAW_BB	=	9;

+	

+	/**

+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in

+	 * different blender versions.

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public ParticlesHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, fixUpAxis);

+	}

+

+	@SuppressWarnings("unchecked")

+	public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException {

+		ParticleEmitter result = null;

+		Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");

+		if(pParticleSettings.isNotNull()) {

+			Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0);

+			

+			int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();

+			

+			//draw type will be stored temporarily in the name (it is used during modifier applying operation)

+			int drawAs = ((Number)particleSettings.getFieldValue("draw_as")).intValue();

+			char nameSuffix;//P - point, L - line, N - None, B - Bilboard

+			switch(drawAs) {

+				case PART_DRAW_NOT:

+					nameSuffix = 'N';

+					totPart = 0;//no need to generate particles in this case

+					break;

+				case PART_DRAW_BB:

+					nameSuffix = 'B';

+					break;

+				case PART_DRAW_OB:

+				case PART_DRAW_GR:

+					nameSuffix = 'P';

+					LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");//TODO: support groups and aobjects

+					break;

+				case PART_DRAW_LINE:

+					nameSuffix = 'L';

+					LOGGER.warning("Lines not yet supported! Using point representation instead!");//TODO: support lines

+				default://all others are rendered as points in blender

+					nameSuffix = 'P';

+			}

+			result = new ParticleEmitter(particleSettings.getName()+nameSuffix, Type.Triangle, totPart);

+			if(nameSuffix=='N') {

+				return result;//no need to set anything else

+			}

+			

+			//setting the emitters shape (the shapes meshes will be set later during modifier applying operation)

+			int from = ((Number)particleSettings.getFieldValue("from")).intValue();

+			switch(from) {

+				case PART_FROM_VERT:

+					result.setShape(new EmitterMeshVertexShape());

+					break;

+				case PART_FROM_FACE:

+					result.setShape(new EmitterMeshFaceShape());

+					break;

+				case PART_FROM_VOLUME:

+					result.setShape(new EmitterMeshConvexHullShape());

+					break;

+				default:

+					LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')');

+			}

+			

+			//reading acceleration

+			DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");

+			result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue());

+			

+			//setting the colors

+			result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f));

+			result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f));

+			

+			//reading size

+			float sizeFactor = nameSuffix=='B' ? 1.0f : 0.3f;

+			float size = ((Number)particleSettings.getFieldValue("size")).floatValue() * sizeFactor;

+			result.setStartSize(size);

+			result.setEndSize(size);

+			

+			//reading lifetime

+			int fps = blenderContext.getBlenderKey().getFps();

+			float lifetime = ((Number)particleSettings.getFieldValue("lifetime")).floatValue() / fps;

+			float randlife = ((Number)particleSettings.getFieldValue("randlife")).floatValue() / fps;

+			result.setLowLife(lifetime * (1.0f - randlife));

+		    result.setHighLife(lifetime);

+		    

+		    //preparing influencer

+		    ParticleInfluencer influencer;

+		    int phystype = ((Number)particleSettings.getFieldValue("phystype")).intValue();

+		    switch(phystype) {

+		    	case PART_PHYS_NEWTON:

+		    		influencer = new NewtonianParticleInfluencer();

+		    		((NewtonianParticleInfluencer)influencer).setNormalVelocity(((Number)particleSettings.getFieldValue("normfac")).floatValue());

+		    		((NewtonianParticleInfluencer)influencer).setVelocityVariation(((Number)particleSettings.getFieldValue("randfac")).floatValue());

+		    		((NewtonianParticleInfluencer)influencer).setSurfaceTangentFactor(((Number)particleSettings.getFieldValue("tanfac")).floatValue());

+		    		((NewtonianParticleInfluencer)influencer).setSurfaceTangentRotation(((Number)particleSettings.getFieldValue("tanphase")).floatValue());

+		    		break;

+		    	case PART_PHYS_BOIDS:

+		    	case PART_PHYS_KEYED://TODO: support other influencers

+		    		LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!");

+		    	case PART_PHYS_NO:

+	    		default:

+	    			influencer = new EmptyParticleInfluencer();

+		    }

+		    result.setParticleInfluencer(influencer);

+		}

+		return result;

+	}

+	

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return true;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java
new file mode 100644
index 0000000..d124616
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.scene.plugins.blender.file.BlenderInputStream;
+import com.jme3.texture.Image;
+import com.jme3.texture.plugins.AWTLoader;
+import com.jme3.texture.plugins.DDSLoader;
+import com.jme3.texture.plugins.TGALoader;
+import java.io.InputStream;
+import java.util.logging.Logger;
+
+/**
+ * An image loader class. It uses three loaders (AWTLoader, TGALoader and DDSLoader) in an attempt to load the image from the given
+ * input stream.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/*package*/ class ImageLoader extends AWTLoader {
+	private static final Logger	LOGGER		= Logger.getLogger(ImageLoader.class.getName());
+
+	protected DDSLoader			ddsLoader	= new DDSLoader();									// DirectX image loader
+
+	/**
+	 * This method loads the image from the blender file itself. It tries each loader to load the image.
+	 * 
+	 * @param inputStream
+	 *        blender input stream
+	 * @param startPosition
+	 *        position in the stream where the image data starts
+	 * @param flipY
+	 *        if the image should be flipped (does not work with DirectX image)
+	 * @return loaded image or null if it could not be loaded
+	 */
+	public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) {
+		// loading using AWT loader
+		inputStream.setPosition(startPosition);
+		Image result = this.loadImage(inputStream, ImageType.AWT, flipY);
+		// loading using TGA loader
+		if (result == null) {
+			inputStream.setPosition(startPosition);
+			result = this.loadImage(inputStream, ImageType.TGA, flipY);
+		}
+		// loading using DDS loader
+		if (result == null) {
+			inputStream.setPosition(startPosition);
+			result = this.loadImage(inputStream, ImageType.DDS, flipY);
+		}
+
+		if (result == null) {
+			LOGGER.warning("Image could not be loaded by none of available loaders!");
+		}
+
+		return result;
+	}
+
+	/**
+	 * This method loads an image of a specified type from the given input stream.
+	 * 
+	 * @param inputStream
+	 *        the input stream we read the image from
+	 * @param imageType
+	 *        the type of the image {@link ImageType}
+	 * @param flipY
+	 *        if the image should be flipped (does not work with DirectX image)
+	 * @return loaded image or null if it could not be loaded
+	 */
+	public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) {
+		Image result = null;
+		switch (imageType) {
+			case AWT:
+				try {
+					result = this.load(inputStream, flipY);
+				} catch (Exception e) {
+					LOGGER.info("Unable to load image using AWT loader!");
+				}
+				break;
+			case DDS:
+				try {
+					result = ddsLoader.load(inputStream);
+				} catch (Exception e) {
+					LOGGER.info("Unable to load image using DDS loader!");
+				}
+				break;
+			case TGA:
+				try {
+					result = TGALoader.load(inputStream, flipY);
+				} catch (Exception e) {
+					LOGGER.info("Unable to load image using TGA loader!");
+				}
+				break;
+			default:
+				throw new IllegalStateException("Unknown image type: " + imageType);
+		}
+		return result;
+	}
+	
+	/**
+	 * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	private static enum ImageType {
+		AWT, TGA, DDS;
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/NoiseGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/NoiseGenerator.java
new file mode 100644
index 0000000..a8bd890
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/NoiseGenerator.java
@@ -0,0 +1,875 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.textures;

+

+import com.jme3.math.FastMath;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.textures.TextureGeneratorMusgrave.MusgraveData;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.ObjectInputStream;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * This generator is responsible for creating various noises used to create

+ * generated textures loaded from blender.

+ * It derives from AbstractBlenderHelper but is not stored in blender context.

+ * It is only used by TextureHelper.

+ * @author Marcin Roguski (Kaelthas)

+ */

+/*package*/ class NoiseGenerator extends AbstractBlenderHelper {

+    private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName());

+    

+    // flag

+    protected static final int TEX_COLORBAND = 1;

+    protected static final int TEX_FLIPBLEND = 2;

+    protected static final int TEX_NEGALPHA = 4;

+    protected static final int TEX_CHECKER_ODD = 8;

+    protected static final int TEX_CHECKER_EVEN = 16;

+    protected static final int TEX_PRV_ALPHA = 32;

+    protected static final int TEX_PRV_NOR = 64;

+    protected static final int TEX_REPEAT_XMIR = 128;

+    protected static final int TEX_REPEAT_YMIR = 256;

+    protected static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR;

+

+    // tex->stype

+    protected static final int TEX_PLASTIC = 0;

+    protected static final int TEX_WALLIN = 1;

+    protected static final int TEX_WALLOUT = 2;

+

+    // musgrave stype

+    protected static final int TEX_MFRACTAL = 0;

+    protected static final int TEX_RIDGEDMF = 1;

+    protected static final int TEX_HYBRIDMF = 2;

+    protected static final int TEX_FBM = 3;

+    protected static final int TEX_HTERRAIN = 4;

+

+    // keyblock->type

+    protected static final int KEY_LINEAR = 0;

+    protected static final int KEY_CARDINAL = 1;

+    protected static final int KEY_BSPLINE = 2;

+

+    // CONSTANTS (read from file)

+    protected static float[] hashpntf;

+    protected static short[] hash;

+    protected static float[] hashvectf;

+    protected static short[] p;

+    protected static float[][] g;

+

+    /**

+     * Constructor. Stores the blender version number and loads the constants needed for computations.

+     * @param blenderVersion

+     *        the number of blender version

+     */

+    public NoiseGenerator(String blenderVersion) {

+        super(blenderVersion, false);

+        this.loadConstants();

+    }

+

+    /**

+     * This method loads the constants needed for computations. They are exactly like the ones the blender uses. Each

+     * deriving class should override this method and load its own constraints. Be carefult with overriding though, if

+     * an exception will be thrown the class will not be instantiated.

+     */

+    protected void loadConstants() {

+        InputStream is = NoiseGenerator.class.getResourceAsStream("noiseconstants.dat");

+        try {

+            ObjectInputStream ois = new ObjectInputStream(is);

+            hashpntf = (float[]) ois.readObject();

+            hash = (short[]) ois.readObject();

+            hashvectf = (float[]) ois.readObject();

+            p = (short[]) ois.readObject();

+            g = (float[][]) ois.readObject();

+        } catch (IOException e) {

+            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);

+        } catch (ClassNotFoundException e) {

+            assert false : "Constants' classes should be arrays of primitive types, so they are ALWAYS known!";

+        } finally {

+            if (is != null) {

+                try {

+                    is.close();

+                } catch (IOException e) {

+                    LOGGER.log(Level.WARNING, e.getLocalizedMessage());

+                }

+            }

+        }

+    }

+    

+    protected static Map<Integer, NoiseFunction> noiseFunctions = new HashMap<Integer, NoiseFunction>();

+    static {

+        noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() {

+        	// originalBlenderNoise

+            @Override

+            public float execute(float x, float y, float z) {

+                return NoiseFunctions.originalBlenderNoise(x, y, z);

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                return 2.0f * NoiseFunctions.originalBlenderNoise(x, y, z) - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() {

+        	// orgPerlinNoise

+            @Override

+            public float execute(float x, float y, float z) {

+                return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z);

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                return NoiseFunctions.noise3Perlin(x, y, z);

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() {

+        	// newPerlin

+            @Override

+            public float execute(float x, float y, float z) {

+                return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z);

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                return this.execute(x, y, z);

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() {

+        	// voronoi_F1

+            @Override

+            public float execute(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return da[0];

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return 2.0f * da[0] - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() {

+        	// voronoi_F2

+            @Override

+            public float execute(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return da[1];

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return 2.0f * da[1] - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() {

+        	// voronoi_F3

+            @Override

+            public float execute(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return da[2];

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return 2.0f * da[2] - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() {

+        	// voronoi_F4

+            @Override

+            public float execute(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return da[3];

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return 2.0f * da[3] - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() {

+        	// voronoi_F1F2

+            @Override

+            public float execute(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return da[1] - da[0];

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float[] da = new float[4], pa = new float[12];

+                NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);

+                return 2.0f * (da[1] - da[0]) - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() {

+        	// voronoi_Cr

+            @Override

+            public float execute(float x, float y, float z) {

+                float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2

+                return t > 1.0f ? 1.0f : t;

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                float t = 10.0f * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2

+                return t > 1.0f ? 1.0f : 2.0f * t - 1.0f;

+            }

+        });

+        noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() {

+        	// cellNoise

+            @Override

+            public float execute(float x, float y, float z) {

+                int xi = (int) Math.floor(x);

+                int yi = (int) Math.floor(y);

+                int zi = (int) Math.floor(z);

+                long n = xi + yi * 1301 + zi * 314159;

+                n ^= n << 13;

+                return (n * (n * n * 15731 + 789221) + 1376312589) / 4294967296.0f;

+            }

+

+            @Override

+            public float executeSigned(float x, float y, float z) {

+                return 2.0f * this.execute(x, y, z) - 1.0f;

+            }

+        });

+    }

+    /** Distance metrics for voronoi. e parameter only used in Minkovsky. */

+    protected static Map<Integer, DistanceFunction> distanceFunctions = new HashMap<Integer, NoiseGenerator.DistanceFunction>();

+

+    static {

+        distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() {

+        	// real distance

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                return (float) Math.sqrt(x * x + y * y + z * z);

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() {

+        	// distance squared

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                return x * x + y * y + z * z;

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() {

+        	// manhattan/taxicab/cityblock distance

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() {

+        	// Chebychev

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                x = FastMath.abs(x);

+                y = FastMath.abs(y);

+                z = FastMath.abs(z);

+                float t = x > y ? x : y;

+                return z > t ? z : t;

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() {

+        	// Minkovsky, preset exponent 0.5 (MinkovskyH)

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z)));

+                return d * d;

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() {

+        	// Minkovsky, preset exponent 0.25 (Minkovsky4)

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                x *= x;

+                y *= y;

+                z *= z;

+                return (float) Math.sqrt(Math.sqrt(x * x + y * y + z * z));

+            }

+        });

+        distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() {

+        	// Minkovsky, general case

+            @Override

+            public float execute(float x, float y, float z, float e) {

+                return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e);

+            }

+        });

+    }

+    

+    protected static Map<Integer, MusgraveFunction> musgraveFunctions = new HashMap<Integer, NoiseGenerator.MusgraveFunction>();

+    static {

+        musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() {

+

+            @Override

+            public float execute(MusgraveData musgraveData, float x, float y, float z) {

+                float rmd, value = 1.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h);

+                NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(musgraveData.noisebasis));

+                if (abstractNoiseFunc == null) {

+                    abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));

+                }

+

+                for (int i = 0; i < (int) musgraveData.octaves; ++i) {

+                    value *= pwr * abstractNoiseFunc.executeSigned(x, y, z) + 1.0f;

+                    pwr *= pwHL;

+                    x *= musgraveData.lacunarity;

+                    y *= musgraveData.lacunarity;

+                    z *= musgraveData.lacunarity;

+                }

+                rmd = (float) (musgraveData.octaves - Math.floor(musgraveData.octaves));

+                if (rmd != 0.0f) {

+                    value *= rmd * abstractNoiseFunc.executeSigned(x, y, z) * pwr + 1.0f;

+                }

+                return value;

+            }

+        });

+        musgraveFunctions.put(Integer.valueOf(TEX_RIDGEDMF), new MusgraveFunction() {

+

+            @Override

+            public float execute(MusgraveData musgraveData, float x, float y, float z) {

+                float result, signal, weight;

+                float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h);

+                float pwr = pwHL;

+

+                NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(musgraveData.noisebasis));

+                if (abstractNoiseFunc == null) {

+                    abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));

+                }

+

+                signal = musgraveData.offset - FastMath.abs(abstractNoiseFunc.executeSigned(x, y, z));

+                signal *= signal;

+                result = signal;

+                weight = 1.0f;

+

+                for (int i = 1; i < (int) musgraveData.octaves; ++i) {

+                    x *= musgraveData.lacunarity;

+                    y *= musgraveData.lacunarity;

+                    z *= musgraveData.lacunarity;

+                    weight = signal * musgraveData.gain;

+                    if (weight > 1.0f) {

+                        weight = 1.0f;

+                    } else if (weight < 0.0) {

+                        weight = 0.0f;

+                    }

+                    signal = musgraveData.offset - FastMath.abs(abstractNoiseFunc.executeSigned(x, y, z));

+                    signal *= signal;

+                    signal *= weight;

+                    result += signal * pwr;

+                    pwr *= pwHL;

+                }

+                return result;

+            }

+        });

+        musgraveFunctions.put(Integer.valueOf(TEX_HYBRIDMF), new MusgraveFunction() {

+

+            @Override

+            public float execute(MusgraveData musgraveData, float x, float y, float z) {

+                float result, signal, weight, rmd;

+                float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h);

+                float pwr = pwHL;

+                NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(musgraveData.noisebasis));

+                if (abstractNoiseFunc == null) {

+                    abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));

+                }

+

+                result = abstractNoiseFunc.executeSigned(x, y, z) + musgraveData.offset;

+                weight = musgraveData.gain * result;

+                x *= musgraveData.lacunarity;

+                y *= musgraveData.lacunarity;

+                z *= musgraveData.lacunarity;

+

+                for (int i = 1; weight > 0.001f && i < (int) musgraveData.octaves; ++i) {

+                    if (weight > 1.0f) {

+                        weight = 1.0f;

+                    }

+                    signal = (abstractNoiseFunc.executeSigned(x, y, z) + musgraveData.offset) * pwr;

+                    pwr *= pwHL;

+                    result += weight * signal;

+                    weight *= musgraveData.gain * signal;

+                    x *= musgraveData.lacunarity;

+                    y *= musgraveData.lacunarity;

+                    z *= musgraveData.lacunarity;

+                }

+

+                rmd = musgraveData.octaves - (float) Math.floor(musgraveData.octaves);

+                if (rmd != 0.0f) {

+                    result += rmd * (abstractNoiseFunc.executeSigned(x, y, z) + musgraveData.offset) * pwr;

+                }

+                return result;

+            }

+        });

+        musgraveFunctions.put(Integer.valueOf(TEX_FBM), new MusgraveFunction() {

+

+            @Override

+            public float execute(MusgraveData musgraveData, float x, float y, float z) {

+                float rmd, value = 0.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h);

+

+                NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(musgraveData.noisebasis));

+                if (abstractNoiseFunc == null) {

+                    abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));

+                }

+

+                for (int i = 0; i < (int) musgraveData.octaves; ++i) {

+                    value += abstractNoiseFunc.executeSigned(x, y, z) * pwr;

+                    pwr *= pwHL;

+                    x *= musgraveData.lacunarity;

+                    y *= musgraveData.lacunarity;

+                    z *= musgraveData.lacunarity;

+                }

+

+                rmd = (float) (musgraveData.octaves - Math.floor(musgraveData.octaves));

+                if (rmd != 0.f) {

+                    value += rmd * abstractNoiseFunc.executeSigned(x, y, z) * pwr;

+                }

+                return value;

+            }

+        });

+        musgraveFunctions.put(Integer.valueOf(TEX_HTERRAIN), new MusgraveFunction() {

+

+            @Override

+            public float execute(MusgraveData musgraveData, float x, float y, float z) {

+                float value, increment, rmd;

+                float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h);

+                float pwr = pwHL;

+                NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(musgraveData.noisebasis));

+                if (abstractNoiseFunc == null) {

+                    abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));

+                }

+

+                value = musgraveData.offset + abstractNoiseFunc.executeSigned(x, y, z);

+                x *= musgraveData.lacunarity;

+                y *= musgraveData.lacunarity;

+                z *= musgraveData.lacunarity;

+

+                for (int i = 1; i < (int) musgraveData.octaves; ++i) {

+                    increment = (abstractNoiseFunc.executeSigned(x, y, z) + musgraveData.offset) * pwr * value;

+                    value += increment;

+                    pwr *= pwHL;

+                    x *= musgraveData.lacunarity;

+                    y *= musgraveData.lacunarity;

+                    z *= musgraveData.lacunarity;

+                }

+

+                rmd = musgraveData.octaves - (float) Math.floor(musgraveData.octaves);

+                if (rmd != 0.0) {

+                    increment = (abstractNoiseFunc.executeSigned(x, y, z) + musgraveData.offset) * pwr * value;

+                    value += rmd * increment;

+                }

+                return value;

+            }

+        });

+    }

+    

+    public static class NoiseFunctions {

+    	public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) {

+    		NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));

+    		if (abstractNoiseFunc == null) {

+	            abstractNoiseFunc = noiseFunctions.get(0);

+	            noiseBasis = 0;

+	        }

+    		

+    		if (noiseBasis == 0) {

+    			++x;

+	            ++y;

+	            ++z;

+	        }

+

+	        if (noiseSize != 0.0) {

+	            noiseSize = 1.0f / noiseSize;

+	            x *= noiseSize;

+	            y *= noiseSize;

+	            z *= noiseSize;

+	        }

+	        float result = abstractNoiseFunc.execute(x, y, z);

+	        return isHard ? Math.abs(2.0f * result - 1.0f) : result;

+    	}

+    	

+    	public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) {

+    		NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));

+    		if (abstractNoiseFunc == null) {

+	            abstractNoiseFunc = noiseFunctions.get(0);

+	            noiseBasis = 0;

+	        }

+    		

+    		if (noiseBasis == 0) {

+	            ++x;

+	            ++y;

+	            ++z;

+	        }

+	        if (noiseSize != 0.0) {

+	            noiseSize = 1.0f / noiseSize;

+	            x *= noiseSize;

+	            y *= noiseSize;

+	            z *= noiseSize;

+	        }

+	        

+	        float sum = 0, t, amp = 1, fscale = 1;

+	        for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) {

+	            t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z);

+	            if (isHard) {

+	                t = FastMath.abs(2.0f * t - 1.0f);

+	            }

+	            sum += t * amp;

+	        }

+

+	        sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1);

+	        return sum;

+    	}

+    	

+    	/**

+         * Not 'pure' Worley, but the results are virtually the same. Returns distances in da and point coords in pa

+         */

+        public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, int distanceType) {

+            float xd, yd, zd, d, p[] = new float[3];

+

+            DistanceFunction distanceFunc = distanceFunctions.get(Integer.valueOf(distanceType));

+            if (distanceFunc == null) {

+                distanceFunc = distanceFunctions.get(Integer.valueOf(0));

+            }

+

+            int xi = (int) FastMath.floor(x);

+            int yi = (int) FastMath.floor(y);

+            int zi = (int) FastMath.floor(z);

+            da[0] = da[1] = da[2] = da[3] = 1e10f;

+            for (int i = xi - 1; i <= xi + 1; ++i) {

+                for (int j = yi - 1; j <= yi + 1; ++j) {

+                    for (int k = zi - 1; k <= zi + 1; ++k) {

+                        NoiseMath.hash(i, j, k, p);

+                        xd = x - (p[0] + i);

+                        yd = y - (p[1] + j);

+                        zd = z - (p[2] + k);

+                        d = distanceFunc.execute(xd, yd, zd, distanceExponent);

+                        if (d < da[0]) {

+                            da[3] = da[2];

+                            da[2] = da[1];

+                            da[1] = da[0];

+                            da[0] = d;

+                            pa[9] = pa[6];

+                            pa[10] = pa[7];

+                            pa[11] = pa[8];

+                            pa[6] = pa[3];

+                            pa[7] = pa[4];

+                            pa[8] = pa[5];

+                            pa[3] = pa[0];

+                            pa[4] = pa[1];

+                            pa[5] = pa[2];

+                            pa[0] = p[0] + i;

+                            pa[1] = p[1] + j;

+                            pa[2] = p[2] + k;

+                        } else if (d < da[1]) {

+                            da[3] = da[2];

+                            da[2] = da[1];

+                            da[1] = d;

+                            pa[9] = pa[6];

+                            pa[10] = pa[7];

+                            pa[11] = pa[8];

+                            pa[6] = pa[3];

+                            pa[7] = pa[4];

+                            pa[8] = pa[5];

+                            pa[3] = p[0] + i;

+                            pa[4] = p[1] + j;

+                            pa[5] = p[2] + k;

+                        } else if (d < da[2]) {

+                            da[3] = da[2];

+                            da[2] = d;

+                            pa[9] = pa[6];

+                            pa[10] = pa[7];

+                            pa[11] = pa[8];

+                            pa[6] = p[0] + i;

+                            pa[7] = p[1] + j;

+                            pa[8] = p[2] + k;

+                        } else if (d < da[3]) {

+                            da[3] = d;

+                            pa[9] = p[0] + i;

+                            pa[10] = p[1] + j;

+                            pa[11] = p[2] + k;

+                        }

+                    }

+                }

+            }

+        }

+        

+        // instead of adding another permutation array, just use hash table defined above

+        public static float newPerlin(float x, float y, float z) {

+            int A, AA, AB, B, BA, BB;

+            float floorX = (float) Math.floor(x), floorY = (float) Math.floor(y), floorZ = (float) Math.floor(z);

+            int intX = (int) floorX & 0xFF, intY = (int) floorY & 0xFF, intZ = (int) floorZ & 0xFF;

+            x -= floorX;

+            y -= floorY;

+            z -= floorZ;

+            //computing fading curves

+            floorX = NoiseMath.npfade(x);

+            floorY = NoiseMath.npfade(y);

+            floorZ = NoiseMath.npfade(z);

+            A = hash[intX] + intY;

+            AA = hash[A] + intZ;

+            AB = hash[A + 1] + intZ;

+            B = hash[intX + 1] + intY;

+            BA = hash[B] + intZ;

+            BB = hash[B + 1] + intZ;

+            return  NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z),

+            		NoiseMath.grad(hash[BA], x - 1, y, z)),

+            		NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z),

+            		NoiseMath.grad(hash[BB], x - 1, y - 1, z))),

+            		NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1),

+            		NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)),

+            		NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), 

+            		NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1))));

+        }

+

+        public static float noise3Perlin(float x, float y, float z) {

+            float t = x + 10000.0f;

+            int bx0 = (int) t & 0xFF;

+            int bx1 = bx0 + 1 & 0xFF;

+            float rx0 = t - (int) t;

+            float rx1 = rx0 - 1.0f;

+            

+            t = y + 10000.0f;

+            int by0 = (int) t & 0xFF;

+            int by1 = by0 + 1 & 0xFF;

+            float ry0 = t - (int) t;

+            float ry1 = ry0 - 1.0f;

+            

+            t = z + 10000.0f;

+            int bz0 = (int) t & 0xFF;

+            int bz1 = bz0 + 1 & 0xFF;

+            float rz0 = t - (int) t;

+            float rz1 = rz0 - 1.0f;

+

+            int i = p[bx0];

+            int j = p[bx1];

+

+            int b00 = p[i + by0];

+            int b10 = p[j + by0];

+            int b01 = p[i + by1];

+            int b11 = p[j + by1];

+

+            float sx = NoiseMath.surve(rx0);

+            float sy = NoiseMath.surve(ry0);

+            float sz = NoiseMath.surve(rz0);

+

+            float[] q = g[b00 + bz0];

+            float u = NoiseMath.at(rx0, ry0, rz0, q);

+            q = g[b10 + bz0];

+            float v = NoiseMath.at(rx1, ry0, rz0, q);

+            float a = NoiseMath.lerp(sx, u, v);

+

+            q = g[b01 + bz0];

+            u = NoiseMath.at(rx0, ry1, rz0, q);

+            q = g[b11 + bz0];

+            v = NoiseMath.at(rx1, ry1, rz0, q);

+            float b = NoiseMath.lerp(sx, u, v);

+

+            float c = NoiseMath.lerp(sy, a, b);

+

+            q = g[b00 + bz1];

+            u = NoiseMath.at(rx0, ry0, rz1, q);

+            q = g[b10 + bz1];

+            v = NoiseMath.at(rx1, ry0, rz1, q);

+            a = NoiseMath.lerp(sx, u, v);

+

+            q = g[b01 + bz1];

+            u = NoiseMath.at(rx0, ry1, rz1, q);

+            q = g[b11 + bz1];

+            v = NoiseMath.at(rx1, ry1, rz1, q);

+            b = NoiseMath.lerp(sx, u, v);

+

+            float d = NoiseMath.lerp(sy, a, b);

+            return 1.5f * NoiseMath.lerp(sz, c, d);

+        }

+

+        public static float originalBlenderNoise(float x, float y, float z) {

+            float n = 0.5f;

+

+            int ix = (int) Math.floor(x);

+            int iy = (int) Math.floor(y);

+            int iz = (int) Math.floor(z);

+            

+            float ox = x - ix;

+            float oy = y - iy;

+            float oz = z - iz;

+

+            float jx = ox - 1;

+            float jy = oy - 1;

+            float jz = oz - 1;

+

+            float cn1 = ox * ox;

+            float cn2 = oy * oy;

+            float cn3 = oz * oz;

+            float cn4 = jx * jx;

+            float cn5 = jy * jy;

+            float cn6 = jz * jz;

+

+            cn1 = 1.0f - 3.0f * cn1 + 2.0f * cn1 * ox;

+            cn2 = 1.0f - 3.0f * cn2 + 2.0f * cn2 * oy;

+            cn3 = 1.0f - 3.0f * cn3 + 2.0f * cn3 * oz;

+            cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx;

+            cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy;

+            cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz;

+            float[] cn = new float[] {cn1 * cn2 * cn3, cn1 * cn2 * cn6, cn1 * cn5 * cn3, cn1 * cn5 * cn6,

+					  cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * cn5 * cn6,};

+            

+            int b00 = hash[hash[ix & 0xFF] + (iy & 0xFF)];

+            int b01 = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)];

+            int b10 = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)];

+            int b11 = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)];

+            int[] b1 = new int[] {b00, b00, b01, b01, b10, b10, b11, b11};

+            

+            int[] b2 = new int[] {iz & 0xFF, iz + 1 & 0xFF};

+            

+            float[] xFactor = new float[] {ox, ox, ox, ox, jx, jx, jx, jx};

+            float[] yFactor = new float[] {oy, oy, jy, jy, oy, oy, jy, jy};

+            float[] zFactor = new float[] {oz, jz, oz, jz, oz, jz, oz, jz};

+            

+            for(int i=0;i<8;++i) {

+            	int hIndex = 3 * hash[b1[i] + b2[i%2]];

+            	n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]);

+            }

+

+            if (n < 0.0f) {

+                n = 0.0f;

+            } else if (n > 1.0f) {

+                n = 1.0f;

+            }

+            return n;

+        }

+    }

+

+    /**

+     * This class is abstract to the noise functions computations. It has two methods. One calculates the Signed (with

+     * 'S' at the end) and the other Unsigned value.

+     * @author Marcin Roguski (Kaelthas)

+     */

+    interface NoiseFunction {

+

+        /**

+         * This method calculates the unsigned value of the noise.

+         * @param x

+         *        the x texture coordinate

+         * @param y

+         *        the y texture coordinate

+         * @param z

+         *        the z texture coordinate

+         * @return value of the noise

+         */

+        float execute(float x, float y, float z);

+

+        /**

+         * This method calculates the signed value of the noise.

+         * @param x

+         *        the x texture coordinate

+         * @param y

+         *        the y texture coordinate

+         * @param z

+         *        the z texture coordinate

+         * @return value of the noise

+         */

+        float executeSigned(float x, float y, float z);

+    }

+    

+    public static class NoiseMath {

+    	public static float lerp(float t, float a, float b) {

+            return a + t * (b - a);

+        }

+

+    	public static float npfade(float t) {

+            return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);

+        }

+

+    	public static float grad(int hash, float x, float y, float z) {

+            int h = hash & 0x0F;

+            float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;

+            return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);

+        }

+

+    	public static float surve(float t) {

+            return t * t * (3.0f - 2.0f * t);

+        }

+

+    	public static float at(float x, float y, float z, float[] q) {

+            return x * q[0] + y * q[1] + z * q[2];

+        }

+    	

+    	public static void hash(int x, int y, int z, float[] result) {

+            result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]];

+            result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1];

+            result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2];

+        }

+    }

+    

+    @Override

+    public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+    	return true;

+    }

+

+    /**

+     * This interface is used for distance calculation classes. Distance metrics for voronoi. e parameter only used in

+     * Minkovsky.

+     */

+    interface DistanceFunction {

+

+        /**

+         * This method calculates the distance for voronoi algorithms.

+         * @param x

+         *        the x coordinate

+         * @param y

+         *        the y coordinate

+         * @param z

+         *        the z coordinate

+         * @param e

+         *        this parameter used in Monkovsky (no idea what it really is ;)

+         * @return

+         */

+        float execute(float x, float y, float z, float e);

+    }

+

+    interface MusgraveFunction {

+

+        float execute(MusgraveData musgraveData, float x, float y, float z);

+    }

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGenerator.java
new file mode 100644
index 0000000..a727277
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGenerator.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.DynamicArray;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Texture;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class is a base class for texture generators.
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */abstract class TextureGenerator {
+	private static final Logger	LOGGER	= Logger.getLogger(TextureGenerator.class.getName());
+
+	protected NoiseGenerator	noiseGenerator;
+	
+	public TextureGenerator(NoiseGenerator noiseGenerator) {
+		this.noiseGenerator = noiseGenerator;
+	}
+
+	/**
+	 * This method generates the texture.
+	 * @param tex
+	 *        texture's structure
+	 * @param width
+	 *        the width of the result texture
+	 * @param height
+	 *        the height of the result texture
+	 * @param depth
+	 *        the depth of the texture
+	 * @param blenderContext
+	 *        the blender context
+	 * @return newly generated texture
+	 */
+	protected abstract Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext);
+
+	/**
+	 * This method reads the colorband data from the given texture structure.
+	 * 
+	 * @param tex
+	 *        the texture structure
+	 * @param blenderContext
+	 *        the blender context
+	 * @return read colorband or null if not present
+	 */
+	private ColorBand readColorband(Structure tex, BlenderContext blenderContext) {
+		ColorBand result = null;
+		int flag = ((Number) tex.getFieldValue("flag")).intValue();
+		if ((flag & NoiseGenerator.TEX_COLORBAND) != 0) {
+			Pointer pColorband = (Pointer) tex.getFieldValue("coba");
+			Structure colorbandStructure;
+			try {
+				colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
+				result = new ColorBand(colorbandStructure);
+			} catch (BlenderFileException e) {
+				LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage());
+			}
+		}
+		return result;
+	}
+	
+	protected float[][] computeColorband(Structure tex, BlenderContext blenderContext) {
+		ColorBand colorBand = this.readColorband(tex, blenderContext);
+		float[][] result = null;
+		if(colorBand!=null) {
+			result = new float[1001][4];//1001 - amount of possible cursor positions; 4 = [r, g, b, a]
+			ColorBandData[] dataArray = colorBand.data;
+			
+			if(dataArray.length==1) {//special case; use only one color for all types of colorband interpolation
+				for(int i=0;i<result.length;++i) {
+					result[i][0] = dataArray[0].r;
+					result[i][1] = dataArray[0].g;
+					result[i][2] = dataArray[0].b;
+					result[i][3] = dataArray[0].a;
+				}
+			} else {
+				int currentCursor = 0;
+				ColorBandData currentData = dataArray[0];
+				ColorBandData nextData = dataArray[0];
+				switch(colorBand.ipoType) {
+					case ColorBand.IPO_LINEAR:
+						float rDiff = 0, gDiff = 0, bDiff = 0, aDiff = 0, posDiff;
+						for(int i=0;i<result.length;++i) {
+							posDiff = i - currentData.pos;
+							result[i][0] = currentData.r + rDiff * posDiff;
+							result[i][1] = currentData.g + gDiff * posDiff;
+							result[i][2] = currentData.b + bDiff * posDiff;
+							result[i][3] = currentData.a + aDiff * posDiff;
+							if(nextData.pos==i) {
+								currentData = dataArray[currentCursor++];
+								if(currentCursor < dataArray.length) {
+									nextData = dataArray[currentCursor];
+									//calculate differences
+									int d = nextData.pos - currentData.pos;
+									rDiff = (nextData.r - currentData.r)/d;
+									gDiff = (nextData.g - currentData.g)/d;
+									bDiff = (nextData.b - currentData.b)/d;
+									aDiff = (nextData.a - currentData.a)/d;
+								} else {
+									rDiff = gDiff = bDiff = aDiff = 0;
+								}							
+							}
+						}
+						break;
+					case ColorBand.IPO_BSPLINE:
+					case ColorBand.IPO_CARDINAL:
+						Map<Integer, ColorBandData> cbDataMap = new TreeMap<Integer, ColorBandData>();
+						for(int i=0;i<colorBand.data.length;++i) {
+							cbDataMap.put(Integer.valueOf(i), colorBand.data[i]);
+						}
+						
+						if(colorBand.data[0].pos==0) {
+							cbDataMap.put(Integer.valueOf(-1), colorBand.data[0]);
+						} else {
+							ColorBandData cbData = colorBand.data[0].clone();
+							cbData.pos = 0;
+							cbDataMap.put(Integer.valueOf(-1), cbData);
+							cbDataMap.put(Integer.valueOf(-2), cbData);
+						}
+						
+						if(colorBand.data[colorBand.data.length - 1].pos==1000) {
+							cbDataMap.put(Integer.valueOf(colorBand.data.length), colorBand.data[colorBand.data.length - 1]);
+						} else {
+							ColorBandData cbData = colorBand.data[colorBand.data.length - 1].clone();
+							cbData.pos = 1000;
+							cbDataMap.put(Integer.valueOf(colorBand.data.length), cbData);
+							cbDataMap.put(Integer.valueOf(colorBand.data.length + 1), cbData);
+						}
+						
+						float[] ipoFactors = new float[4];
+						float f;
+						
+						ColorBandData data0 = cbDataMap.get(currentCursor - 2);
+						ColorBandData data1 = cbDataMap.get(currentCursor - 1);
+						ColorBandData data2 = cbDataMap.get(currentCursor);
+						ColorBandData data3 = cbDataMap.get(currentCursor + 1);
+						
+						for(int i=0;i<result.length;++i) {
+							if (data2.pos != data1.pos) {
+		                        f = (i - data2.pos) / (float)(data1.pos - data2.pos);
+		                    } else {
+		                        f = 0.0f;
+		                    }
+							
+							f = FastMath.clamp(f, 0.0f, 1.0f);
+							
+							this.getIpoData(colorBand, f, ipoFactors);
+							result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r;
+							result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g;
+							result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b;
+							result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a;
+							result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f);
+							result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f);
+							result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f);
+							result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f);
+							
+							if(nextData.pos==i) {
+								++currentCursor;
+								data0 = cbDataMap.get(currentCursor - 2);
+								data1 = cbDataMap.get(currentCursor - 1);
+								data2 = cbDataMap.get(currentCursor);
+								data3 = cbDataMap.get(currentCursor + 1);
+							}
+						}
+						break;
+					case ColorBand.IPO_EASE:
+						float d, a, b, d2;
+						for(int i=0;i<result.length;++i) {
+							if(nextData.pos != currentData.pos) {
+								d = (i - currentData.pos) / (float)(nextData.pos - currentData.pos);
+								d2 = d * d;
+								a = 3.0f * d2 - 2.0f * d * d2;
+								b = 1.0f - a;
+							} else {
+								d = a = 0.0f;
+								b = 1.0f;
+							}
+							
+							result[i][0] = b * currentData.r + a * nextData.r;
+							result[i][1] = b * currentData.g + a * nextData.g;
+							result[i][2] = b * currentData.b + a * nextData.b;
+							result[i][3] = b * currentData.a + a * nextData.a;
+							if(nextData.pos==i) {
+								currentData = dataArray[currentCursor++];
+								if(currentCursor < dataArray.length) {
+									nextData = dataArray[currentCursor];
+								}						
+							}
+						}
+						break;
+					case ColorBand.IPO_CONSTANT:
+						for(int i=0;i<result.length;++i) {
+							result[i][0] = currentData.r;
+							result[i][1] = currentData.g;
+							result[i][2] = currentData.b;
+							result[i][3] = currentData.a;
+							if(nextData.pos==i) {
+								currentData = dataArray[currentCursor++];
+								if(currentCursor < dataArray.length) {
+									nextData = dataArray[currentCursor];
+								}
+							}
+						}
+						break;
+					default:
+						throw new IllegalStateException("Unknown interpolation type: " + colorBand.ipoType);
+				}
+			}
+		}
+		return result;
+	}
+	
+	/**
+	 * This method returns the data for either B-spline of Cardinal interpolation.
+	 * @param colorBand the color band
+	 * @param d distance factor for the current intensity
+	 * @param ipoFactors table to store the results (size of the table must be at least 4)
+	 */
+	private void getIpoData(ColorBand colorBand, float d, float[] ipoFactors) {
+		float d2 = d * d;
+		float d3 = d2 * d;
+		if(colorBand.ipoType==ColorBand.IPO_BSPLINE) {
+			ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d;
+			ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f;
+			ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d;
+			ipoFactors[3] = 0.71f * d3 - 0.71f * d2;
+		} else if(colorBand.ipoType==ColorBand.IPO_CARDINAL) {
+			ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
+			ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f;
+			ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
+			ipoFactors[3] = 0.16666666f * d3;
+		} else {
+			throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!");
+		}
+	}
+	
+	/**
+	 * This method applies brightness and contrast for RGB textures.
+	 * @param tex texture structure
+	 * @param texres
+	 */
+	protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) {
+        texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness;
+        if (texres.red < 0.0f) {
+            texres.red = 0.0f;
+        }
+        texres.green =(texres.green - 0.5f) * bacd.contrast + bacd.brightness;
+        if (texres.green < 0.0f) {
+            texres.green = 0.0f;
+        }
+        texres.blue = (texres.blue - 0.5f) * bacd.contrast + bacd.brightness;
+        if (texres.blue < 0.0f) {
+            texres.blue = 0.0f;
+        }
+    }
+	
+	/**
+	 * This method applies brightness and contrast for Luminance textures.
+	 * @param texres
+	 * @param contrast
+	 * @param brightness
+	 */
+	protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) {
+        texres.intensity = (texres.intensity - 0.5f) * contrast + brightness;
+        if (texres.intensity < 0.0f) {
+            texres.intensity = 0.0f;
+        } else if (texres.intensity > 1.0f) {
+            texres.intensity = 1.0f;
+        }
+    }
+	
+	/**
+	 * A class constaining the colorband data.
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class ColorBand {
+		//interpolation types
+		public static final int IPO_LINEAR 		= 0;
+		public static final int IPO_EASE 		= 1;
+		public static final int IPO_BSPLINE 	= 2;
+		public static final int IPO_CARDINAL 	= 3;
+		public static final int IPO_CONSTANT 	= 4;
+
+		public int		cursorsAmount, ipoType;
+		public ColorBandData[]	data;
+
+		/**
+		 * Constructor. Loads the data from the given structure.
+		 * 
+		 * @param cbdataStructure
+		 *        the colorband structure
+		 */
+		@SuppressWarnings("unchecked")
+		public ColorBand(Structure colorbandStructure) {
+			this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
+			this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
+			this.data = new ColorBandData[this.cursorsAmount];
+			DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
+			for (int i = 0; i < this.cursorsAmount; ++i) {
+				this.data[i] = new ColorBandData(data.get(i));
+			}
+		}
+	}
+
+	/**
+	 * Class to store the single colorband cursor data.
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class ColorBandData implements Cloneable {
+		public final float	r, g, b, a;
+		public int 	pos;
+
+		/**
+		 * Copy constructor.
+		 */
+		private ColorBandData(ColorBandData data) {
+			this.r = data.r;
+			this.g = data.g;
+			this.b = data.b;
+			this.a = data.a;
+			this.pos = data.pos;
+		}
+
+		/**
+		 * Constructor. Loads the data from the given structure.
+		 * 
+		 * @param cbdataStructure
+		 *        the structure containing the CBData object
+		 */
+		public ColorBandData(Structure cbdataStructure) {
+			this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue();
+			this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue();
+			this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue();
+			this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue();
+			this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f);
+		}
+
+		@Override
+		public ColorBandData clone() {
+			try {
+				return (ColorBandData) super.clone();
+			} catch (CloneNotSupportedException e) {
+				return new ColorBandData(this);
+			}
+		}
+
+		@Override
+		public String toString() {
+			return "P: " + this.pos + " [" + this.r+", "+this.g+", "+this.b+", "+this.a+"]";
+		}
+	}
+	
+	/**
+	 * This class contains brightness and contrast data.
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class BrightnessAndContrastData {
+		public final float contrast;
+        public final float brightness;
+        public final float rFactor;
+        public final float gFactor;
+        public final float bFactor;
+        
+        /**
+         * Constructor reads the required data from the given structure.
+         * @param tex texture structure
+         */
+		public BrightnessAndContrastData(Structure tex) {
+			contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+	        brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f;
+	        rFactor = ((Number) tex.getFieldValue("rfac")).floatValue();
+	        gFactor = ((Number) tex.getFieldValue("gfac")).floatValue();
+	        bFactor = ((Number) tex.getFieldValue("bfac")).floatValue();
+		}
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorBlend.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorBlend.java
new file mode 100644
index 0000000..18ef409
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorBlend.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'blend' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class TextureGeneratorBlend extends TextureGenerator {
+    
+    private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7];
+    static {
+    	INTENSITY_FUNCTION[0] = new IntensityFunction() {//Linear: stype = 0 (TEX_LIN)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				return (1.0f + x) * 0.5f;
+			}
+		};
+		INTENSITY_FUNCTION[1] = new IntensityFunction() {//Quad: stype = 1 (TEX_QUAD)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				float result = (1.0f + x) * 0.5f;
+				return result * result;
+			}
+		};
+		INTENSITY_FUNCTION[2] = new IntensityFunction() {//Ease: stype = 2 (TEX_EASE)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				float result = (1.0f + x) * 0.5f;
+				if (result <= 0.0f) {
+					return 0.0f;
+				} else if (result >= 1.0f) {
+					return 1.0f;
+				} else {
+					return result * result *(3.0f - 2.0f * result);
+				}
+			}
+		};
+		INTENSITY_FUNCTION[3] = new IntensityFunction() {//Diagonal: stype = 3 (TEX_DIAG)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				return (2.0f + x + y) * 0.25f;
+			}
+		};
+		INTENSITY_FUNCTION[4] = new IntensityFunction() {//Sphere: stype = 4 (TEX_SPHERE)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
+				return result < 0.0f ? 0.0f : result;
+			}
+		};
+		INTENSITY_FUNCTION[5] = new IntensityFunction() {//Halo: stype = 5 (TEX_HALO)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
+				return result <= 0.0f ? 0.0f : result * result;
+			}
+		};
+		INTENSITY_FUNCTION[6] = new IntensityFunction() {//Radial: stype = 6 (TEX_RAD)
+			@Override
+			public float getIntensity(float x, float y, float z) {
+				return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f;
+			}
+		};
+    }
+    
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorBlend(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		int flag = ((Number) tex.getFieldValue("flag")).intValue();
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD, x, y;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		boolean flipped = (flag & NoiseGenerator.TEX_FLIPBLEND) != 0;
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			x = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				if (flipped) {
+					y = x;
+					x = hDelta * j;
+				} else {
+					y = hDelta * j;
+				}
+				for (int k = -halfD; k < halfD; ++k) {
+					texres.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, dDelta * k);
+					
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+	private static interface IntensityFunction {
+		float getIntensity(float x, float y, float z);
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorClouds.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorClouds.java
new file mode 100644
index 0000000..9ac24e5
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorClouds.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'clouds' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorClouds extends TextureGenerator {
+	// tex->noisetype
+    protected static final int TEX_NOISESOFT = 0;
+    protected static final int TEX_NOISEPERL = 1;
+    
+    // tex->stype
+    protected static final int TEX_DEFAULT = 0;
+    protected static final int TEX_COLOR = 1;
+    
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorClouds(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexturePixel texres = new TexturePixel();
+
+		// reading the data from the texture structure
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		int noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		int noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
+		boolean isHard = noiseType != TEX_NOISESOFT;
+		int sType = ((Number) tex.getFieldValue("stype")).intValue();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = sType == TEX_COLOR || colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = sType == TEX_COLOR || colorBand != null ? 4 : 1;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					texres.intensity = NoiseGenerator.NoiseFunctions.turbulence(texvec[0], texvec[1], texvec[2], noisesize, noiseDepth, noiseBasis, isHard);
+					texres.intensity = FastMath.clamp(texres.intensity, 0.0f, 1.0f);
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else if (sType == TEX_COLOR) {
+						texres.red = texres.intensity;
+						texres.green = NoiseGenerator.NoiseFunctions.turbulence(texvec[1], texvec[0], texvec[2], noisesize, noiseDepth, noiseBasis, isHard);
+						texres.blue = NoiseGenerator.NoiseFunctions.turbulence(texvec[1], texvec[2], texvec[0], noisesize, noiseDepth, noiseBasis, isHard);
+						
+						texres.green = FastMath.clamp(texres.green, 0.0f, 1.0f);
+						texres.blue = FastMath.clamp(texres.blue, 0.0f, 1.0f);
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (255);//1.0f * 255.0f
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorDistnoise.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorDistnoise.java
new file mode 100644
index 0000000..f66beb6
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorDistnoise.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.textures.NoiseGenerator.NoiseFunction;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'distorted noise' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorDistnoise extends TextureGenerator {
+
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		float distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue();
+		int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue();
+
+		TexturePixel texres = new TexturePixel();
+		float[] texvec = new float[] { 0, 0, 0 };
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					texres.intensity = this.musgraveVariableLunacrityNoise(texvec[0], texvec[1], texvec[2], distAmount, noisebasis, noisebasis2);
+					texres.intensity = FastMath.clamp(texres.intensity, 0.0f, 1.0f);
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+	/**
+     * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise
+     * texture.
+     * @param x
+     * @param y
+     * @param z
+     * @param distortion
+     * @param nbas1
+     * @param nbas2
+     * @return
+     */
+    private float musgraveVariableLunacrityNoise(float x, float y, float z, float distortion, int nbas1, int nbas2) {
+        NoiseFunction abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas1));
+        if (abstractNoiseFunc1 == null) {
+            abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0));
+        }
+        NoiseFunction abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas2));
+        if (abstractNoiseFunc2 == null) {
+            abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0));
+        }
+        // get a random vector and scale the randomization
+        float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion;
+        float ry = abstractNoiseFunc1.execute(x, y, z) * distortion;
+        float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion;
+        return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); //distorted-domain noise
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMagic.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMagic.java
new file mode 100644
index 0000000..e8a1637
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMagic.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'magic' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorMagic extends TextureGenerator {
+	private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10];
+	static {
+		noiseDepthFunctions[0] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[1] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[2] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[3] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[4] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[5] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[6] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[7] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[8] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+		noiseDepthFunctions[9] = new NoiseDepthFunction() {
+			@Override
+			public void compute(float[] xyz, float turbulence) {
+				xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence;
+			}
+		};
+	}
+	
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorMagic(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float xyz[] = new float[3], turb;
+		int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		float turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		byte[] data = new byte[width * height * depth * 4];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				for (int k = -halfD; k < halfD; ++k) {
+					turb = turbul;
+					texvec[2] = dDelta * k;
+					xyz[0] = (float) Math.sin((texvec[0] + texvec[1] + texvec[2]) * 5.0f);
+					xyz[1] = (float) Math.cos((-texvec[0] + texvec[1] - texvec[2]) * 5.0f);
+					xyz[2] = -(float) Math.cos((-texvec[0] - texvec[1] + texvec[2]) * 5.0f);
+
+					if (colorBand != null) {
+						texres.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f);
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						texres.alpha = colorBand[colorbandIndex][3];
+					} else {
+						if (noisedepth > 0) {
+							xyz[0] *= turb;
+							xyz[1] *= turb;
+							xyz[2] *= turb;
+							for (int m=0;m<noisedepth;++m) {
+								noiseDepthFunctions[m].compute(xyz, turb);
+							}
+						}
+
+						if (turb != 0.0f) {
+							turb *= 2.0f;
+							xyz[0] /= turb;
+							xyz[1] /= turb;
+							xyz[2] /= turb;
+						}
+						texres.red = 0.5f - xyz[0];
+						texres.green = 0.5f - xyz[1];
+						texres.blue = 0.5f - xyz[2];
+						texres.alpha = 1.0f;
+					}
+					this.applyBrightnessAndContrast(bacd, texres);
+					data[index++] = (byte) (texres.red * 255.0f);
+					data[index++] = (byte) (texres.green * 255.0f);
+					data[index++] = (byte) (texres.blue * 255.0f);
+					data[index++] = (byte) (texres.alpha * 255.0f);
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray));
+	}
+	
+	private static interface NoiseDepthFunction {
+		void compute(float[] xyz, float turbulence);
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMarble.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMarble.java
new file mode 100644
index 0000000..06c08a9
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMarble.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'marble' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorMarble extends TextureGeneratorWood {
+	// tex->stype
+    protected static final int TEX_SOFT = 0;
+    protected static final int TEX_SHARP = 1;
+    protected static final int TEX_SHARPER = 2;
+    
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorMarble(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		MarbleData marbleData = new MarbleData(tex);
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					texres.intensity = this.marbleInt(marbleData, texvec[0], texvec[1], texvec[2]);
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+    public float marbleInt(MarbleData marbleData, float x, float y, float z) {
+    	int waveform;
+        if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) {
+        	waveform = 0;
+        } else {
+        	waveform = marbleData.waveform;
+        }
+
+        float n = 5.0f * (x + y + z);
+        float mi = n + marbleData.turbul * NoiseGenerator.NoiseFunctions.turbulence(x, y, z, marbleData.noisesize, marbleData.noisedepth, marbleData.noisebasis, marbleData.isHard);
+
+        if (marbleData.stype >= TEX_SOFT) {
+            mi = waveformFunctions[waveform].execute(mi);
+            if (marbleData.stype == TEX_SHARP) {
+                mi = (float) Math.sqrt(mi);
+            } else if (marbleData.stype == TEX_SHARPER) {
+                mi = (float) Math.sqrt(Math.sqrt(mi));
+            }
+        }
+        return mi;
+    }
+    
+    private static class MarbleData {
+    	public final float noisesize;
+    	public final int noisebasis;
+    	public final int noisedepth;
+    	public final int stype;
+    	public final float turbul;
+    	public final int waveform;
+    	public final boolean isHard;
+        
+        public MarbleData(Structure tex) {
+        	noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+            noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+            noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+            stype = ((Number) tex.getFieldValue("stype")).intValue();
+            turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
+            int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
+            waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();
+            isHard = noisetype != TEX_NOISESOFT;
+		}
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMusgrave.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMusgrave.java
new file mode 100644
index 0000000..667063f
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorMusgrave.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.textures.NoiseGenerator.MusgraveFunction;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'musgrave' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorMusgrave extends TextureGenerator {
+
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		TexturePixel texres = new TexturePixel();
+		float[] texvec = new float[] { 0, 0, 0 };
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		MusgraveData musgraveData = new MusgraveData(tex);
+		MusgraveFunction musgraveFunction;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k / noisesize;
+					musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype));
+					if(musgraveFunction==null) {
+						throw new IllegalStateException("Unknown type of musgrave texture: " + stype);
+					}
+					texres.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, texvec[0], texvec[1], texvec[2]);
+					if(texres.intensity>1) {
+						texres.intensity = 1.0f;
+					} else if(texres.intensity < 0) {
+						texres.intensity = 0.0f;
+					}
+					
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+    protected static class MusgraveData {
+    	public final int stype;
+    	public final float outscale;
+    	public final float h;
+    	public final float lacunarity;
+    	public final float octaves;
+    	public final int noisebasis;
+    	public final float offset;
+    	public final float gain;
+        
+    	public MusgraveData(Structure tex) {
+    		stype = ((Number) tex.getFieldValue("stype")).intValue();
+            outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue();
+            h = ((Number) tex.getFieldValue("mg_H")).floatValue();
+            lacunarity = ((Number) tex.getFieldValue("mg_lacunarity")).floatValue();
+            octaves = ((Number) tex.getFieldValue("mg_octaves")).floatValue();
+            noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+            offset = ((Number) tex.getFieldValue("mg_offset")).floatValue();
+            gain = ((Number) tex.getFieldValue("mg_gain")).floatValue();
+		}
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorNoise.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorNoise.java
new file mode 100644
index 0000000..02d4687
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorNoise.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'noise' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorNoise extends TextureGenerator {
+
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorNoise(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		int val, random, loop;
+		int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			for (int j = -halfH; j < halfH; ++j) {
+				for (int k = -halfD; k < halfD; ++k) {
+					random = FastMath.rand.nextInt();
+					val = random & 3;
+
+					loop = noisedepth;
+					while (loop-- != 0) {
+						random >>= 2;
+						val *= random & 3;
+					}
+					texres.intensity = FastMath.clamp(val, 0.0f, 1.0f);
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorStucci.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorStucci.java
new file mode 100644
index 0000000..76a6614
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorStucci.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'stucci' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorStucci extends TextureGenerator {
+	protected static final int TEX_NOISESOFT = 0;
+	
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorStucci(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
+		float turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
+		boolean isHard = noisetype != TEX_NOISESOFT;
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+
+		if(noisesize<=0.001f) {//the texture goes black if this value is lower than 0.001f
+			noisesize = 0.001f;
+		}
+		
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD, noiseValue, ofs;;
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					noiseValue = NoiseGenerator.NoiseFunctions.noise(texvec[0], texvec[1], texvec[2], noisesize, 0, noisebasis, isHard);
+					ofs = turbul / 200.0f;
+					if (stype != 0) {
+						ofs *= noiseValue * noiseValue;
+					}
+
+					texres.intensity = NoiseGenerator.NoiseFunctions.noise(texvec[0], texvec[1], texvec[2] + ofs, noisesize, 0, noisebasis, isHard);
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						texres.alpha = colorBand[colorbandIndex][3];
+					}
+
+					if (stype == NoiseGenerator.TEX_WALLOUT) {
+						texres.intensity = 1.0f - texres.intensity;
+					}
+					if (texres.intensity < 0.0f) {
+						texres.intensity = 0.0f;
+					}
+					//no brightness and contrast needed for stucci (it doesn't affect the texture)
+					if (colorBand != null) {
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (texres.alpha * 255.0f);
+					} else {
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorVoronoi.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorVoronoi.java
new file mode 100644
index 0000000..1dee158
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorVoronoi.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.textures.NoiseGenerator.NoiseMath;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'voronoi' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorVoronoi extends TextureGenerator {
+
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator
+	 *        the noise generator
+	 */
+	public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float voronoiWeight1 = ((Number) tex.getFieldValue("vn_w1")).floatValue();
+		float voronoiWeight2 = ((Number) tex.getFieldValue("vn_w2")).floatValue();
+		float voronoiWeight3 = ((Number) tex.getFieldValue("vn_w3")).floatValue();
+		float voronoiWeight4 = ((Number) tex.getFieldValue("vn_w4")).floatValue();
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		float outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue();
+		float mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue();
+		int distm = ((Number) tex.getFieldValue("vn_distm")).intValue();
+		int voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue();
+
+		TexturePixel texres = new TexturePixel();
+		float[] texvec = new float[] { 0, 0, 0 };
+		int halfW = width >> 1, halfH = height >> 1, halfD = depth >> 1, index = 0;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = voronoiColorType != 0 || colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = voronoiColorType != 0 || colorBand != null ? 4 : 1;
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		float[] da = new float[4], pa = new float[12];
+		float[] hashPoint = voronoiColorType != 0 ? new float[3] : null;
+		float[] voronoiWeights = new float[] {FastMath.abs(voronoiWeight1), FastMath.abs(voronoiWeight2), 
+											  FastMath.abs(voronoiWeight3), FastMath.abs(voronoiWeight4)};
+		float weight;
+		float sc = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3];
+		if (sc != 0.0f) {
+			sc = outscale / sc;
+		}
+
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+				for (int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					NoiseGenerator.NoiseFunctions.voronoi(texvec[0], texvec[1], texvec[2], da, pa, mexp, distm);
+					texres.intensity = sc * FastMath.abs(voronoiWeight1 * da[0] + voronoiWeight2 * da[1] + voronoiWeight3 * da[2] + voronoiWeight4 * da[3]);
+					if(texres.intensity>1.0f) {
+						texres.intensity = 1.0f;
+					} else if(texres.intensity<0.0f) {
+						texres.intensity = 0.0f;
+					}
+					
+					if (colorBand != null) {//colorband ALWAYS goes first and covers the color (if set)
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						texres.alpha = colorBand[colorbandIndex][3];
+					} else if (voronoiColorType != 0) {
+						texres.red = texres.green = texres.blue = 0.0f;
+						texres.alpha = 1.0f;
+						for(int m=0; m<12; m+=3) {
+							weight = voronoiWeights[m/3];
+							this.cellNoiseV(pa[m], pa[m + 1], pa[m + 2], hashPoint);
+							texres.red += weight * hashPoint[0];
+							texres.green += weight * hashPoint[1];
+							texres.blue += weight * hashPoint[2];
+						}
+						if (voronoiColorType >= 2) {
+							float t1 = (da[1] - da[0]) * 10.0f;
+							if (t1 > 1.0f) {
+								t1 = 1.0f;
+							}
+							if (voronoiColorType == 3) {
+								t1 *= texres.intensity;
+							} else {
+								t1 *= sc;
+							}
+							texres.red *= t1;
+							texres.green *= t1;
+							texres.blue *= t1;
+						} else {
+							texres.red *= sc;
+							texres.green *= sc;
+							texres.blue *= sc;
+						}
+					}
+
+					if (voronoiColorType != 0 || colorBand != null) {
+						this.applyBrightnessAndContrast(bacd, texres);
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (texres.alpha * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+	/**
+     * Returns a vector/point/color in ca, using point hasharray directly
+     */
+    private void cellNoiseV(float x, float y, float z, float[] hashPoint) {
+        int xi = (int) Math.floor(x);
+        int yi = (int) Math.floor(y);
+        int zi = (int) Math.floor(z);
+        NoiseMath.hash(xi, yi, zi, hashPoint);
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorWood.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorWood.java
new file mode 100644
index 0000000..bf5e050
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureGeneratorWood.java
@@ -0,0 +1,216 @@
+/*
+ *
+ * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * This class generates the 'wood' texture.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TextureGeneratorWood extends TextureGenerator {
+	// tex->noisebasis2
+    protected static final int TEX_SIN = 0;
+    protected static final int TEX_SAW = 1;
+    protected static final int TEX_TRI = 2;
+    
+    // tex->stype
+    protected static final int TEX_BAND = 0;
+    protected static final int TEX_RING = 1;
+    protected static final int TEX_BANDNOISE = 2;
+    protected static final int TEX_RINGNOISE = 3;
+    
+    // tex->noisetype
+    protected static final int TEX_NOISESOFT = 0;
+    protected static final int TEX_NOISEPERL = 1;
+    
+	/**
+	 * Constructor stores the given noise generator.
+	 * @param noiseGenerator the noise generator
+	 */
+	public TextureGeneratorWood(NoiseGenerator noiseGenerator) {
+		super(noiseGenerator);
+	}
+
+	@Override
+	protected Texture generate(Structure tex, int width, int height, int depth, BlenderContext blenderContext) {
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexturePixel texres = new TexturePixel();
+		int halfW = width >> 1;
+		int halfH = height >> 1;
+		int halfD = depth >> 1;
+		float wDelta = 1.0f / halfW, hDelta = 1.0f / halfH, dDelta = 1.0f / halfD;
+		
+		float[][] colorBand = this.computeColorband(tex, blenderContext);
+		Format format = colorBand != null ? Format.RGBA8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 4 : 1;
+		WoodIntensityData woodIntensityData = new WoodIntensityData(tex);
+		BrightnessAndContrastData bacd = new BrightnessAndContrastData(tex);
+		
+		int index = 0;
+		byte[] data = new byte[width * height * depth * bytesPerPixel];
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				for(int k = -halfD; k < halfD; ++k) {
+					texvec[2] = dDelta * k;
+					texres.intensity = this.woodIntensity(woodIntensityData, texvec[0], texvec[1], texvec[2]);
+					
+					if (colorBand != null) {
+						int colorbandIndex = (int) (texres.intensity * 1000.0f);
+						texres.red = colorBand[colorbandIndex][0];
+						texres.green = colorBand[colorbandIndex][1];
+						texres.blue = colorBand[colorbandIndex][2];
+						
+						this.applyBrightnessAndContrast(bacd, texres);
+						
+						data[index++] = (byte) (texres.red * 255.0f);
+						data[index++] = (byte) (texres.green * 255.0f);
+						data[index++] = (byte) (texres.blue * 255.0f);
+						data[index++] = (byte) (colorBand[colorbandIndex][3] * 255.0f);
+					} else {
+						this.applyBrightnessAndContrast(texres, bacd.contrast, bacd.brightness);
+						data[index++] = (byte) (texres.intensity * 255.0f);
+					}
+				}
+			}
+		}
+		
+		ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);
+		dataArray.add(BufferUtils.createByteBuffer(data));
+		return new Texture3D(new Image(format, width, height, depth, dataArray));
+	}
+	
+    protected static WaveForm[] waveformFunctions = new WaveForm[3];
+    static {
+        waveformFunctions[0] = new WaveForm() {// sinus (TEX_SIN)
+
+            @Override
+            public float execute(float x) {
+                return 0.5f + 0.5f * (float) Math.sin(x);
+            }
+        };
+        waveformFunctions[1] = new WaveForm() {// saw (TEX_SAW)
+
+            @Override
+            public float execute(float x) {
+                int n = (int) (x * FastMath.INV_TWO_PI);
+                x -= n * FastMath.TWO_PI;
+                if (x < 0.0f) {
+                    x += FastMath.TWO_PI;
+                }
+                return x * FastMath.INV_TWO_PI;
+            }
+        };
+        waveformFunctions[2] = new WaveForm() {// triangle (TEX_TRI)
+
+            @Override
+            public float execute(float x) {
+                return 1.0f - 2.0f * FastMath.abs((float) Math.floor(x * FastMath.INV_TWO_PI + 0.5f) - x * FastMath.INV_TWO_PI);
+            }
+        };
+    }
+
+    /**
+     * Computes basic wood intensity value at x,y,z.
+     * @param woodIntData
+     * @param x X coordinate of the texture pixel
+     * @param y Y coordinate of the texture pixel
+     * @param z Z coordinate of the texture pixel
+     * @return wood intensity at position [x, y, z]
+     */
+    public float woodIntensity(WoodIntensityData woodIntData, float x, float y, float z) {
+        float result;
+
+        switch(woodIntData.woodType) {
+	        case TEX_BAND:
+	        	result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f);
+	        	break;
+	        case TEX_RING:
+	        	result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f);
+	        	break;
+	        case TEX_BANDNOISE:
+	        	result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard);
+	            result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f + result);
+	        	break;
+	        case TEX_RINGNOISE:
+	        	result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard);
+	            result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f + result);
+	        	break;
+        	default:
+        		result = 0;
+        }
+        return result;
+    }
+    
+    /**
+     * A class that collects the data for wood intensity calculations.
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class WoodIntensityData {
+    	public final WaveForm waveformFunction;
+    	public final int noisebasis;
+    	public final float noisesize;
+    	public final float turbul;
+    	public final int noiseType;
+    	public final int woodType;
+    	public final boolean isHard;
+        
+    	public WoodIntensityData(Structure tex) {
+    		int waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();//wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2
+    		if (waveform > TEX_TRI || waveform < TEX_SIN) {
+                waveform = 0; // check to be sure noisebasis2 is initialized ahead of time
+            }
+    		waveformFunction = waveformFunctions[waveform];
+    		noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+            woodType = ((Number) tex.getFieldValue("stype")).intValue();
+            noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+            turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
+            noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
+            isHard = noiseType != TEX_NOISESOFT;
+		}
+    }
+    
+    protected static interface WaveForm {
+
+        float execute(float x);
+    }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java
new file mode 100644
index 0000000..0e0861d
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java
@@ -0,0 +1,481 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.plugins.blender.textures;

+

+import java.awt.color.ColorSpace;

+import java.awt.image.BufferedImage;

+import java.awt.image.ColorConvertOp;

+import java.nio.ByteBuffer;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import jme3tools.converters.ImageToAwt;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.asset.AssetNotFoundException;

+import com.jme3.asset.BlenderKey;

+import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.asset.GeneratedTextureKey;

+import com.jme3.asset.TextureKey;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;

+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;

+import com.jme3.scene.plugins.blender.file.FileBlockHeader;

+import com.jme3.scene.plugins.blender.file.Pointer;

+import com.jme3.scene.plugins.blender.file.Structure;

+import com.jme3.scene.plugins.blender.materials.MaterialContext;

+import com.jme3.texture.Image;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.MinFilter;

+import com.jme3.texture.Texture.WrapMode;

+import com.jme3.texture.Texture2D;

+import com.jme3.texture.Texture3D;

+import com.jme3.util.BufferUtils;

+

+/**

+ * A class that is used in texture calculations.

+ * 

+ * @author Marcin Roguski

+ */

+public class TextureHelper extends AbstractBlenderHelper {

+	private static final Logger	LOGGER				= Logger.getLogger(TextureHelper.class.getName());

+

+	// texture types

+	public static final int		TEX_NONE			= 0;

+	public static final int		TEX_CLOUDS			= 1;

+	public static final int		TEX_WOOD			= 2;

+	public static final int		TEX_MARBLE			= 3;

+	public static final int		TEX_MAGIC			= 4;

+	public static final int		TEX_BLEND			= 5;

+	public static final int		TEX_STUCCI			= 6;

+	public static final int		TEX_NOISE			= 7;

+	public static final int		TEX_IMAGE			= 8;

+	public static final int		TEX_PLUGIN			= 9;

+	public static final int		TEX_ENVMAP			= 10;

+	public static final int		TEX_MUSGRAVE		= 11;

+	public static final int		TEX_VORONOI			= 12;

+	public static final int		TEX_DISTNOISE		= 13;

+	public static final int 	TEX_POINTDENSITY 	= 14;//v. 25+

+	public static final int 	TEX_VOXELDATA 		= 15;//v. 25+

+

+	// mapto

+	public static final int		MAP_COL				= 1;

+	public static final int		MAP_NORM			= 2;

+	public static final int		MAP_COLSPEC			= 4;

+	public static final int		MAP_COLMIR			= 8;

+	public static final int		MAP_VARS			= 0xFFF0;

+	public static final int		MAP_REF				= 16;

+	public static final int		MAP_SPEC			= 32;

+	public static final int		MAP_EMIT			= 64;

+	public static final int		MAP_ALPHA			= 128;

+	public static final int		MAP_HAR				= 256;

+	public static final int		MAP_RAYMIRR			= 512;

+	public static final int		MAP_TRANSLU			= 1024;

+	public static final int		MAP_AMB				= 2048;

+	public static final int		MAP_DISPLACE		= 4096;

+	public static final int		MAP_WARP			= 8192;

+	public static final int		MAP_LAYER			= 16384;

+

+	protected NoiseGenerator noiseGenerator;

+	private Map<Integer, TextureGenerator> textureGenerators = new HashMap<Integer, TextureGenerator>();

+

+	/**

+	 * This constructor parses the given blender version and stores the result.

+	 * It creates noise generator and texture generators.

+	 * 

+	 * @param blenderVersion

+	 *        the version read from the blend file

+	 * @param fixUpAxis

+     *        a variable that indicates if the Y asxis is the UP axis or not

+	 */

+	public TextureHelper(String blenderVersion, boolean fixUpAxis) {

+		super(blenderVersion, false);

+		noiseGenerator = new NoiseGenerator(blenderVersion);

+		textureGenerators.put(Integer.valueOf(TEX_BLEND), new TextureGeneratorBlend(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_CLOUDS), new TextureGeneratorClouds(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_DISTNOISE), new TextureGeneratorDistnoise(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_MAGIC), new TextureGeneratorMagic(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_MARBLE), new TextureGeneratorMarble(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_MUSGRAVE), new TextureGeneratorMusgrave(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_NOISE), new TextureGeneratorNoise(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_STUCCI), new TextureGeneratorStucci(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_VORONOI), new TextureGeneratorVoronoi(noiseGenerator));

+		textureGenerators.put(Integer.valueOf(TEX_WOOD), new TextureGeneratorWood(noiseGenerator));

+	}

+

+	/**

+	 * This class returns a texture read from the file or from packed blender data. The returned texture has the name set to the value of

+	 * its blender type.

+	 * 

+	 * @param tex

+	 *        texture structure filled with data

+	 * @param blenderContext

+	 *        the blender context

+	 * @return the texture that can be used by JME engine

+	 * @throws BlenderFileException

+	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted

+	 */

+	public Texture getTexture(Structure tex, BlenderContext blenderContext) throws BlenderFileException {

+		Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+		if (result != null) {

+			return result;

+		}

+		int type = ((Number) tex.getFieldValue("type")).intValue();

+		int width = blenderContext.getBlenderKey().getGeneratedTextureWidth();

+		int height = blenderContext.getBlenderKey().getGeneratedTextureHeight();

+		int depth = blenderContext.getBlenderKey().getGeneratedTextureDepth();

+

+		switch (type) {

+		case TEX_IMAGE:// (it is first because probably this will be most commonly used)

+			Pointer pImage = (Pointer) tex.getFieldValue("ima");

+			if (pImage.isNotNull()){

+				Structure image = pImage.fetchData(blenderContext.getInputStream()).get(0);

+				result = this.getTextureFromImage(image, blenderContext);

+			}

+			break;

+		case TEX_CLOUDS:

+		case TEX_WOOD:

+		case TEX_MARBLE:

+		case TEX_MAGIC:

+		case TEX_BLEND:

+		case TEX_STUCCI:

+		case TEX_NOISE:

+		case TEX_MUSGRAVE:

+		case TEX_VORONOI:

+		case TEX_DISTNOISE:

+			TextureGenerator textureGenerator = textureGenerators.get(Integer.valueOf(type));

+			result = textureGenerator.generate(tex, width, height, depth, blenderContext);

+			break;

+		case TEX_NONE:// No texture, do nothing

+			break;

+		case TEX_POINTDENSITY:

+			LOGGER.warning("Point density texture loading currently not supported!");

+			break;

+		case TEX_VOXELDATA:

+			LOGGER.warning("Voxel data texture loading currently not supported!");

+			break;

+		case TEX_PLUGIN:

+		case TEX_ENVMAP:// TODO: implement envmap texture

+			LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[]{type, tex.getName()});

+			break;

+		default:

+			throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName());

+		}

+		if (result != null) {

+			result.setName(tex.getName());

+			result.setWrap(WrapMode.Repeat);

+			// NOTE: Enable mipmaps FOR ALL TEXTURES EVER

+			result.setMinFilter(MinFilter.Trilinear);

+			if(type != TEX_IMAGE) {//only generated textures should have this key

+				result.setKey(new GeneratedTextureKey(tex.getName()));

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method merges the given textures. The result texture has no alpha

+	 * factor (is always opaque).

+	 * 

+	 * @param sources

+	 *            the textures to be merged

+	 * @param materialContext

+	 *            the context of the material

+	 * @return merged textures

+	 */

+	public Texture mergeTextures(List<Texture> sources, MaterialContext materialContext) {

+		Texture result = null;

+		if(sources!=null && sources.size()>0) {

+			if(sources.size() == 1) {

+				return sources.get(0);//just return the texture

+			}

+			//checking the sizes of the textures (tehy should perfectly match)

+			int lastTextureWithoutAlphaIndex = 0;

+			int width = sources.get(0).getImage().getWidth();

+			int height = sources.get(0).getImage().getHeight();

+			int depth = sources.get(0).getImage().getDepth();

+			

+			for(Texture source : sources) {

+				if(source.getImage().getWidth() != width) {

+					throw new IllegalArgumentException("The texture " + source.getName() + " has invalid width! It should be: " + width + '!');

+				}

+				if(source.getImage().getHeight() != height) {

+					throw new IllegalArgumentException("The texture " + source.getName() + " has invalid height! It should be: " + height + '!');

+				}

+				if(source.getImage().getDepth() != depth) {

+					throw new IllegalArgumentException("The texture " + source.getName() + " has invalid depth! It should be: " + depth + '!');

+				}

+				//support for more formats is not necessary at the moment

+				if(source.getImage().getFormat()!=Format.RGB8 && source.getImage().getFormat()!=Format.BGR8) {

+					++lastTextureWithoutAlphaIndex;

+				}

+			}

+			if(depth==0) {

+				depth = 1;

+			}

+			

+			//remove textures before the one without alpha (they will be covered anyway)

+			if(lastTextureWithoutAlphaIndex > 0 && lastTextureWithoutAlphaIndex<sources.size()-1) {

+				sources = sources.subList(lastTextureWithoutAlphaIndex, sources.size()-1);

+			}

+			int pixelsAmount = width * height * depth;

+			

+			ByteBuffer data = BufferUtils.createByteBuffer(pixelsAmount * 3);

+			TexturePixel resultPixel = new TexturePixel();

+			TexturePixel sourcePixel = new TexturePixel();

+			ColorRGBA diffuseColor = materialContext.getDiffuseColor();

+			for (int i = 0; i < pixelsAmount; ++i) {

+				for (int j = 0; j < sources.size(); ++j) {

+					Image image = sources.get(j).getImage();

+					ByteBuffer sourceData = image.getData(0);

+					if(j==0) {

+						resultPixel.fromColor(diffuseColor);

+						sourcePixel.fromImage(image.getFormat(), sourceData, i);

+						resultPixel.merge(sourcePixel);

+					} else {

+						sourcePixel.fromImage(image.getFormat(), sourceData, i);

+						resultPixel.merge(sourcePixel);

+					}

+				}

+				data.put((byte)(255 * resultPixel.red));

+				data.put((byte)(255 * resultPixel.green));

+				data.put((byte)(255 * resultPixel.blue));

+				resultPixel.clear();

+			}

+			

+			if(depth==1) {

+				result = new Texture2D(new Image(Format.RGB8, width, height, data));

+			} else {

+				ArrayList<ByteBuffer> arrayData = new ArrayList<ByteBuffer>(1);

+				arrayData.add(data);

+				result = new Texture3D(new Image(Format.RGB8, width, height, depth, arrayData));

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method converts the given texture into normal-map texture.

+	 * @param source

+	 *        the source texture

+	 * @param strengthFactor

+	 *        the normal strength factor

+	 * @return normal-map texture

+	 */

+	public Texture convertToNormalMapTexture(Texture source, float strengthFactor) {

+		Image image = source.getImage();

+		BufferedImage sourceImage = ImageToAwt.convert(image, false, false, 0);

+		BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);

+		BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);

+		ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);

+		gscale.filter(sourceImage, heightMap);

+

+		Vector3f S = new Vector3f();

+		Vector3f T = new Vector3f();

+		Vector3f N = new Vector3f();

+

+		for (int x = 0; x < bumpMap.getWidth(); ++x) {

+			for (int y = 0; y < bumpMap.getHeight(); ++y) {

+				// generating bump pixel

+				S.x = 1;

+				S.y = 0;

+				S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y);

+				T.x = 0;

+				T.y = 1;

+				T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1);

+

+				float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1);

+				N.x = -S.z;

+				N.y = -T.z;

+				N.z = 1;

+				N.divideLocal(den);

+

+				// setting thge pixel in the result image

+				bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z));

+			}

+		}

+		ByteBuffer byteBuffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 3);

+		ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer);

+		return new Texture2D(new Image(Format.RGB8, image.getWidth(), image.getHeight(), byteBuffer));

+	}

+

+	/**

+	 * This method returns the height represented by the specified pixel in the given texture.

+	 * The given texture should be a height-map.

+	 * @param image

+	 *        the height-map texture

+	 * @param x

+	 *        pixel's X coordinate

+	 * @param y

+	 *        pixel's Y coordinate

+	 * @return height reprezented by the given texture in the specified location

+	 */

+	protected int getHeight(BufferedImage image, int x, int y) {

+		if (x < 0) {

+			x = 0;

+		} else if (x >= image.getWidth()) {

+			x = image.getWidth() - 1;

+		}

+		if (y < 0) {

+			y = 0;

+		} else if (y >= image.getHeight()) {

+			y = image.getHeight() - 1;

+		}

+		return image.getRGB(x, y) & 0xff;

+	}

+

+	/**

+	 * This method transforms given vector's coordinates into ARGB color (A is always = 255).

+	 * @param x X factor of the vector

+	 * @param y Y factor of the vector

+	 * @param z Z factor of the vector

+	 * @return color representation of the given vector

+	 */

+	protected int vectorToColor(float x, float y, float z) {

+		int r = Math.round(255 * (x + 1f) / 2f);

+		int g = Math.round(255 * (y + 1f) / 2f);

+		int b = Math.round(255 * (z + 1f) / 2f);

+		return (255 << 24) + (r << 16) + (g << 8) + b;

+	}

+

+	/**

+	 * This class returns a texture read from the file or from packed blender data.

+	 * 

+	 * @param image

+	 *        image structure filled with data

+	 * @param blenderContext

+	 *        the blender context

+	 * @return the texture that can be used by JME engine

+	 * @throws BlenderFileException

+	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted

+	 */

+	public Texture getTextureFromImage(Structure image, BlenderContext blenderContext) throws BlenderFileException {

+		LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", image.getOldMemoryAddress());

+		Texture result = (Texture) blenderContext.getLoadedFeature(image.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);

+		if (result == null) {

+			String texturePath = image.getFieldValue("name").toString();

+			Pointer pPackedFile = (Pointer) image.getFieldValue("packedfile");

+			if (pPackedFile.isNull()) {

+				LOGGER.log(Level.INFO, "Reading texture from file: {0}", texturePath);

+				result = this.loadTextureFromFile(texturePath, blenderContext);

+			} else {

+				LOGGER.info("Packed texture. Reading directly from the blend file!");

+				Structure packedFile = pPackedFile.fetchData(blenderContext.getInputStream()).get(0);

+				Pointer pData = (Pointer) packedFile.getFieldValue("data");

+				FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress());

+				blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());

+				ImageLoader imageLoader = new ImageLoader();

+

+				// Should the texture be flipped? It works for sinbad ..

+				Image im = imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true);

+				if (im != null) {

+					result = new Texture2D(im);

+				}

+			}

+			if (result != null) {

+				result.setName(texturePath);

+				result.setWrap(Texture.WrapMode.Repeat);

+				if(LOGGER.isLoggable(Level.FINE)) {

+					LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] {texturePath, image.getOldMemoryAddress()});

+				}

+				blenderContext.addLoadedFeatures(image.getOldMemoryAddress(), image.getName(), image, result);

+			}

+		}

+		return result;

+	}

+

+	/**

+	 * This method loads the textre from outside the blend file.

+	 * 

+	 * @param name

+	 *        the path to the image

+	 * @param blenderContext

+	 *        the blender context

+	 * @return the loaded image or null if the image cannot be found

+	 */

+	protected Texture loadTextureFromFile(String name, BlenderContext blenderContext) {

+                if (!name.contains(".")){

+                    return null; // no extension means not a valid image

+                }

+                

+		AssetManager assetManager = blenderContext.getAssetManager();

+		name = name.replaceAll("\\\\", "\\/");

+		Texture result = null;

+

+		List<String> assetNames = new ArrayList<String>();

+		if (name.startsWith("//")) {

+			String relativePath = name.substring(2);

+			//augument the path with blender key path

+			BlenderKey blenderKey = blenderContext.getBlenderKey();

+            int idx = blenderKey.getName().lastIndexOf('/');

+			String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0);

+			assetNames.add(blenderAssetFolder+'/'+relativePath);

+		} else {//use every path from the asset name to the root (absolute path)

+			String[] paths = name.split("\\/");

+			StringBuilder sb = new StringBuilder(paths[paths.length-1]);//the asset name

+			assetNames.add(paths[paths.length-1]);

+

+			for(int i=paths.length-2;i>=0;--i) {

+				sb.insert(0, '/');

+				sb.insert(0, paths[i]);

+				assetNames.add(0, sb.toString());

+			}

+		}

+

+		//now try to locate the asset

+		for(String assetName : assetNames) {

+			try {

+                TextureKey key = new TextureKey(assetName);

+                key.setGenerateMips(true);

+                key.setAsCube(false);

+				result = assetManager.loadTexture(key);

+				break;//if no exception is thrown then accept the located asset and break the loop

+			} catch(AssetNotFoundException e) {

+				LOGGER.fine(e.getLocalizedMessage());

+			}

+		}

+		return result;

+	}

+

+	@Override

+	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {

+		return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0;

+	}

+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java
new file mode 100644
index 0000000..37c0122
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java
@@ -0,0 +1,294 @@
+package com.jme3.scene.plugins.blender.textures;

+

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.FastMath;

+import com.jme3.texture.Image.Format;

+import java.nio.ByteBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * The class that stores the pixel values of a texture.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class TexturePixel implements Cloneable {

+	private static final Logger	LOGGER	= Logger.getLogger(TexturePixel.class.getName());

+

+	/** The pixel data. */

+	public float				intensity, red, green, blue, alpha;

+

+	/**

+	 * Copies the values from the given pixel.

+	 * 

+	 * @param pixel

+	 *            the pixel that we read from

+	 */

+	public void fromPixel(TexturePixel pixel) {

+		this.intensity = pixel.intensity;

+		this.red = pixel.red;

+		this.green = pixel.green;

+		this.blue = pixel.blue;

+		this.alpha = pixel.alpha;

+	}

+

+	/**

+	 * Copies the values from the given color.

+	 * 

+	 * @param colorRGBA

+	 *            the color that we read from

+	 */

+	public void fromColor(ColorRGBA colorRGBA) {

+		this.red = colorRGBA.r;

+		this.green = colorRGBA.g;

+		this.blue = colorRGBA.b;

+		this.alpha = colorRGBA.a;

+	}

+

+	/**

+	 * Copies the values from the given values.

+	 * 

+	 * @param a

+	 *            the alpha value

+	 * @param r

+	 *            the red value

+	 * @param g

+	 *            the green value

+	 * @param b

+	 *            the blue value

+	 */

+	public void fromARGB8(float a, float r, float g, float b) {

+		this.alpha = a;

+		this.red = r;

+		this.green = g;

+		this.blue = b;

+	}

+

+	/**

+	 * Copies the values from the given integer that stores the ARGB8 data.

+	 * 

+	 * @param argb8

+	 *            the data stored in an integer

+	 */

+	public void fromARGB8(int argb8) {

+		byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24);

+		this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+		pixelValue = (byte) ((argb8 & 0xFF0000) >> 16);

+		this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+		pixelValue = (byte) ((argb8 & 0xFF00) >> 8);

+		this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+		pixelValue = (byte) (argb8 & 0xFF);

+		this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+	}

+

+	/**

+	 * Copies the data from the given image.

+	 * 

+	 * @param imageFormat

+	 *            the image format

+	 * @param data

+	 *            the image data

+	 * @param pixelIndex

+	 *            the index of the required pixel

+	 */

+	public void fromImage(Format imageFormat, ByteBuffer data, int pixelIndex) {

+		int firstByteIndex;

+		byte pixelValue;

+		switch (imageFormat) {

+			case ABGR8:

+				firstByteIndex = pixelIndex << 2;

+				pixelValue = data.get(firstByteIndex);

+				this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 1);

+				this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 2);

+				this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 3);

+				this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			case RGBA8:

+				firstByteIndex = pixelIndex << 2;

+				pixelValue = data.get(firstByteIndex);

+				this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 1);

+				this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 2);

+				this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 3);

+				this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			case BGR8:

+				firstByteIndex = pixelIndex * 3;

+				pixelValue = data.get(firstByteIndex);

+				this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 1);

+				this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 2);

+				this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				this.alpha = 1.0f;

+				break;

+			case RGB8:

+				firstByteIndex = pixelIndex * 3;

+				pixelValue = data.get(firstByteIndex);

+				this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 1);

+				this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get(firstByteIndex + 2);

+				this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				this.alpha = 1.0f;

+				break;

+			case Luminance8:

+				pixelValue = data.get(pixelIndex);

+				this.intensity = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			default:

+				LOGGER.log(Level.FINEST, "Unknown type of texture: {0}. Black pixel used!", imageFormat);

+				this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f;

+		}

+	}

+

+	/**

+	 * Stores RGBA values in the given array.

+	 * 

+	 * @param result

+	 *            the array to store values

+	 */

+	public void toRGBA(float[] result) {

+		result[0] = this.red;

+		result[1] = this.green;

+		result[2] = this.blue;

+		result[3] = this.alpha;

+	}

+

+	/**

+	 * Stores the data in the given table.

+	 * 

+	 * @param result

+	 *            the result table

+	 */

+	public void toRGBA8(byte[] result) {

+		result[0] = (byte) (this.red * 255.0f);

+		result[1] = (byte) (this.green * 255.0f);

+		result[2] = (byte) (this.blue * 255.0f);

+		result[3] = (byte) (this.alpha * 255.0f);

+	}

+

+	/**

+	 * Stores the pixel values in the integer.

+	 * 

+	 * @return the integer that stores the pixel values

+	 */

+	public int toARGB8() {

+		int result = 0;

+		int b = (int) (this.alpha * 255.0f);

+		result |= b << 24;

+		b = (int) (this.red * 255.0f);

+		result |= b << 16;

+		b = (int) (this.green * 255.0f);

+		result |= b << 8;

+		b = (int) (this.blue * 255.0f);

+		result |= b;

+		return result;

+	}

+

+	/**

+	 * Merges two pixels (adds the values of each color).

+	 * 

+	 * @param pixel

+	 *            the pixel we merge with

+	 */

+	public void merge(TexturePixel pixel) {

+		float oneMinusAlpha = 1 - pixel.alpha;

+		this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red;

+		this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green;

+		this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue;

+		// alpha should be always 1.0f as a result

+	}

+

+	/**

+	 * This method negates the colors.

+	 */

+	public void negate() {

+		this.red = 1.0f - this.red;

+		this.green = 1.0f - this.green;

+		this.blue = 1.0f - this.blue;

+		this.alpha = 1.0f - this.alpha;

+	}

+

+	/**

+	 * This method clears the pixel values.

+	 */

+	public void clear() {

+		this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f;

+	}

+

+	/**

+	 * This method adds the calues of the given pixel to the current pixel.

+	 * 

+	 * @param pixel

+	 *            the pixel we add

+	 */

+	public void add(TexturePixel pixel) {

+		this.red += pixel.red;

+		this.green += pixel.green;

+		this.blue += pixel.blue;

+		this.alpha += pixel.alpha;

+		this.intensity += pixel.intensity;

+	}

+

+	/**

+	 * This method multiplies the values of the given pixel by the given value.

+	 * 

+	 * @param value

+	 *            multiplication factor

+	 */

+	public void mult(float value) {

+		this.red *= value;

+		this.green *= value;

+		this.blue *= value;

+		this.alpha *= value;

+		this.intensity *= value;

+	}

+

+	/**

+	 * This method divides the values of the given pixel by the given value.

+	 * ATTENTION! Beware of the zero value. This will cause you NaN's in the

+	 * pixel values.

+	 * 

+	 * @param value

+	 *            division factor

+	 */

+	public void divide(float value) {

+		this.red /= value;

+		this.green /= value;

+		this.blue /= value;

+		this.alpha /= value;

+		this.intensity /= value;

+	}

+

+	/**

+	 * This method clamps the pixel values to the given borders.

+	 * 

+	 * @param min

+	 *            the minimum value

+	 * @param max

+	 *            the maximum value

+	 */

+	public void clamp(float min, float max) {

+		this.red = FastMath.clamp(this.red, min, max);

+		this.green = FastMath.clamp(this.green, min, max);

+		this.blue = FastMath.clamp(this.blue, min, max);

+		this.alpha = FastMath.clamp(this.alpha, min, max);

+		this.intensity = FastMath.clamp(this.intensity, min, max);

+	}

+

+	@Override

+	public Object clone() throws CloneNotSupportedException {

+		return super.clone();

+	}

+

+	@Override

+	public String toString() {

+		return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]";

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java
new file mode 100644
index 0000000..56e4293
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * This class is used for UV coordinates generation.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class UVCoordinatesGenerator {
+	private static final Logger	LOGGER						= Logger.getLogger(UVCoordinatesGenerator.class.getName());
+
+	// texture UV coordinates types
+	public static final int		TEXCO_ORCO					= 1;
+	public static final int		TEXCO_REFL					= 2;
+	public static final int		TEXCO_NORM					= 4;
+	public static final int		TEXCO_GLOB					= 8;
+	public static final int		TEXCO_UV					= 16;
+	public static final int		TEXCO_OBJECT				= 32;
+	public static final int		TEXCO_LAVECTOR				= 64;
+	public static final int		TEXCO_VIEW					= 128;
+	public static final int		TEXCO_STICKY				= 256;
+	public static final int		TEXCO_OSA					= 512;
+	public static final int		TEXCO_WINDOW				= 1024;
+	public static final int		NEED_UV						= 2048;
+	public static final int		TEXCO_TANGENT				= 4096;
+	// still stored in vertex->accum, 1 D
+	public static final int		TEXCO_PARTICLE_OR_STRAND	= 8192;													// strand is used
+	public static final int		TEXCO_STRESS				= 16384;
+	public static final int		TEXCO_SPEED					= 32768;
+
+	// 2D texture mapping (projection)
+	public static final int		PROJECTION_FLAT				= 0;
+	public static final int		PROJECTION_CUBE				= 1;
+	public static final int		PROJECTION_TUBE				= 2;
+	public static final int		PROJECTION_SPHERE			= 3;
+
+	/**
+	 * This method generates UV coordinates for the given mesh.
+	 * IMPORTANT! This method assumes that all geometries represent one node.
+	 * Each containing mesh with separate material.
+	 * So all meshes have the same reference to vertex table which stores all their vertices.
+	 * @param texco
+	 *        texture coordinates type
+	 * @param projection
+	 *        the projection type for 2D textures
+	 * @param textureDimension
+	 *        the dimension of the texture (only 2D and 3D)
+	 * @param coordinatesSwappingIndexes
+	 *        an array that tells how UV-coordinates need to be swapped
+	 * @param geometries
+	 *        a list of geometries the UV coordinates will be applied to
+	 * @return created UV-coordinates buffer
+	 */
+	public static VertexBuffer generateUVCoordinates(int texco, int projection, int textureDimension, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
+		if (textureDimension != 2 && textureDimension != 3) {
+			throw new IllegalStateException("Unsupported texture dimension: " + textureDimension);
+		}
+
+		VertexBuffer result = new VertexBuffer(VertexBuffer.Type.TexCoord);
+		Mesh mesh = geometries.get(0).getMesh();
+		BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
+		float[] inputData = null;// positions, normals, reflection vectors, etc.
+
+		switch (texco) {
+			case TEXCO_ORCO:
+				inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
+				break;
+			case TEXCO_UV:
+				FloatBuffer uvCoordinatesBuffer = BufferUtils.createFloatBuffer(mesh.getVertexCount() * textureDimension);
+				Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
+				for (int i = 0; i < mesh.getVertexCount(); ++i) {
+					Vector2f uv = data[i % 3];
+					uvCoordinatesBuffer.put(uv.x);
+					uvCoordinatesBuffer.put(uv.y);
+					if(textureDimension == 3) {
+						uvCoordinatesBuffer.put(0);
+					}
+				}
+				result.setupData(Usage.Static, textureDimension, Format.Float, uvCoordinatesBuffer);
+				break;
+			case TEXCO_NORM:
+				inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
+				break;
+			case TEXCO_REFL:
+			case TEXCO_GLOB:
+			case TEXCO_TANGENT:
+			case TEXCO_STRESS:
+			case TEXCO_LAVECTOR:
+			case TEXCO_OBJECT:
+			case TEXCO_OSA:
+			case TEXCO_PARTICLE_OR_STRAND:
+			case TEXCO_SPEED:
+			case TEXCO_STICKY:
+			case TEXCO_VIEW:
+			case TEXCO_WINDOW:
+				LOGGER.warning("Texture coordinates type not currently supported: " + texco);
+				break;
+			default:
+				throw new IllegalStateException("Unknown texture coordinates value: " + texco);
+		}
+
+		if (inputData != null) {// make calculations
+			if (textureDimension == 2) {
+				switch (projection) {
+					case PROJECTION_FLAT:
+						inputData = UVProjectionGenerator.flatProjection(mesh, bb);
+						break;
+					case PROJECTION_CUBE:
+						inputData = UVProjectionGenerator.cubeProjection(mesh, bb);
+						break;
+					case PROJECTION_TUBE:
+						BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries);
+						inputData = UVProjectionGenerator.tubeProjection(mesh, bt);
+						break;
+					case PROJECTION_SPHERE:
+						BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries);
+						inputData = UVProjectionGenerator.sphereProjection(mesh, bs);
+						break;
+					default:
+						throw new IllegalStateException("Unknown projection type: " + projection);
+				}
+			} else {
+				Vector3f min = bb.getMin(null);
+				float[] uvCoordsResults = new float[4];//used for coordinates swapping
+				float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 };
+
+				// now transform the coordinates so that they are in the range of <0; 1>
+				for (int i = 0; i < inputData.length; i += 3) {
+					uvCoordsResults[1] = (inputData[i] - min.x) / ext[0];
+					uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1];
+					uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2];
+					
+					
+					inputData[i] = uvCoordsResults[coordinatesSwappingIndexes[0]];
+					inputData[i + 1] = uvCoordsResults[coordinatesSwappingIndexes[1]];
+					inputData[i + 2] = uvCoordsResults[coordinatesSwappingIndexes[2]];
+				}
+			}
+			result.setupData(Usage.Static, textureDimension, Format.Float, BufferUtils.createFloatBuffer(inputData));
+		}
+
+		// each mesh will have the same coordinates
+		for (Geometry geometry : geometries) {
+			mesh = geometry.getMesh();
+			mesh.clearBuffer(VertexBuffer.Type.TexCoord);// in case there are coordinates already set
+			mesh.setBuffer(result);
+		}
+		
+		return result;
+	}
+
+	/**
+	 * This method returns the bounding box of the given geometries.
+	 * @param geometries
+	 *        the list of geometries
+	 * @return bounding box of the given geometries
+	 */
+	/* package */static BoundingBox getBoundingBox(List<Geometry> geometries) {
+		BoundingBox result = null;
+		for (Geometry geometry : geometries) {
+			BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
+			if (result == null) {
+				result = bb;
+			} else {
+				result.merge(bb);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method returns the bounding box of the given mesh.
+	 * @param mesh
+	 *        the mesh
+	 * @return bounding box of the given mesh
+	 */
+	/* package */static BoundingBox getBoundingBox(Mesh mesh) {
+		mesh.updateBound();
+		BoundingVolume bv = mesh.getBound();
+		if (bv instanceof BoundingBox) {
+			return (BoundingBox) bv;
+		} else if (bv instanceof BoundingSphere) {
+			BoundingSphere bs = (BoundingSphere) bv;
+			float r = bs.getRadius();
+			return new BoundingBox(bs.getCenter(), r, r, r);
+		} else {
+			throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
+		}
+	}
+
+	/**
+	 * This method returns the bounding sphere of the given geometries.
+	 * @param geometries
+	 *        the list of geometries
+	 * @return bounding sphere of the given geometries
+	 */
+	/* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
+		BoundingSphere result = null;
+		for (Geometry geometry : geometries) {
+			BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
+			if (result == null) {
+				result = bs;
+			} else {
+				result.merge(bs);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method returns the bounding sphere of the given mesh.
+	 * @param mesh
+	 *        the mesh
+	 * @return bounding sphere of the given mesh
+	 */
+	/* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
+		mesh.updateBound();
+		BoundingVolume bv = mesh.getBound();
+		if (bv instanceof BoundingBox) {
+			BoundingBox bb = (BoundingBox) bv;
+			float r = Math.max(bb.getXExtent(), bb.getYExtent());
+			r = Math.max(r, bb.getZExtent());
+			return new BoundingSphere(r, bb.getCenter());
+		} else if (bv instanceof BoundingSphere) {
+			return (BoundingSphere) bv;
+		} else {
+			throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
+		}
+	}
+
+	/**
+	 * This method returns the bounding tube of the given mesh.
+	 * @param mesh
+	 *        the mesh
+	 * @return bounding tube of the given mesh
+	 */
+	/* package */static BoundingTube getBoundingTube(Mesh mesh) {
+		Vector3f center = new Vector3f();
+		float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
+		float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
+		float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
+
+		FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
+		int limit = positions.limit();
+		for (int i = 0; i < limit; i += 3) {
+			float x = positions.get(i);
+			float y = positions.get(i + 1);
+			float z = positions.get(i + 2);
+			center.addLocal(x, y, z);
+			maxx = x > maxx ? x : maxx;
+			minx = x < minx ? x : minx;
+			maxy = y > maxy ? y : maxy;
+			miny = y < miny ? y : miny;
+			maxz = z > maxz ? z : maxz;
+			minz = z < minz ? z : minz;
+		}
+		center.divideLocal(limit / 3);
+
+		float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
+		return new BoundingTube(radius, maxz - minz, center);
+	}
+	
+	/**
+	 * This method returns the bounding tube of the given geometries.
+	 * @param geometries
+	 *        the list of geometries
+	 * @return bounding tube of the given geometries
+	 */
+	/* package */static BoundingTube getBoundingTube(List<Geometry> geometries) {
+		BoundingTube result = null;
+		for (Geometry geometry : geometries) {
+			BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh());
+			if (result == null) {
+				result = bt;
+			} else {
+				result.merge(bt);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * A very simple bounding tube. Id holds only the basic data bout the bounding tube
+	 * and does not provide full functionality of a BoundingVolume.
+	 * Should be replaced with a bounding tube that extends the BoundingVolume if it is ever created.
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	/* package */static class BoundingTube {
+		private float		radius;
+		private float		height;
+		private Vector3f	center;
+
+		/**
+		 * Constructor creates the tube with the given params.
+		 * @param radius
+		 *        the radius of the tube
+		 * @param height
+		 *        the height of the tube
+		 * @param center
+		 *        the center of the tube
+		 */
+		public BoundingTube(float radius, float height, Vector3f center) {
+			this.radius = radius;
+			this.height = height;
+			this.center = center;
+		}
+
+		/**
+		 * This method merges two bounding tubes.
+		 * @param boundingTube
+		 *        bounding tube to be merged woth the current one
+		 * @return new instance of bounding tube representing the tubes' merge
+		 */
+		public BoundingTube merge(BoundingTube boundingTube) {
+			// get tubes (tube1.radius >= tube2.radius)
+			BoundingTube tube1, tube2;
+			if (this.radius >= boundingTube.radius) {
+				tube1 = this;
+				tube2 = boundingTube;
+			} else {
+				tube1 = boundingTube;
+				tube2 = this;
+			}
+			float r1 = tube1.radius;
+			float r2 = tube2.radius;
+
+			float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f);
+			float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f);
+			float height = maxZ - minZ;
+			Vector3f distance = tube2.center.subtract(tube1.center);
+			Vector3f center = tube1.center.add(distance.mult(0.5f));
+			distance.z = 0;// projecting this vector on XY plane
+			float d = distance.length();
+			// d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the inside
+			// d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1
+			float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f;
+			return new BoundingTube(radius, height, center);
+		}
+
+		/**
+		 * This method returns the radius of the tube.
+		 * @return the radius of the tube
+		 */
+		public float getRadius() {
+			return radius;
+		}
+
+		/**
+		 * This method returns the height of the tube.
+		 * @return the height of the tube
+		 */
+		public float getHeight() {
+			return height;
+		}
+
+		/**
+		 * This method returns the center of the tube.
+		 * @return the center of the tube
+		 */
+		public Vector3f getCenter() {
+			return center;
+		}
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java
new file mode 100644
index 0000000..4412d60
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java
@@ -0,0 +1,226 @@
+package com.jme3.scene.plugins.blender.textures;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.math.FastMath;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTube;
+import java.nio.FloatBuffer;
+
+/**
+ * This class helps with projection calculations.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class UVProjectionGenerator {
+	/**
+	 * Flat projection for 2D textures.
+	 * 
+	 * @param mesh
+	 *            mesh that is to be projected
+	 * @param bb
+	 *            the bounding box for projecting
+	 * @return UV coordinates after the projection
+	 */
+	public static float[] flatProjection(Mesh mesh, BoundingBox bb) {
+		if (bb == null) {
+			bb = UVCoordinatesGenerator.getBoundingBox(mesh);
+		}
+		Vector3f min = bb.getMin(null);
+		float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f };
+		FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
+		float[] uvCoordinates = new float[positions.limit() / 3 * 2];
+		for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) {
+			uvCoordinates[j] = (positions.get(i) - min.x) / ext[0];
+			uvCoordinates[j + 1] = (positions.get(i + 1) - min.y) / ext[1];
+			// skip the Z-coordinate
+		}
+		return uvCoordinates;
+	}
+
+	/**
+	 * Cube projection for 2D textures.
+	 * 
+	 * @param mesh
+	 *            mesh that is to be projected
+	 * @param bb
+	 *            the bounding box for projecting
+	 * @return UV coordinates after the projection
+	 */
+	public static float[] cubeProjection(Mesh mesh, BoundingBox bb) {
+		Triangle triangle = new Triangle();
+		Vector3f x = new Vector3f(1, 0, 0);
+		Vector3f y = new Vector3f(0, 1, 0);
+		Vector3f z = new Vector3f(0, 0, 1);
+		Vector3f min = bb.getMin(null);
+		float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f };
+
+		float[] uvCoordinates = new float[mesh.getTriangleCount() * 6];// 6 == 3 * 2
+		float borderAngle = (float) Math.sqrt(2.0f) / 2.0f;
+		for (int i = 0, pointIndex = 0; i < mesh.getTriangleCount(); ++i) {
+			mesh.getTriangle(i, triangle);
+			Vector3f n = triangle.getNormal();
+			float dotNX = Math.abs(n.dot(x));
+			float dorNY = Math.abs(n.dot(y));
+			float dotNZ = Math.abs(n.dot(z));
+			if (dotNX > borderAngle) {
+				if (dotNZ < borderAngle) {// discard X-coordinate
+					uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
+					uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
+					uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
+				} else {// discard Z-coordinate
+					uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
+				}
+			} else {
+				if (dorNY > borderAngle) {// discard Y-coordinate
+					uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
+					uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
+					uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
+				} else {// discard Z-coordinate
+					uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
+					uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
+					uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
+				}
+			}
+			triangle.setNormal(null);// clear the previous normal vector
+		}
+		return uvCoordinates;
+	}
+
+	/**
+	 * Tube projection for 2D textures.
+	 * 
+	 * @param mesh
+	 *            mesh that is to be projected
+	 * @param bt
+	 *            the bounding tube for projecting
+	 * @return UV coordinates after the projection
+	 */
+	public static float[] tubeProjection(Mesh mesh, BoundingTube bt) {
+		FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
+		float[] uvCoordinates = new float[positions.limit() / 3 * 2];
+		Vector3f v = new Vector3f();
+		float cx = bt.getCenter().x, cy = bt.getCenter().y;
+		Vector3f uBase = new Vector3f(0, -1, 0);
+		
+		float vBase = bt.getCenter().z - bt.getHeight() * 0.5f;
+		for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) {
+			// calculating U
+			v.set(positions.get(i)-cx, positions.get(i + 1)-cy, 0);
+			v.normalizeLocal();
+			float angle = v.angleBetween(uBase);// result between [0; PI]
+			if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
+				angle = FastMath.TWO_PI - angle;
+			}
+			uvCoordinates[j] = angle / FastMath.TWO_PI;
+
+			// calculating V
+			float z = positions.get(i + 2);
+			uvCoordinates[j + 1] = (z - vBase) / bt.getHeight();
+		}
+		
+		//looking for splitted triangles
+		Triangle triangle = new Triangle();
+		for(int i=0;i<mesh.getTriangleCount();++i) {
+			mesh.getTriangle(i, triangle);
+			float sgn1 = Math.signum(triangle.get1().x-cx);
+			float sgn2 = Math.signum(triangle.get2().x-cx);
+			float sgn3 = Math.signum(triangle.get3().x-cx);
+			float xSideFactor = sgn1 + sgn2 + sgn3;
+			float ySideFactor = Math.signum(triangle.get1().y-cy)+
+					   Math.signum(triangle.get2().y-cy)+
+					   Math.signum(triangle.get3().y-cy);
+			if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
+				//indexOfUcoord = (indexOfTriangle*3 + indexOfTrianglesVertex)*2
+				if(sgn1==1.0f) {
+					uvCoordinates[i*3*2] += 1.0f;
+				}
+				if(sgn2==1.0f) {
+					uvCoordinates[(i*3+1)*2] += 1.0f;
+				}
+				if(sgn3==1.0f) {
+					uvCoordinates[(i*3+2)*2] += 1.0f;
+				}
+			}
+		}
+		return uvCoordinates;
+	}
+
+	/**
+	 * Sphere projection for 2D textures.
+	 * 
+	 * @param mesh
+	 *            mesh that is to be projected
+	 * @param bb
+	 *            the bounding box for projecting
+	 * @return UV coordinates after the projection
+	 */
+	public static float[] sphereProjection(Mesh mesh, BoundingSphere bs) {
+		FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
+		float[] uvCoordinates = new float[positions.limit() / 3 * 2];
+		Vector3f v = new Vector3f();
+		float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z;
+		Vector3f uBase = new Vector3f(0, -1, 0);
+		Vector3f vBase = new Vector3f(0, 0, -1);
+
+		for (int i = 0, j = 0; i < positions.limit(); i += 3, j += 2) {
+			// calculating U
+			v.set(positions.get(i)-cx, positions.get(i + 1)-cy, 0);
+			v.normalizeLocal();
+			float angle = v.angleBetween(uBase);// result between [0; PI]
+			if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
+				angle = FastMath.TWO_PI - angle;
+			}
+			uvCoordinates[j] = angle / FastMath.TWO_PI;
+
+			// calculating V
+			v.set(positions.get(i)-cx, positions.get(i + 1)-cy, positions.get(i + 2)-cz);
+			v.normalizeLocal();
+			angle = v.angleBetween(vBase);// result between [0; PI]
+			uvCoordinates[j+1] = angle / FastMath.PI;
+		}
+		
+		//looking for splitted triangles
+		Triangle triangle = new Triangle();
+		for(int i=0;i<mesh.getTriangleCount();++i) {
+			mesh.getTriangle(i, triangle);
+			float sgn1 = Math.signum(triangle.get1().x-cx);
+			float sgn2 = Math.signum(triangle.get2().x-cx);
+			float sgn3 = Math.signum(triangle.get3().x-cx);
+			float xSideFactor = sgn1 + sgn2 + sgn3;
+			float ySideFactor = Math.signum(triangle.get1().y-cy)+
+					   Math.signum(triangle.get2().y-cy)+
+					   Math.signum(triangle.get3().y-cy);
+			if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
+				//indexOfUcoord = (indexOfTriangle*3 + indexOfTrianglesVertex)*2
+				if(sgn1==1.0f) {
+					uvCoordinates[i*3*2] += 1.0f;
+				}
+				if(sgn2==1.0f) {
+					uvCoordinates[(i*3+1)*2] += 1.0f;
+				}
+				if(sgn3==1.0f) {
+					uvCoordinates[(i*3+2)*2] += 1.0f;
+				}
+			}
+		}
+		return uvCoordinates;
+	}
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
new file mode 100644
index 0000000..505c94b
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
@@ -0,0 +1,195 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import com.jme3.math.FastMath;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.materials.MaterialHelper;

+

+/**

+ * An abstract class that contains the basic methods used by the classes that

+ * will derive from it.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+/* package */abstract class AbstractTextureBlender implements TextureBlender {

+	/**

+	 * This method blends the single pixel depending on the blending type.

+	 * 

+	 * @param result

+	 *            the result pixel

+	 * @param materialColor

+	 *            the material color

+	 * @param pixelColor

+	 *            the pixel color

+	 * @param blendFactor

+	 *            the blending factor

+	 * @param blendtype

+	 *            the blending type

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, float blendFactor, int blendtype, BlenderContext blenderContext) {

+		float oneMinusFactor = 1.0f - blendFactor, col;

+

+		switch (blendtype) {

+			case MTEX_BLEND:

+				result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];

+				result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1];

+				result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2];

+				break;

+			case MTEX_MUL:

+				result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0];

+				result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1];

+				result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2];

+				break;

+			case MTEX_DIV:

+				if (pixelColor[0] != 0.0) {

+					result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f;

+				}

+				if (pixelColor[1] != 0.0) {

+					result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f;

+				}

+				if (pixelColor[2] != 0.0) {

+					result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f;

+				}

+				break;

+			case MTEX_SCREEN:

+				result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);

+				result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);

+				result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);

+				break;

+			case MTEX_OVERLAY:

+				if (materialColor[0] < 0.5f) {

+					result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]);

+				} else {

+					result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);

+				}

+				if (materialColor[1] < 0.5f) {

+					result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]);

+				} else {

+					result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);

+				}

+				if (materialColor[2] < 0.5f) {

+					result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]);

+				} else {

+					result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);

+				}

+				break;

+			case MTEX_SUB:

+				result[0] = materialColor[0] - blendFactor * pixelColor[0];

+				result[1] = materialColor[1] - blendFactor * pixelColor[1];

+				result[2] = materialColor[2] - blendFactor * pixelColor[2];

+				result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);

+				result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);

+				result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);

+				break;

+			case MTEX_ADD:

+				result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f;

+				result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f;

+				result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f;

+				break;

+			case MTEX_DIFF:

+				result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]);

+				result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]);

+				result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]);

+				break;

+			case MTEX_DARK:

+				col = blendFactor * pixelColor[0];

+				result[0] = col < materialColor[0] ? col : materialColor[0];

+				col = blendFactor * pixelColor[1];

+				result[1] = col < materialColor[1] ? col : materialColor[1];

+				col = blendFactor * pixelColor[2];

+				result[2] = col < materialColor[2] ? col : materialColor[2];

+				break;

+			case MTEX_LIGHT:

+				col = blendFactor * pixelColor[0];

+				result[0] = col > materialColor[0] ? col : materialColor[0];

+				col = blendFactor * pixelColor[1];

+				result[1] = col > materialColor[1] ? col : materialColor[1];

+				col = blendFactor * pixelColor[2];

+				result[2] = col > materialColor[2] ? col : materialColor[2];

+				break;

+			case MTEX_BLEND_HUE:

+			case MTEX_BLEND_SAT:

+			case MTEX_BLEND_VAL:

+			case MTEX_BLEND_COLOR:

+				System.arraycopy(materialColor, 0, result, 0, 3);

+				this.blendHSV(blendtype, result, blendFactor, pixelColor, blenderContext);

+				break;

+			default:

+				throw new IllegalStateException("Unknown blend type: " + blendtype);

+		}

+	}

+

+	/**

+	 * The method that performs the ramp blending.

+	 * 

+	 * @param type

+	 *            the blend type

+	 * @param materialRGB

+	 *            the rgb value of the material, here the result is stored too

+	 * @param fac

+	 *            color affection factor

+	 * @param pixelColor

+	 *            the texture color

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) {

+		float oneMinusFactor = 1.0f - fac;

+		MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);

+

+		switch (type) {

+			case MTEX_BLEND_HUE: {// FIXME: not working well for image textures

+									// (works fine for generated textures)

+				float[] colorTransformResult = new float[3];

+				materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);

+				if (colorTransformResult[0] != 0.0f) {

+					float colH = colorTransformResult[0];

+					materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);

+					materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult);

+					materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0];

+					materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1];

+					materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2];

+				}

+				break;

+			}

+			case MTEX_BLEND_SAT: {

+				float[] colorTransformResult = new float[3];

+				materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);

+				float h = colorTransformResult[0];

+				float s = colorTransformResult[1];

+				float v = colorTransformResult[2];

+				if (s != 0.0f) {

+					materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);

+					materialHelper.hsvToRgb(h, (oneMinusFactor * s + fac * colorTransformResult[1]), v, materialRGB);

+				}

+				break;

+			}

+			case MTEX_BLEND_VAL: {

+				float[] rgbToHsv = new float[3];

+				float[] colToHsv = new float[3];

+				materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);

+				materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);

+				materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], (oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2]), materialRGB);

+				break;

+			}

+			case MTEX_BLEND_COLOR: {// FIXME: not working well for image

+									// textures (works fine for generated

+									// textures)

+				float[] rgbToHsv = new float[3];

+				float[] colToHsv = new float[3];

+				materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);

+				if (colToHsv[2] != 0) {

+					materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);

+					materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv);

+					materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0];

+					materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1];

+					materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2];

+				}

+				break;

+			}

+			default:

+				throw new IllegalStateException("Unknown ramp type: " + type);

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java
new file mode 100644
index 0000000..08e25d3
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java
@@ -0,0 +1,51 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.texture.Texture;

+

+/**

+ * An interface for texture blending classes (the classes that mix the texture

+ * pixels with the material colors).

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public interface TextureBlender {

+	// types of blending

+	int	MTEX_BLEND			= 0;

+	int	MTEX_MUL			= 1;

+	int	MTEX_ADD			= 2;

+	int	MTEX_SUB			= 3;

+	int	MTEX_DIV			= 4;

+	int	MTEX_DARK			= 5;

+	int	MTEX_DIFF			= 6;

+	int	MTEX_LIGHT			= 7;

+	int	MTEX_SCREEN			= 8;

+	int	MTEX_OVERLAY		= 9;

+	int	MTEX_BLEND_HUE		= 10;

+	int	MTEX_BLEND_SAT		= 11;

+	int	MTEX_BLEND_VAL		= 12;

+	int	MTEX_BLEND_COLOR	= 13;

+	int	MTEX_NUM_BLENDTYPES	= 14;

+

+	/**

+	 * This method blends the given texture with material color and the defined

+	 * color in 'map to' panel. As a result of this method a new texture is

+	 * created. The input texture is NOT.

+	 * 

+	 * @param materialColor

+	 *            the material diffuse color

+	 * @param texture

+	 *            the texture we use in blending

+	 * @param color

+	 *            the color defined for the texture

+	 * @param affectFactor

+	 *            the factor that the color affects the texture (value form 0.0

+	 *            to 1.0)

+	 * @param blendType

+	 *            the blending type

+	 * @param blenderContext

+	 *            the blender context

+	 * @return new texture that was created after the blending

+	 */

+	Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext);

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
new file mode 100644
index 0000000..75fc0c5
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
@@ -0,0 +1,162 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import java.nio.ByteBuffer;

+import java.util.ArrayList;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.texture.Image;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture2D;

+import com.jme3.texture.Texture3D;

+import com.jme3.texture.Image.Format;

+import com.jme3.util.BufferUtils;

+

+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> RGBA8

+ * <li> ABGR8

+ * <li> BGR8

+ * <li> RGB8

+ * Not yet supported (but will be):

+ * <li> ARGB4444:

+ * <li> RGB10:

+ * <li> RGB111110F:

+ * <li> RGB16:

+ * <li> RGB16F:

+ * <li> RGB16F_to_RGB111110F:

+ * <li> RGB16F_to_RGB9E5:

+ * <li> RGB32F:

+ * <li> RGB565:

+ * <li> RGB5A1:

+ * <li> RGB9E5:

+ * <li> RGBA16:

+ * <li> RGBA16F

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class TextureBlenderAWT extends AbstractTextureBlender {

+	private static final Logger	LOGGER	= Logger.getLogger(TextureBlenderAWT.class.getName());

+

+	@Override

+	public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) {

+		float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };

+		Format format = texture.getImage().getFormat();

+		ByteBuffer data = texture.getImage().getData(0);

+		data.rewind();

+

+		int width = texture.getImage().getWidth();

+		int height = texture.getImage().getHeight();

+		int depth = texture.getImage().getDepth();

+		if (depth == 0) {

+			depth = 1;

+		}

+		ByteBuffer newData = BufferUtils.createByteBuffer(width * height * depth * 4);

+

+		float[] resultPixel = new float[4];

+		int dataIndex = 0;

+		while (data.hasRemaining()) {

+			float tin = this.setupMaterialColor(data, format, neg, pixelColor);

+			this.blendPixel(resultPixel, materialColor, color, tin, blendType, blenderContext);

+			newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));

+			newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));

+			newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));

+			newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f));

+		}

+		if (texture.getType() == Texture.Type.TwoDimensional) {

+			return new Texture2D(new Image(Format.RGBA8, width, height, newData));

+		} else {

+			ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);

+			dataArray.add(newData);

+			return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray));

+		}

+	}

+

+	/**

+	 * This method alters the material color in a way dependent on the type of

+	 * the image. For example the color remains untouched if the texture is of

+	 * Luminance type. The luminance defines the interaction between the

+	 * material color and color defined for texture blending. If the type has 3

+	 * or more color channels then the material color is replaced with the

+	 * texture's color and later blended with the defined blend color. All alpha

+	 * values (if present) are ignored and not used during blending.

+	 * 

+	 * @param data

+	 *            the image data

+	 * @param imageFormat

+	 *            the format of the image

+	 * @param neg

+	 *            defines it the result color should be nagated

+	 * @param materialColor

+	 *            the material's color (value may be changed)

+	 * @return texture intensity for the current pixel

+	 */

+	protected float setupMaterialColor(ByteBuffer data, Format imageFormat, boolean neg, float[] materialColor) {

+		float tin = 0.0f;

+		byte pixelValue = data.get();// at least one byte is always taken :)

+		float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+		switch (imageFormat) {

+			case RGBA8:

+				materialColor[0] = firstPixelValue;

+				pixelValue = data.get();

+				materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[3] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			case ABGR8:

+				materialColor[3] = firstPixelValue;

+				pixelValue = data.get();

+				materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[0] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			case BGR8:

+				materialColor[2] = firstPixelValue;

+				pixelValue = data.get();

+				materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[0] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				materialColor[3] = 1.0f;

+				break;

+			case RGB8:

+				materialColor[0] = firstPixelValue;

+				pixelValue = data.get();

+				materialColor[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				pixelValue = data.get();

+				materialColor[2] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				materialColor[3] = 1.0f;

+				break;

+			case ARGB4444:

+			case RGB10:

+			case RGB111110F:

+			case RGB16:

+			case RGB16F:

+			case RGB16F_to_RGB111110F:

+			case RGB16F_to_RGB9E5:

+			case RGB32F:

+			case RGB565:

+			case RGB5A1:

+			case RGB9E5:

+			case RGBA16:

+			case RGBA16F:

+			case RGBA32F:// TODO: implement these textures

+				LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat);

+				break;

+			default:

+				throw new IllegalStateException("Invalid image format type for AWT texture blender: " + imageFormat);

+		}

+		if (neg) {

+			materialColor[0] = 1.0f - materialColor[0];

+			materialColor[1] = 1.0f - materialColor[1];

+			materialColor[2] = 1.0f - materialColor[2];

+		}

+		// Blender formula for texture intensity calculation:

+		// 0.35*texres.tr+0.45*texres.tg+0.2*texres.tb

+		tin = 0.35f * materialColor[0] + 0.45f * materialColor[1] + 0.2f * materialColor[2];

+		return tin;

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java
new file mode 100644
index 0000000..a532cfb
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java
@@ -0,0 +1,96 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import java.nio.ByteBuffer;

+import java.util.ArrayList;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import jme3tools.converters.RGB565;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.scene.plugins.blender.textures.TexturePixel;

+import com.jme3.texture.Image;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture2D;

+import com.jme3.texture.Texture3D;

+import com.jme3.util.BufferUtils;

+

+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> DXT1

+ * <li> DXT3

+ * <li> DXT5

+ * Not yet supported (but will be):

+ * <li> DXT1A:

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class TextureBlenderDDS extends AbstractTextureBlender {

+	private static final Logger	LOGGER	= Logger.getLogger(TextureBlenderDDS.class.getName());

+

+	@Override

+	public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) {

+		Format format = texture.getImage().getFormat();

+		ByteBuffer data = texture.getImage().getData(0);

+		data.rewind();

+

+		int width = texture.getImage().getWidth();

+		int height = texture.getImage().getHeight();

+		int depth = texture.getImage().getDepth();

+		if (depth == 0) {

+			depth = 1;

+		}

+		ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());

+

+		float[] resultPixel = new float[4];

+		float[] pixelColor = new float[4];

+		TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };

+		int dataIndex = 0;

+		while (data.hasRemaining()) {

+			switch (format) {

+				case DXT3:

+				case DXT5:

+					newData.putLong(dataIndex, data.getLong());// just copy the

+																// 8 bytes of

+																// alphas

+					dataIndex += 8;

+				case DXT1:

+					int col0 = RGB565.RGB565_to_ARGB8(data.getShort());

+					int col1 = RGB565.RGB565_to_ARGB8(data.getShort());

+					colors[0].fromARGB8(col0);

+					colors[1].fromARGB8(col1);

+					break;

+				case DXT1A:

+					LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", format);

+					break;

+				default:

+					throw new IllegalStateException("Invalid image format type for DDS texture blender: " + format);

+			}

+

+			// blending colors

+			for (int i = 0; i < colors.length; ++i) {

+				if (neg) {

+					colors[i].negate();

+				}

+				colors[i].toRGBA(pixelColor);

+				this.blendPixel(resultPixel, materialColor, pixelColor, affectFactor, blendType, blenderContext);

+				colors[i].fromARGB8(1, resultPixel[0], resultPixel[1], resultPixel[2]);

+				int argb8 = colors[i].toARGB8();

+				short rgb565 = RGB565.ARGB8_to_RGB565(argb8);

+				newData.putShort(dataIndex, rgb565);

+				dataIndex += 2;

+			}

+

+			// just copy the remaining 4 bytes of the current texel

+			newData.putInt(dataIndex, data.getInt());

+			dataIndex += 4;

+		}

+		if (texture.getType() == Texture.Type.TwoDimensional) {

+			return new Texture2D(new Image(format, width, height, newData));

+		} else {

+			ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);

+			dataArray.add(newData);

+			return new Texture3D(new Image(format, width, height, depth, dataArray));

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
new file mode 100644
index 0000000..c9ad61a
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
@@ -0,0 +1,81 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Image.Format;

+

+/**

+ * This class creates the texture blending class depending on the texture type.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class TextureBlenderFactory {

+	private static final Logger	LOGGER	= Logger.getLogger(TextureBlenderFactory.class.getName());

+

+	/**

+	 * This method creates the blending class.

+	 * 

+	 * @param format

+	 *            the texture format

+	 * @returntexture blending class

+	 */

+	public static TextureBlender createTextureBlender(Format format) {

+		switch (format) {

+			case Luminance8:

+			case Luminance8Alpha8:

+			case Luminance16:

+			case Luminance16Alpha16:

+			case Luminance16F:

+			case Luminance16FAlpha16F:

+			case Luminance32F:

+				return new TextureBlenderLuminance();

+			case RGBA8:

+			case ABGR8:

+			case BGR8:

+			case RGB8:

+			case RGB10:

+			case RGB111110F:

+			case RGB16:

+			case RGB16F:

+			case RGB16F_to_RGB111110F:

+			case RGB16F_to_RGB9E5:

+			case RGB32F:

+			case RGB565:

+			case RGB5A1:

+			case RGB9E5:

+			case RGBA16:

+			case RGBA16F:

+			case RGBA32F:

+				return new TextureBlenderAWT();

+			case DXT1:

+			case DXT1A:

+			case DXT3:

+			case DXT5:

+				return new TextureBlenderDDS();

+			case Alpha16:

+			case Alpha8:

+			case ARGB4444:

+			case Depth:

+			case Depth16:

+			case Depth24:

+			case Depth32:

+			case Depth32F:

+			case Intensity16:

+			case Intensity8:

+			case LATC:

+			case LTC:

+				LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format);

+				return new TextureBlender() {

+					@Override

+					public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) {

+						return texture;

+					}

+				};

+			default:

+				throw new IllegalStateException("Unknown image format type: " + format);

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java
new file mode 100644
index 0000000..a616685
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java
@@ -0,0 +1,221 @@
+package com.jme3.scene.plugins.blender.textures.blending;

+

+import java.nio.ByteBuffer;

+import java.util.ArrayList;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+import com.jme3.math.FastMath;

+import com.jme3.scene.plugins.blender.BlenderContext;

+import com.jme3.texture.Image;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture2D;

+import com.jme3.texture.Texture3D;

+import com.jme3.texture.Image.Format;

+import com.jme3.util.BufferUtils;

+

+/**

+ * The class that is responsible for blending the following texture types:

+ * <li> Luminance8

+ * <li> Luminance8Alpha8

+ * Not yet supported (but will be):

+ * <li> Luminance16:

+ * <li> Luminance16Alpha16:

+ * <li> Luminance16F:

+ * <li> Luminance16FAlpha16F:

+ * <li> Luminance32F:

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class TextureBlenderLuminance extends AbstractTextureBlender {

+	private static final Logger	LOGGER	= Logger.getLogger(TextureBlenderLuminance.class.getName());

+

+	@Override

+	public Texture blend(float[] materialColor, Texture texture, float[] color, float affectFactor, int blendType, boolean neg, BlenderContext blenderContext) {

+		Format format = texture.getImage().getFormat();

+		ByteBuffer data = texture.getImage().getData(0);

+		data.rewind();

+

+		int width = texture.getImage().getWidth();

+		int height = texture.getImage().getHeight();

+		int depth = texture.getImage().getDepth();

+		if (depth == 0) {

+			depth = 1;

+		}

+		ByteBuffer newData = BufferUtils.createByteBuffer(width * height * depth * 4);

+

+		float[] resultPixel = new float[4];

+		float[] tinAndAlpha = new float[2];

+		int dataIndex = 0;

+		while (data.hasRemaining()) {

+			this.getTinAndAlpha(data, format, neg, tinAndAlpha);

+			this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], affectFactor, blendType, blenderContext);

+			newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));

+			newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));

+			newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));

+			newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f));

+		}

+		if (texture.getType() == Texture.Type.TwoDimensional) {

+			return new Texture2D(new Image(Format.RGBA8, width, height, newData));

+		} else {

+			ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(1);

+			dataArray.add(newData);

+			return new Texture3D(new Image(Format.RGBA8, width, height, depth, dataArray));

+		}

+	}

+

+	/**

+	 * This method return texture intensity and alpha value.

+	 * 

+	 * @param data

+	 *            the texture data

+	 * @param imageFormat

+	 *            the image format

+	 * @param neg

+	 *            indicates if the texture is negated

+	 * @param result

+	 *            the table (2 elements) where the result is being stored

+	 */

+	protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) {

+		byte pixelValue = data.get();// at least one byte is always taken

+		float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+		switch (imageFormat) {

+			case Luminance8:

+				result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;

+				result[1] = 1.0f;

+				break;

+			case Luminance8Alpha8:

+				result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;

+				pixelValue = data.get();

+				result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - (~pixelValue) / 255.0f;

+				break;

+			case Luminance16:

+			case Luminance16Alpha16:

+			case Luminance16F:

+			case Luminance16FAlpha16F:

+			case Luminance32F:

+				LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat);

+				break;

+			default:

+				throw new IllegalStateException("Invalid image format type for DDS texture blender: " + imageFormat);

+		}

+	}

+

+	/**

+	 * This method blends the texture with an appropriate color.

+	 * 

+	 * @param result

+	 *            the result color (variable 'in' in blender source code)

+	 * @param materialColor

+	 *            the texture color (variable 'out' in blender source coude)

+	 * @param color

+	 *            the previous color (variable 'tex' in blender source code)

+	 * @param textureIntensity

+	 *            texture intensity (variable 'fact' in blender source code)

+	 * @param textureFactor

+	 *            texture affection factor (variable 'facg' in blender source

+	 *            code)

+	 * @param blendtype

+	 *            the blend type

+	 * @param blenderContext

+	 *            the blender context

+	 */

+	protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) {

+		float oneMinusFactor, col;

+		textureIntensity *= textureFactor;

+

+		switch (blendtype) {

+			case MTEX_BLEND:

+				oneMinusFactor = 1.0f - textureIntensity;

+				result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0];

+				result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1];

+				result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2];

+				break;

+			case MTEX_MUL:

+				oneMinusFactor = 1.0f - textureFactor;

+				result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0];

+				result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1];

+				result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2];

+				break;

+			case MTEX_DIV:

+				oneMinusFactor = 1.0f - textureIntensity;

+				if (color[0] != 0.0) {

+					result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f;

+				}

+				if (color[1] != 0.0) {

+					result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f;

+				}

+				if (color[2] != 0.0) {

+					result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f;

+				}

+				break;

+			case MTEX_SCREEN:

+				oneMinusFactor = 1.0f - textureFactor;

+				result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);

+				result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);

+				result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);

+				break;

+			case MTEX_OVERLAY:

+				oneMinusFactor = 1.0f - textureFactor;

+				if (materialColor[0] < 0.5f) {

+					result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]);

+				} else {

+					result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);

+				}

+				if (materialColor[1] < 0.5f) {

+					result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]);

+				} else {

+					result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);

+				}

+				if (materialColor[2] < 0.5f) {

+					result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]);

+				} else {

+					result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);

+				}

+				break;

+			case MTEX_SUB:

+				result[0] = materialColor[0] - textureIntensity * color[0];

+				result[1] = materialColor[1] - textureIntensity * color[1];

+				result[2] = materialColor[2] - textureIntensity * color[2];

+				result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);

+				result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);

+				result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);

+				break;

+			case MTEX_ADD:

+				result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f;

+				result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f;

+				result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f;

+				break;

+			case MTEX_DIFF:

+				oneMinusFactor = 1.0f - textureIntensity;

+				result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]);

+				result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]);

+				result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]);

+				break;

+			case MTEX_DARK:

+				col = textureIntensity * color[0];

+				result[0] = col < materialColor[0] ? col : materialColor[0];

+				col = textureIntensity * color[1];

+				result[1] = col < materialColor[1] ? col : materialColor[1];

+				col = textureIntensity * color[2];

+				result[2] = col < materialColor[2] ? col : materialColor[2];

+				break;

+			case MTEX_LIGHT:

+				col = textureIntensity * color[0];

+				result[0] = col > materialColor[0] ? col : materialColor[0];

+				col = textureIntensity * color[1];

+				result[1] = col > materialColor[1] ? col : materialColor[1];

+				col = textureIntensity * color[2];

+				result[2] = col > materialColor[2] ? col : materialColor[2];

+				break;

+			case MTEX_BLEND_HUE:

+			case MTEX_BLEND_SAT:

+			case MTEX_BLEND_VAL:

+			case MTEX_BLEND_COLOR:

+				System.arraycopy(materialColor, 0, result, 0, 3);

+				this.blendHSV(blendtype, result, textureIntensity, color, blenderContext);

+				break;

+			default:

+				throw new IllegalStateException("Unknown blend type: " + blendtype);

+		}

+	}

+}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/noiseconstants.dat b/engine/src/blender/com/jme3/scene/plugins/blender/textures/noiseconstants.dat
new file mode 100644
index 0000000..81fea0b
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/noiseconstants.dat
Binary files differ
diff --git a/engine/src/bullet-common/com/jme3/bullet/BulletAppState.java b/engine/src/bullet-common/com/jme3/bullet/BulletAppState.java
new file mode 100644
index 0000000..f6d24f4
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/BulletAppState.java
@@ -0,0 +1,274 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.AppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.bullet.PhysicsSpace.BroadphaseType;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import java.util.concurrent.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>BulletAppState</code> allows using bullet physics in an Application.
+ * @author normenhansen
+ */
+public class BulletAppState implements AppState, PhysicsTickListener {
+
+    protected boolean initialized = false;
+    protected Application app;
+    protected AppStateManager stateManager;
+    protected ScheduledThreadPoolExecutor executor;
+    protected PhysicsSpace pSpace;
+    protected ThreadingType threadingType = ThreadingType.SEQUENTIAL;
+    protected BroadphaseType broadphaseType = BroadphaseType.DBVT;
+    protected Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f);
+    protected Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f);
+    private float speed = 1;
+    protected boolean active = true;
+    protected float tpf;
+    protected Future physicsFuture;
+
+    /**
+     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
+     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
+     */
+    public BulletAppState() {
+    }
+
+    /**
+     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
+     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
+     * @param broadphaseType The type of broadphase collision detection, BroadphaseType.DVBT is the default
+     */
+    public BulletAppState(BroadphaseType broadphaseType) {
+        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType);
+    }
+
+    /**
+     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
+     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
+     * An AxisSweep broadphase is used.
+     * @param worldMin The minimum world extent
+     * @param worldMax The maximum world extent
+     */
+    public BulletAppState(Vector3f worldMin, Vector3f worldMax) {
+        this(worldMin, worldMax, BroadphaseType.AXIS_SWEEP_3);
+    }
+
+    public BulletAppState(Vector3f worldMin, Vector3f worldMax, BroadphaseType broadphaseType) {
+        this.worldMin.set(worldMin);
+        this.worldMax.set(worldMax);
+        this.broadphaseType = broadphaseType;
+    }
+
+    private boolean startPhysicsOnExecutor() {
+        if (executor != null) {
+            executor.shutdown();
+        }
+        executor = new ScheduledThreadPoolExecutor(1);
+        final BulletAppState app = this;
+        Callable<Boolean> call = new Callable<Boolean>() {
+
+            public Boolean call() throws Exception {
+                detachedPhysicsLastUpdate = System.currentTimeMillis();
+                pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
+                pSpace.addTickListener(app);
+                return true;
+            }
+        };
+        try {
+            return executor.submit(call).get();
+        } catch (InterruptedException ex) {
+            Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
+            return false;
+        } catch (ExecutionException ex) {
+            Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
+            return false;
+        }
+    }
+    private Callable<Boolean> parallelPhysicsUpdate = new Callable<Boolean>() {
+
+        public Boolean call() throws Exception {
+            pSpace.update(tpf * getSpeed());
+            return true;
+        }
+    };
+    long detachedPhysicsLastUpdate = 0;
+    private Callable<Boolean> detachedPhysicsUpdate = new Callable<Boolean>() {
+
+        public Boolean call() throws Exception {
+            pSpace.update(getPhysicsSpace().getAccuracy() * getSpeed());
+            pSpace.distributeEvents();
+            long update = System.currentTimeMillis() - detachedPhysicsLastUpdate;
+            detachedPhysicsLastUpdate = System.currentTimeMillis();
+            executor.schedule(detachedPhysicsUpdate, Math.round(getPhysicsSpace().getAccuracy() * 1000000.0f) - (update * 1000), TimeUnit.MICROSECONDS);
+            return true;
+        }
+    };
+
+    public PhysicsSpace getPhysicsSpace() {
+        return pSpace;
+    }
+
+    /**
+     * The physics system is started automatically on attaching, if you want to start it
+     * before for some reason, you can use this method.
+     */
+    public void startPhysics() {
+        //start physics thread(pool)
+        if (threadingType == ThreadingType.PARALLEL) {
+            startPhysicsOnExecutor();
+//        } else if (threadingType == ThreadingType.DETACHED) {
+//            startPhysicsOnExecutor();
+//            executor.submit(detachedPhysicsUpdate);
+        } else {
+            pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
+        }
+        pSpace.addTickListener(this);
+        initialized = true;
+    }
+
+    public void initialize(AppStateManager stateManager, Application app) {
+        if (!initialized) {
+            startPhysics();
+        }
+        initialized = true;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+    
+    public void setEnabled(boolean enabled) {
+        this.active = enabled;
+    }
+    
+    public boolean isEnabled() {
+        return active;
+    }
+
+    public void stateAttached(AppStateManager stateManager) {
+        if (!initialized) {
+            startPhysics();
+        }
+        if (threadingType == ThreadingType.PARALLEL) {
+            PhysicsSpace.setLocalThreadPhysicsSpace(pSpace);
+        }
+    }
+
+    public void stateDetached(AppStateManager stateManager) {
+    }
+
+    public void update(float tpf) {
+        if (!active) {
+            return;
+        }
+//        if (threadingType != ThreadingType.DETACHED) {
+            pSpace.distributeEvents();
+//        }
+        this.tpf = tpf;
+    }
+
+    public void render(RenderManager rm) {
+        if (!active) {
+            return;
+        }
+        if (threadingType == ThreadingType.PARALLEL) {
+            physicsFuture = executor.submit(parallelPhysicsUpdate);
+        } else if (threadingType == ThreadingType.SEQUENTIAL) {
+            pSpace.update(active ? tpf * speed : 0);
+        } else {
+        }
+    }
+
+    public void postRender() {
+        if (physicsFuture != null) {
+            try {
+                physicsFuture.get();
+                physicsFuture = null;
+            } catch (InterruptedException ex) {
+                Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
+            } catch (ExecutionException ex) {
+                Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+
+    public void cleanup() {
+        if (executor != null) {
+            executor.shutdown();
+            executor = null;
+        }
+        pSpace.removeTickListener(this);
+        pSpace.destroy();
+    }
+
+    /**
+     * @return the threadingType
+     */
+    public ThreadingType getThreadingType() {
+        return threadingType;
+    }
+
+    /**
+     * Use before attaching state
+     * @param threadingType the threadingType to set
+     */
+    public void setThreadingType(ThreadingType threadingType) {
+        this.threadingType = threadingType;
+    }
+
+    /**
+     * Use before attaching state
+     */
+    public void setBroadphaseType(BroadphaseType broadphaseType) {
+        this.broadphaseType = broadphaseType;
+    }
+
+    /**
+     * Use before attaching state
+     */
+    public void setWorldMin(Vector3f worldMin) {
+        this.worldMin = worldMin;
+    }
+
+    /**
+     * Use before attaching state
+     */
+    public void setWorldMax(Vector3f worldMax) {
+        this.worldMax = worldMax;
+    }
+
+    public float getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    public void prePhysicsTick(PhysicsSpace space, float f) {
+    }
+
+    public void physicsTick(PhysicsSpace space, float f) {
+    }
+
+    public enum ThreadingType {
+
+        /**
+         * Default mode; user update, physics update and rendering happen sequentially (single threaded)
+         */
+        SEQUENTIAL,
+        /**
+         * Parallel threaded mode; physics update and rendering are executed in parallel, update order is kept.<br/>
+         * Multiple BulletAppStates will execute in parallel in this mode.
+         */
+        PARALLEL,
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/PhysicsTickListener.java b/engine/src/bullet-common/com/jme3/bullet/PhysicsTickListener.java
new file mode 100644
index 0000000..0f3bbca
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/PhysicsTickListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+/**
+ * Implement this interface to be called from the physics thread on a physics update.
+ * @author normenhansen
+ */
+public interface PhysicsTickListener {
+
+    /**
+     * Called before the physics is actually stepped, use to apply forces etc.
+     * @param space
+     * @param f
+     */
+    public void prePhysicsTick(PhysicsSpace space, float f);
+
+    /**
+     * Called after the physics has been stepped, use to check for forces etc.
+     * @param space
+     * @param f
+     */
+    public void physicsTick(PhysicsSpace space, float f);
+
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java b/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java
new file mode 100644
index 0000000..739598c
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java
@@ -0,0 +1,25 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.bullet.collision;
+
+/**
+ *
+ * @author normenhansen
+ */
+public interface PhysicsCollisionGroupListener {
+
+    /**
+     * Called when two physics objects of the registered group are about to collide, <i>called from physics thread</i>.<br>
+     * This is only called when the collision will happen based on the collisionGroup and collideWithGroups
+     * settings in the PhysicsCollisionObject. That is the case when <b>one</b> of the partys has the
+     * collisionGroup of the other in its collideWithGroups set.<br>
+     * @param nodeA CollisionObject #1
+     * @param nodeB CollisionObject #2
+     * @return true if the collision should happen, false otherwise
+     */
+    public boolean collide(PhysicsCollisionObject nodeA, PhysicsCollisionObject nodeB);
+
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionListener.java b/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionListener.java
new file mode 100644
index 0000000..2739c04
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/collision/PhysicsCollisionListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+/**
+ * Interface for Objects that want to be informed about collision events in the physics space
+ * @author normenhansen
+ */
+public interface PhysicsCollisionListener {
+
+    /**
+     * Called when a collision happened in the PhysicsSpace, <i>called from render thread</i>.<br/>
+     * Do not store the event object as it will be cleared after the method has finished.
+     * @param event the CollisionEvent
+     */
+    public void collision(PhysicsCollisionEvent event);
+
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/collision/RagdollCollisionListener.java b/engine/src/bullet-common/com/jme3/bullet/collision/RagdollCollisionListener.java
new file mode 100644
index 0000000..44805a4
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/collision/RagdollCollisionListener.java
@@ -0,0 +1,17 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision;
+
+import com.jme3.animation.Bone;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface RagdollCollisionListener {
+    
+    public void collide(Bone bone, PhysicsCollisionObject object, PhysicsCollisionEvent event);
+    
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java b/engine/src/bullet-common/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java
new file mode 100644
index 0000000..0223018
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java
@@ -0,0 +1,46 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision.shapes.infos;
+
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.export.*;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class ChildCollisionShape implements Savable {
+
+    public Vector3f location;
+    public Matrix3f rotation;
+    public CollisionShape shape;
+
+    public ChildCollisionShape() {
+    }
+
+    public ChildCollisionShape(Vector3f location, Matrix3f rotation, CollisionShape shape) {
+        this.location = location;
+        this.rotation = rotation;
+        this.shape = shape;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(location, "location", new Vector3f());
+        capsule.write(rotation, "rotation", new Matrix3f());
+        capsule.write(shape, "shape", new BoxCollisionShape(new Vector3f(1, 1, 1)));
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        location = (Vector3f) capsule.readSavable("location", new Vector3f());
+        rotation = (Matrix3f) capsule.readSavable("rotation", new Matrix3f());
+        shape = (CollisionShape) capsule.readSavable("shape", new BoxCollisionShape(new Vector3f(1, 1, 1)));
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/CharacterControl.java b/engine/src/bullet-common/com/jme3/bullet/control/CharacterControl.java
new file mode 100644
index 0000000..2acac53
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/CharacterControl.java
@@ -0,0 +1,208 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import java.io.IOException;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class CharacterControl extends PhysicsCharacter implements PhysicsControl {
+
+    protected Spatial spatial;
+    protected boolean enabled = true;
+    protected boolean added = false;
+    protected PhysicsSpace space = null;
+    protected Vector3f viewDirection = new Vector3f(Vector3f.UNIT_Z);
+    protected boolean useViewDirection = true;
+    protected boolean applyLocal = false;
+
+    public CharacterControl() {
+    }
+
+    public CharacterControl(CollisionShape shape, float stepHeight) {
+        super(shape, stepHeight);
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return applyLocal;
+    }
+
+    /**
+     * When set to true, the physics coordinates will be applied to the local
+     * translation of the Spatial
+     * @param applyPhysicsLocal
+     */
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        applyLocal = applyPhysicsLocal;
+    }
+
+    private Vector3f getSpatialTranslation() {
+        if (applyLocal) {
+            return spatial.getLocalTranslation();
+        }
+        return spatial.getWorldTranslation();
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        CharacterControl control = new CharacterControl(collisionShape, stepHeight);
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setFallSpeed(getFallSpeed());
+        control.setGravity(getGravity());
+        control.setJumpSpeed(getJumpSpeed());
+        control.setMaxSlope(getMaxSlope());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setUpAxis(getUpAxis());
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+
+        control.setSpatial(spatial);
+        return control;
+    }
+
+    public void setSpatial(Spatial spatial) {
+        if (getUserObject() == null || getUserObject() == this.spatial) {
+            setUserObject(spatial);
+        }
+        this.spatial = spatial;
+        if (spatial == null) {
+            if (getUserObject() == spatial) {
+                setUserObject(null);
+            }
+            return;
+        }
+        setPhysicsLocation(getSpatialTranslation());
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        if (space != null) {
+            if (enabled && !added) {
+                if (spatial != null) {
+                    warp(getSpatialTranslation());
+                }
+                space.addCollisionObject(this);
+                added = true;
+            } else if (!enabled && added) {
+                space.removeCollisionObject(this);
+                added = false;
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setViewDirection(Vector3f vec) {
+        viewDirection.set(vec);
+    }
+
+    public Vector3f getViewDirection() {
+        return viewDirection;
+    }
+
+    public boolean isUseViewDirection() {
+        return useViewDirection;
+    }
+
+    public void setUseViewDirection(boolean viewDirectionEnabled) {
+        this.useViewDirection = viewDirectionEnabled;
+    }
+
+    public void update(float tpf) {
+        if (enabled && spatial != null) {
+            Quaternion localRotationQuat = spatial.getLocalRotation();
+            Vector3f localLocation = spatial.getLocalTranslation();
+            if (!applyLocal && spatial.getParent() != null) {
+                getPhysicsLocation(localLocation);
+                localLocation.subtractLocal(spatial.getParent().getWorldTranslation());
+                localLocation.divideLocal(spatial.getParent().getWorldScale());
+                tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+                spatial.setLocalTranslation(localLocation);
+
+                if (useViewDirection) {
+                    localRotationQuat.lookAt(viewDirection, Vector3f.UNIT_Y);
+                    spatial.setLocalRotation(localRotationQuat);
+                }
+            } else {
+                spatial.setLocalTranslation(getPhysicsLocation());
+                localRotationQuat.lookAt(viewDirection, Vector3f.UNIT_Y);
+                spatial.setLocalRotation(localRotationQuat);
+            }
+        }
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (debugShape == null) {
+                attachDebugShape(space.getDebugManager());
+            }
+            debugShape.setLocalTranslation(getPhysicsLocation());
+            debugShape.updateLogicalState(0);
+            debugShape.updateGeometricState();
+            rm.renderScene(debugShape, vp);
+        }
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        if (space == null) {
+            if (this.space != null) {
+                this.space.removeCollisionObject(this);
+                added = false;
+            }
+        } else {
+            if (this.space == space) {
+                return;
+            }
+            space.addCollisionObject(this);
+            added = true;
+        }
+        this.space = space;
+    }
+
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(applyLocal, "applyLocalPhysics", false);
+        oc.write(useViewDirection, "viewDirectionEnabled", true);
+        oc.write(viewDirection, "viewDirection", new Vector3f(Vector3f.UNIT_Z));
+        oc.write(spatial, "spatial", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        useViewDirection = ic.readBoolean("viewDirectionEnabled", true);
+        viewDirection = (Vector3f) ic.readSavable("viewDirection", new Vector3f(Vector3f.UNIT_Z));
+        applyLocal = ic.readBoolean("applyLocalPhysics", false);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+        setUserObject(spatial);
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/GhostControl.java b/engine/src/bullet-common/com/jme3/bullet/control/GhostControl.java
new file mode 100644
index 0000000..99e5984
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/GhostControl.java
@@ -0,0 +1,178 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import java.io.IOException;
+
+/**
+ * A GhostControl moves with the spatial it is attached to and can be used to check
+ * overlaps with other physics objects (e.g. aggro radius).
+ * @author normenhansen
+ */
+public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
+
+    protected Spatial spatial;
+    protected boolean enabled = true;
+    protected boolean added = false;
+    protected PhysicsSpace space = null;
+    protected boolean applyLocal = false;
+
+    public GhostControl() {
+    }
+
+    public GhostControl(CollisionShape shape) {
+        super(shape);
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return applyLocal;
+    }
+
+    /**
+     * When set to true, the physics coordinates will be applied to the local
+     * translation of the Spatial
+     * @param applyPhysicsLocal
+     */
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        applyLocal = applyPhysicsLocal;
+    }
+
+    private Vector3f getSpatialTranslation() {
+        if (applyLocal) {
+            return spatial.getLocalTranslation();
+        }
+        return spatial.getWorldTranslation();
+    }
+
+    private Quaternion getSpatialRotation() {
+        if (applyLocal) {
+            return spatial.getLocalRotation();
+        }
+        return spatial.getWorldRotation();
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        GhostControl control = new GhostControl(collisionShape);
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setPhysicsRotation(getPhysicsRotationMatrix());
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+
+        control.setSpatial(spatial);
+        return control;
+    }
+
+    public void setSpatial(Spatial spatial) {
+        if (getUserObject() == null || getUserObject() == this.spatial) {
+            setUserObject(spatial);
+        }
+        this.spatial = spatial;
+        if (spatial == null) {
+            if (getUserObject() == spatial) {
+                setUserObject(null);
+            }
+            return;
+        }
+        setPhysicsLocation(getSpatialTranslation());
+        setPhysicsRotation(getSpatialRotation());
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        if (space != null) {
+            if (enabled && !added) {
+                if (spatial != null) {
+                    setPhysicsLocation(getSpatialTranslation());
+                    setPhysicsRotation(getSpatialRotation());
+                }
+                space.addCollisionObject(this);
+                added = true;
+            } else if (!enabled && added) {
+                space.removeCollisionObject(this);
+                added = false;
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void update(float tpf) {
+        if (!enabled) {
+            return;
+        }
+        setPhysicsLocation(getSpatialTranslation());
+        setPhysicsRotation(getSpatialRotation());
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (debugShape == null) {
+                attachDebugShape(space.getDebugManager());
+            }
+            debugShape.setLocalTranslation(spatial.getWorldTranslation());
+            debugShape.setLocalRotation(spatial.getWorldRotation());
+            debugShape.updateLogicalState(0);
+            debugShape.updateGeometricState();
+            rm.renderScene(debugShape, vp);
+        }
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        if (space == null) {
+            if (this.space != null) {
+                this.space.removeCollisionObject(this);
+                added = false;
+            }
+        } else {
+            if (this.space == space) {
+                return;
+            }
+            space.addCollisionObject(this);
+            added = true;
+        }
+        this.space = space;
+    }
+
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(applyLocal, "applyLocalPhysics", false);
+        oc.write(spatial, "spatial", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+        applyLocal = ic.readBoolean("applyLocalPhysics", false);
+        setUserObject(spatial);
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/KinematicRagdollControl.java b/engine/src/bullet-common/com/jme3/bullet/control/KinematicRagdollControl.java
new file mode 100644
index 0000000..1da0442
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/KinematicRagdollControl.java
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.control;
+
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionEvent;
+import com.jme3.bullet.collision.PhysicsCollisionListener;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.RagdollCollisionListener;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.HullCollisionShape;
+import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset;
+import com.jme3.bullet.control.ragdoll.RagdollPreset;
+import com.jme3.bullet.control.ragdoll.RagdollUtils;
+import com.jme3.bullet.joints.SixDofJoint;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**<strong>This control is still a WIP, use it at your own risk</strong><br>
+ * To use this control you need a model with an AnimControl and a SkeletonControl.<br>
+ * This should be the case if you imported an animated model from Ogre or blender.<br>
+ * Note enabling/disabling the control add/removes it from the physic space<br>
+ * <p>
+ * This control creates collision shapes for each bones of the skeleton when you call spatial.addControl(ragdollControl).
+ * <ul>
+ *     <li>The shape is HullCollision shape based on the vertices associated with each bone and based on a tweakable weight threshold (see setWeightThreshold)</li>
+ *     <li>If you don't want each bone to be a collision shape, you can specify what bone to use by using the addBoneName method<br>
+ *         By using this method, bone that are not used to create a shape, are "merged" to their parent to create the collision shape.
+ *     </li>
+ * </ul>
+ *</p>
+ *<p>
+ *There are 2 modes for this control : 
+ * <ul>
+ *     <li><strong>The kinematic modes :</strong><br>
+ *        this is the default behavior, this means that the collision shapes of the body are able to interact with physics enabled objects.
+ *        in this mode physic shapes follow the moovements of the animated skeleton (for example animated by a key framed animation)
+ *        this mode is enabled by calling setKinematicMode();                
+ *     </li>
+ *     <li><strong>The ragdoll modes :</strong><br>
+ *        To enable this behavior, you need to call setRagdollMode() method.
+ *        In this mode the charater is entirely controled by physics, so it will fall under the gravity and move if any force is applied to it.
+ *     </li>
+ * </ul>
+ *</p>
+ *
+ * @author Normen Hansen and Rémy Bouquet (Nehon)
+ */
+public class KinematicRagdollControl implements PhysicsControl, PhysicsCollisionListener {
+
+    protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
+    protected Map<String, PhysicsBoneLink> boneLinks = new HashMap<String, PhysicsBoneLink>();
+    protected Skeleton skeleton;
+    protected PhysicsSpace space;
+    protected boolean enabled = true;
+    protected boolean debug = false;
+    protected PhysicsRigidBody baseRigidBody;
+    protected float weightThreshold = -1.0f;
+    protected Spatial targetModel;
+    protected Vector3f initScale;
+    protected Mode mode = Mode.Kinetmatic;
+    protected boolean blendedControl = false;
+    protected float blendTime = 1.0f;
+    protected float blendStart = 0.0f;
+    protected List<RagdollCollisionListener> listeners;
+    protected float eventDispatchImpulseThreshold = 10;
+    protected RagdollPreset preset = new HumanoidRagdollPreset();
+    protected Set<String> boneList = new TreeSet<String>();
+    protected Vector3f modelPosition = new Vector3f();
+    protected Quaternion modelRotation = new Quaternion();
+    protected float rootMass = 15;
+    protected float totalMass = 0;
+    protected boolean added = false;
+
+    public static enum Mode {
+
+        Kinetmatic,
+        Ragdoll
+    }
+
+    protected class PhysicsBoneLink {
+
+        protected Bone bone;
+        protected Quaternion initalWorldRotation;
+        protected SixDofJoint joint;
+        protected PhysicsRigidBody rigidBody;
+        protected Quaternion startBlendingRot = new Quaternion();
+        protected Vector3f startBlendingPos = new Vector3f();
+    }
+
+    /**
+     * contruct a KinematicRagdollControl
+     */
+    public KinematicRagdollControl() {
+    }
+
+    public KinematicRagdollControl(float weightThreshold) {
+        this.weightThreshold = weightThreshold;
+    }
+
+    public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) {
+        this.preset = preset;
+        this.weightThreshold = weightThreshold;
+    }
+
+    public KinematicRagdollControl(RagdollPreset preset) {
+        this.preset = preset;
+    }
+
+    public void update(float tpf) {
+        if (!enabled) {
+            return;
+        }
+        TempVars vars = TempVars.get();
+        
+        Quaternion tmpRot1 = vars.quat1;
+        Quaternion tmpRot2 = vars.quat2;
+
+        //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
+        if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
+            for (PhysicsBoneLink link : boneLinks.values()) {
+
+                Vector3f position = vars.vect1;
+
+                //retrieving bone position in physic world space
+                Vector3f p = link.rigidBody.getMotionState().getWorldLocation();
+                //transforming this position with inverse transforms of the model
+                targetModel.getWorldTransform().transformInverseVector(p, position);
+
+                //retrieving bone rotation in physic world space
+                Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat();
+
+                //multiplying this rotation by the initialWorld rotation of the bone, 
+                //then transforming it with the inverse world rotation of the model
+                tmpRot1.set(q).multLocal(link.initalWorldRotation);
+                tmpRot2.set(targetModel.getWorldRotation()).inverseLocal().mult(tmpRot1, tmpRot1);
+                tmpRot1.normalizeLocal();
+
+                //if the bone is the root bone, we apply the physic's transform to the model, so its position and rotation are correctly updated
+                if (link.bone.getParent() == null) {
+
+                    //offsetting the physic's position/rotation by the root bone inverse model space position/rotaion
+                    modelPosition.set(p).subtractLocal(link.bone.getWorldBindPosition());
+                    targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition);
+                    modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getWorldBindRotation()).inverseLocal());
+
+
+                    //applying transforms to the model
+                    targetModel.setLocalTranslation(modelPosition);
+
+                    targetModel.setLocalRotation(modelRotation);
+
+                    //Applying computed transforms to the bone
+                    link.bone.setUserTransformsWorld(position, tmpRot1);
+
+                } else {
+                    //if boneList is empty, this means that every bone in the ragdoll has a collision shape,
+                    //so we just update the bone position
+                    if (boneList.isEmpty()) {
+                        link.bone.setUserTransformsWorld(position, tmpRot1);
+                    } else {
+                        //boneList is not empty, this means some bones of the skeleton might not be associated with a collision shape.
+                        //So we update them recusively
+                        RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList);
+                    }
+                }
+            }
+        } else {
+            //the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces
+            for (PhysicsBoneLink link : boneLinks.values()) {
+
+                Vector3f position = vars.vect1;
+
+                //if blended control this means, keyframed animation is updating the skeleton, 
+                //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll
+                if (blendedControl) {
+                    Vector3f position2 = vars.vect2;
+                    //initializing tmp vars with the start position/rotation of the ragdoll
+                    position.set(link.startBlendingPos);
+                    tmpRot1.set(link.startBlendingRot);
+
+                    //interpolating between ragdoll position/rotation and keyframed position/rotation
+                    tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime);
+                    position2.set(position).interpolate(link.bone.getModelSpacePosition(), blendStart / blendTime);
+                    tmpRot1.set(tmpRot2);
+                    position.set(position2);
+
+                    //updating bones transforms
+                    if (boneList.isEmpty()) {
+                        //we ensure we have the control to update the bone
+                        link.bone.setUserControl(true);
+                        link.bone.setUserTransformsWorld(position, tmpRot1);
+                        //we give control back to the key framed animation.
+                        link.bone.setUserControl(false);
+                    } else {
+                        RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList);
+                    }
+
+                }
+                //setting skeleton transforms to the ragdoll
+                matchPhysicObjectToBone(link, position, tmpRot1);
+                modelPosition.set(targetModel.getLocalTranslation());
+
+            }
+
+            //time control for blending
+            if (blendedControl) {
+                blendStart += tpf;
+                if (blendStart > blendTime) {
+                    blendedControl = false;
+                }
+            }
+        }
+        vars.release();
+
+    }
+
+    /**
+     * Set the transforms of a rigidBody to match the transforms of a bone.
+     * this is used to make the ragdoll follow the skeleton motion while in Kinematic mode
+     * @param link the link containing the bone and the rigidBody
+     * @param position just a temp vector for position
+     * @param tmpRot1  just a temp quaternion for rotation
+     */
+    private void matchPhysicObjectToBone(PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1) {
+        //computing position from rotation and scale
+        targetModel.getWorldTransform().transformVector(link.bone.getModelSpacePosition(), position);
+
+        //computing rotation
+        tmpRot1.set(link.bone.getModelSpaceRotation()).multLocal(link.bone.getWorldBindInverseRotation());
+        targetModel.getWorldRotation().mult(tmpRot1, tmpRot1);
+        tmpRot1.normalizeLocal();
+
+        //updating physic location/rotation of the physic bone
+        link.rigidBody.setPhysicsLocation(position);
+        link.rigidBody.setPhysicsRotation(tmpRot1);
+
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * rebuild the ragdoll
+     * this is useful if you applied scale on the ragdoll after it's been initialized
+     */
+    public void reBuild() {
+        setSpatial(targetModel);
+        addToPhysicsSpace();
+    }
+
+    public void setSpatial(Spatial model) {
+        if (model == null) {
+            removeFromPhysicsSpace();
+            clearData();
+            return;
+        }
+        targetModel = model;
+        Node parent = model.getParent();
+
+
+        Vector3f initPosition = model.getLocalTranslation().clone();
+        Quaternion initRotation = model.getLocalRotation().clone();
+        initScale = model.getLocalScale().clone();
+
+        model.removeFromParent();
+        model.setLocalTranslation(Vector3f.ZERO);
+        model.setLocalRotation(Quaternion.IDENTITY);
+        model.setLocalScale(1);
+        //HACK ALERT change this
+        //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack
+        //Find a proper way to order the controls.
+        SkeletonControl sc = model.getControl(SkeletonControl.class);
+        model.removeControl(sc);
+        model.addControl(sc);
+        //---- 
+
+        removeFromPhysicsSpace();
+        clearData();
+        // put into bind pose and compute bone transforms in model space
+        // maybe dont reset to ragdoll out of animations?
+        scanSpatial(model);
+
+
+        if (parent != null) {
+            parent.attachChild(model);
+
+        }
+        model.setLocalTranslation(initPosition);
+        model.setLocalRotation(initRotation);
+        model.setLocalScale(initScale);
+
+        logger.log(Level.INFO, "Created physics ragdoll for skeleton {0}", skeleton);
+    }
+
+    /**
+     * Add a bone name to this control
+     * Using this method you can specify which bones of the skeleton will be used to build the collision shapes.
+     * @param name 
+     */
+    public void addBoneName(String name) {
+        boneList.add(name);
+    }
+
+    private void scanSpatial(Spatial model) {
+        AnimControl animControl = model.getControl(AnimControl.class);
+        Map<Integer, List<Float>> pointsMap = null;
+        if (weightThreshold == -1.0f) {
+            pointsMap = RagdollUtils.buildPointMap(model);
+        }
+
+        skeleton = animControl.getSkeleton();
+        skeleton.resetAndUpdate();
+        for (int i = 0; i < skeleton.getRoots().length; i++) {
+            Bone childBone = skeleton.getRoots()[i];
+            if (childBone.getParent() == null) {
+                logger.log(Level.INFO, "Found root bone in skeleton {0}", skeleton);
+                baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1);
+                baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
+                boneRecursion(model, childBone, baseRigidBody, 1, pointsMap);
+            }
+        }
+    }
+
+    private void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map<Integer, List<Float>> pointsMap) {
+        PhysicsRigidBody parentShape = parent;
+        if (boneList.isEmpty() || boneList.contains(bone.getName())) {
+
+            PhysicsBoneLink link = new PhysicsBoneLink();
+            link.bone = bone;
+
+            //creating the collision shape 
+            HullCollisionShape shape = null;
+            if (pointsMap != null) {
+                //build a shape for the bone, using the vertices that are most influenced by this bone
+                shape = RagdollUtils.makeShapeFromPointMap(pointsMap, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition());
+            } else {
+                //build a shape for the bone, using the vertices associated with this bone with a weight above the threshold
+                shape = RagdollUtils.makeShapeFromVerticeWeights(model, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition(), weightThreshold);
+            }
+
+            PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / (float) reccount);
+
+            shapeNode.setKinematic(mode == Mode.Kinetmatic);
+            totalMass += rootMass / (float) reccount;
+
+            link.rigidBody = shapeNode;
+            link.initalWorldRotation = bone.getModelSpaceRotation().clone();
+
+            if (parent != null) {
+                //get joint position for parent
+                Vector3f posToParent = new Vector3f();
+                if (bone.getParent() != null) {
+                    bone.getModelSpacePosition().subtract(bone.getParent().getModelSpacePosition(), posToParent).multLocal(initScale);
+                }
+
+                SixDofJoint joint = new SixDofJoint(parent, shapeNode, posToParent, new Vector3f(0, 0, 0f), true);
+                preset.setupJointForBone(bone.getName(), joint);
+
+                link.joint = joint;
+                joint.setCollisionBetweenLinkedBodys(false);
+            }
+            boneLinks.put(bone.getName(), link);
+            shapeNode.setUserObject(link);
+            parentShape = shapeNode;
+        }
+
+        for (Iterator<Bone> it = bone.getChildren().iterator(); it.hasNext();) {
+            Bone childBone = it.next();
+            boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap);
+        }
+    }
+
+    /**
+     * Set the joint limits for the joint between the given bone and its parent.
+     * This method can't work before attaching the control to a spatial
+     * @param boneName the name of the bone
+     * @param maxX the maximum rotation on the x axis (in radians)
+     * @param minX the minimum rotation on the x axis (in radians)
+     * @param maxY the maximum rotation on the y axis (in radians)
+     * @param minY the minimum rotation on the z axis (in radians)
+     * @param maxZ the maximum rotation on the z axis (in radians)
+     * @param minZ the minimum rotation on the z axis (in radians)
+     */
+    public void setJointLimit(String boneName, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) {
+        PhysicsBoneLink link = boneLinks.get(boneName);
+        if (link != null) {
+            RagdollUtils.setJointLimit(link.joint, maxX, minX, maxY, minY, maxZ, minZ);
+        } else {
+            logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName);
+        }
+    }
+
+    /**
+     * Return the joint between the given bone and its parent.
+     * This return null if it's called before attaching the control to a spatial
+     * @param boneName the name of the bone
+     * @return the joint between the given bone and its parent
+     */
+    public SixDofJoint getJoint(String boneName) {
+        PhysicsBoneLink link = boneLinks.get(boneName);
+        if (link != null) {
+            return link.joint;
+        } else {
+            logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName);
+            return null;
+        }
+    }
+
+    private void clearData() {
+        boneLinks.clear();
+        baseRigidBody = null;
+    }
+
+    private void addToPhysicsSpace() {
+        if (space == null) {
+            return;
+        }
+        if (baseRigidBody != null) {
+            space.add(baseRigidBody);
+            added = true;
+        }
+        for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
+            PhysicsBoneLink physicsBoneLink = it.next();
+            if (physicsBoneLink.rigidBody != null) {
+                space.add(physicsBoneLink.rigidBody);
+                if (physicsBoneLink.joint != null) {
+                    space.add(physicsBoneLink.joint);
+
+                }
+                added = true;
+            }
+        }
+    }
+
+    protected void removeFromPhysicsSpace() {
+        if (space == null) {
+            return;
+        }
+        if (baseRigidBody != null) {
+            space.remove(baseRigidBody);
+        }
+        for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
+            PhysicsBoneLink physicsBoneLink = it.next();
+            if (physicsBoneLink.joint != null) {
+                space.remove(physicsBoneLink.joint);
+                if (physicsBoneLink.rigidBody != null) {
+                    space.remove(physicsBoneLink.rigidBody);
+                }
+            }
+        }
+        added = false;
+    }
+
+    /**
+     * enable or disable the control
+     * note that if enabled is true and that the physic space has been set on the ragdoll, the ragdoll is added to the physic space     
+     * if enabled is false the ragdoll is removed from physic space.
+     * @param enabled 
+     */
+    public void setEnabled(boolean enabled) {
+        if (this.enabled == enabled) {
+            return;
+        }
+        this.enabled = enabled;
+        if (!enabled && space != null) {
+            removeFromPhysicsSpace();
+        } else if (enabled && space != null) {
+            addToPhysicsSpace();
+        }
+    }
+
+    /**
+     * returns true if the control is enabled
+     * @return 
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    protected void attachDebugShape(AssetManager manager) {
+        for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
+            PhysicsBoneLink physicsBoneLink = it.next();
+            physicsBoneLink.rigidBody.createDebugShape(manager);
+        }
+        debug = true;
+    }
+
+    protected void detachDebugShape() {
+        for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
+            PhysicsBoneLink physicsBoneLink = it.next();
+            physicsBoneLink.rigidBody.detachDebugShape();
+        }
+        debug = false;
+    }
+
+    /**
+     * For internal use only
+     * specific render for the ragdoll(if debugging)      
+     * @param rm
+     * @param vp 
+     */
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (!debug) {
+                attachDebugShape(space.getDebugManager());
+            }
+            for (Iterator<PhysicsBoneLink> it = boneLinks.values().iterator(); it.hasNext();) {
+                PhysicsBoneLink physicsBoneLink = it.next();
+                Spatial debugShape = physicsBoneLink.rigidBody.debugShape();
+                if (debugShape != null) {
+                    debugShape.setLocalTranslation(physicsBoneLink.rigidBody.getMotionState().getWorldLocation());
+                    debugShape.setLocalRotation(physicsBoneLink.rigidBody.getMotionState().getWorldRotationQuat());
+                    debugShape.updateGeometricState();
+                    rm.renderScene(debugShape, vp);
+                }
+            }
+        }
+    }
+
+    /**
+     * set the physic space to this ragdoll
+     * @param space 
+     */
+    public void setPhysicsSpace(PhysicsSpace space) {
+        if (space == null) {
+            removeFromPhysicsSpace();
+            this.space = space;
+        } else {
+            if (this.space == space) {
+                return;
+            }
+            this.space = space;
+            addToPhysicsSpace();
+            this.space.addCollisionListener(this);
+        }
+    }
+
+    /**
+     * returns the physic space
+     * @return 
+     */
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    /**
+     * serialize this control
+     * @param ex
+     * @throws IOException 
+     */
+    public void write(JmeExporter ex) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * de-serialize this control
+     * @param im
+     * @throws IOException 
+     */
+    public void read(JmeImporter im) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * For internal use only
+     * callback for collisionevent
+     * @param event 
+     */
+    public void collision(PhysicsCollisionEvent event) {
+        PhysicsCollisionObject objA = event.getObjectA();
+        PhysicsCollisionObject objB = event.getObjectB();
+
+        //excluding collisions that involve 2 parts of the ragdoll
+        if (event.getNodeA() == null && event.getNodeB() == null) {
+            return;
+        }
+
+        //discarding low impulse collision
+        if (event.getAppliedImpulse() < eventDispatchImpulseThreshold) {
+            return;
+        }
+
+        boolean hit = false;
+        Bone hitBone = null;
+        PhysicsCollisionObject hitObject = null;
+
+        //Computing which bone has been hit
+        if (objA.getUserObject() instanceof PhysicsBoneLink) {
+            PhysicsBoneLink link = (PhysicsBoneLink) objA.getUserObject();
+            if (link != null) {
+                hit = true;
+                hitBone = link.bone;
+                hitObject = objB;
+            }
+        }
+
+        if (objB.getUserObject() instanceof PhysicsBoneLink) {
+            PhysicsBoneLink link = (PhysicsBoneLink) objB.getUserObject();
+            if (link != null) {
+                hit = true;
+                hitBone = link.bone;
+                hitObject = objA;
+
+            }
+        }
+
+        //dispatching the event if the ragdoll has been hit
+        if (hit && listeners != null) {
+            for (RagdollCollisionListener listener : listeners) {
+                listener.collide(hitBone, hitObject, event);
+            }
+        }
+
+    }
+
+    /**
+     * Enable or disable the ragdoll behaviour.
+     * if ragdollEnabled is true, the character motion will only be powerd by physics
+     * else, the characted will be animated by the keyframe animation, 
+     * but will be able to physically interact with its physic environnement
+     * @param ragdollEnabled 
+     */
+    protected void setMode(Mode mode) {
+        this.mode = mode;
+        AnimControl animControl = targetModel.getControl(AnimControl.class);
+        animControl.setEnabled(mode == Mode.Kinetmatic);
+
+        baseRigidBody.setKinematic(mode == Mode.Kinetmatic);
+        TempVars vars = TempVars.get();
+        
+        for (PhysicsBoneLink link : boneLinks.values()) {
+            link.rigidBody.setKinematic(mode == Mode.Kinetmatic);
+            if (mode == Mode.Ragdoll) {
+                Quaternion tmpRot1 = vars.quat1;
+                Vector3f position = vars.vect1;
+                //making sure that the ragdoll is at the correct place.
+                matchPhysicObjectToBone(link, position, tmpRot1);
+            }
+
+        }
+        vars.release();
+
+        for (Bone bone : skeleton.getRoots()) {
+            RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
+        }
+    }
+
+    /**
+     * Smoothly blend from Ragdoll mode to Kinematic mode
+     * This is useful to blend ragdoll actual position to a keyframe animation for example
+     * @param blendTime the blending time between ragdoll to anim.
+     */
+    public void blendToKinematicMode(float blendTime) {
+        if (mode == Mode.Kinetmatic) {
+            return;
+        }
+        blendedControl = true;
+        this.blendTime = blendTime;
+        mode = Mode.Kinetmatic;
+        AnimControl animControl = targetModel.getControl(AnimControl.class);
+        animControl.setEnabled(true);
+
+
+        TempVars vars = TempVars.get();        
+        for (PhysicsBoneLink link : boneLinks.values()) {
+
+            Vector3f p = link.rigidBody.getMotionState().getWorldLocation();
+            Vector3f position = vars.vect1;
+
+            targetModel.getWorldTransform().transformInverseVector(p, position);
+
+            Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat();
+            Quaternion q2 = vars.quat1;
+            Quaternion q3 = vars.quat2;
+
+            q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal();
+            q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2);
+            q2.normalizeLocal();
+            link.startBlendingPos.set(position);
+            link.startBlendingRot.set(q2);
+            link.rigidBody.setKinematic(true);
+        }
+        vars.release();
+
+        for (Bone bone : skeleton.getRoots()) {
+            RagdollUtils.setUserControl(bone, false);
+        }
+
+        blendStart = 0;
+    }
+
+    /**
+     * Set the control into Kinematic mode
+     * In theis mode, the collision shapes follow the movements of the skeleton,
+     * and can interact with physical environement
+     */
+    public void setKinematicMode() {
+        if (mode != Mode.Kinetmatic) {
+            setMode(Mode.Kinetmatic);
+        }
+    }
+
+    /**
+     * Sets the control into Ragdoll mode
+     * The skeleton is entirely controlled by physics.
+     */
+    public void setRagdollMode() {
+        if (mode != Mode.Ragdoll) {
+            setMode(Mode.Ragdoll);
+        }
+    }
+
+    /**
+     * retruns the mode of this control
+     * @return 
+     */
+    public Mode getMode() {
+        return mode;
+    }
+
+    /**
+     * add a 
+     * @param listener 
+     */
+    public void addCollisionListener(RagdollCollisionListener listener) {
+        if (listeners == null) {
+            listeners = new ArrayList<RagdollCollisionListener>();
+        }
+        listeners.add(listener);
+    }
+
+    public void setRootMass(float rootMass) {
+        this.rootMass = rootMass;
+    }
+
+    public float getTotalMass() {
+        return totalMass;
+    }
+
+    public float getWeightThreshold() {
+        return weightThreshold;
+    }
+
+    public void setWeightThreshold(float weightThreshold) {
+        this.weightThreshold = weightThreshold;
+    }
+
+    public float getEventDispatchImpulseThreshold() {
+        return eventDispatchImpulseThreshold;
+    }
+
+    public void setEventDispatchImpulseThreshold(float eventDispatchImpulseThreshold) {
+        this.eventDispatchImpulseThreshold = eventDispatchImpulseThreshold;
+    }
+
+    /**
+     * Set the CcdMotionThreshold of all the bone's rigidBodies of the ragdoll
+     * @see PhysicsRigidBody#setCcdMotionThreshold(float) 
+     * @param value 
+     */
+    public void setCcdMotionThreshold(float value) {
+        for (PhysicsBoneLink link : boneLinks.values()) {
+            link.rigidBody.setCcdMotionThreshold(value);
+        }
+    }
+
+    /**
+     * Set the CcdSweptSphereRadius of all the bone's rigidBodies of the ragdoll
+     * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) 
+     * @param value 
+     */
+    public void setCcdSweptSphereRadius(float value) {
+        for (PhysicsBoneLink link : boneLinks.values()) {
+            link.rigidBody.setCcdSweptSphereRadius(value);
+        }
+    }
+
+    /**
+     * Set the CcdMotionThreshold of the given bone's rigidBodies of the ragdoll
+     * @see PhysicsRigidBody#setCcdMotionThreshold(float) 
+     * @param value 
+     * @deprecated use getBoneRigidBody(String BoneName).setCcdMotionThreshold(float) instead
+     */
+    @Deprecated
+    public void setBoneCcdMotionThreshold(String boneName, float value) {
+        PhysicsBoneLink link = boneLinks.get(boneName);
+        if (link != null) {
+            link.rigidBody.setCcdMotionThreshold(value);
+        }
+    }
+
+    /**
+     * Set the CcdSweptSphereRadius of the given bone's rigidBodies of the ragdoll
+     * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) 
+     * @param value 
+     * @deprecated use getBoneRigidBody(String BoneName).setCcdSweptSphereRadius(float) instead
+     */
+    @Deprecated
+    public void setBoneCcdSweptSphereRadius(String boneName, float value) {
+        PhysicsBoneLink link = boneLinks.get(boneName);
+        if (link != null) {
+            link.rigidBody.setCcdSweptSphereRadius(value);
+        }
+    }
+
+    /**
+     * return the rigidBody associated to the given bone
+     * @param boneName the name of the bone
+     * @return the associated rigidBody.
+     */
+    public PhysicsRigidBody getBoneRigidBody(String boneName) {
+        PhysicsBoneLink link = boneLinks.get(boneName);
+        if (link != null) {
+            return link.rigidBody;
+        }
+        return null;
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/PhysicsControl.java b/engine/src/bullet-common/com/jme3/bullet/control/PhysicsControl.java
new file mode 100644
index 0000000..ba65157
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/PhysicsControl.java
@@ -0,0 +1,28 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.scene.control.Control;
+
+/**
+ *
+ * @author normenhansen
+ */
+public interface PhysicsControl extends Control {
+
+    public void setPhysicsSpace(PhysicsSpace space);
+
+    public PhysicsSpace getPhysicsSpace();
+
+    /**
+     * The physics object is removed from the physics space when the control
+     * is disabled. When the control is enabled  again the physics object is
+     * moved to the current location of the spatial and then added to the physics
+     * space. This allows disabling/enabling physics to move the spatial freely.
+     * @param state
+     */
+    public void setEnabled(boolean state);
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/RigidBodyControl.java b/engine/src/bullet-common/com/jme3/bullet/control/RigidBodyControl.java
new file mode 100644
index 0000000..4fbce1e
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/RigidBodyControl.java
@@ -0,0 +1,264 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.CollisionShapeFactory;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import java.io.IOException;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl {
+
+    protected Spatial spatial;
+    protected boolean enabled = true;
+    protected boolean added = false;
+    protected PhysicsSpace space = null;
+    protected boolean kinematicSpatial = true;
+
+    public RigidBodyControl() {
+    }
+
+    /**
+     * When using this constructor, the CollisionShape for the RigidBody is generated
+     * automatically when the Control is added to a Spatial.
+     * @param mass When not 0, a HullCollisionShape is generated, otherwise a MeshCollisionShape is used. For geometries with box or sphere meshes the proper box or sphere collision shape is used.
+     */
+    public RigidBodyControl(float mass) {
+        this.mass = mass;
+    }
+
+    /**
+     * Creates a new PhysicsNode with the supplied collision shape and mass 1
+     * @param shape
+     */
+    public RigidBodyControl(CollisionShape shape) {
+        super(shape);
+    }
+
+    public RigidBodyControl(CollisionShape shape, float mass) {
+        super(shape, mass);
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
+        control.setAngularFactor(getAngularFactor());
+        control.setAngularSleepingThreshold(getAngularSleepingThreshold());
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setDamping(getLinearDamping(), getAngularDamping());
+        control.setFriction(getFriction());
+        control.setGravity(getGravity());
+        control.setKinematic(isKinematic());
+        control.setKinematicSpatial(isKinematicSpatial());
+        control.setLinearSleepingThreshold(getLinearSleepingThreshold());
+        control.setPhysicsLocation(getPhysicsLocation(null));
+        control.setPhysicsRotation(getPhysicsRotationMatrix(null));
+        control.setRestitution(getRestitution());
+
+        if (mass > 0) {
+            control.setAngularVelocity(getAngularVelocity());
+            control.setLinearVelocity(getLinearVelocity());
+        }
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+
+        control.setSpatial(spatial);
+        return control;
+    }
+
+    public void setSpatial(Spatial spatial) {
+        if (getUserObject() == null || getUserObject() == this.spatial) {
+            setUserObject(spatial);
+        }
+        this.spatial = spatial;
+        if (spatial == null) {
+            if (getUserObject() == spatial) {
+                setUserObject(null);
+            }
+            spatial = null;
+            collisionShape = null;
+            return;
+        }
+        if (collisionShape == null) {
+            createCollisionShape();
+            rebuildRigidBody();
+        }
+        setPhysicsLocation(getSpatialTranslation());
+        setPhysicsRotation(getSpatialRotation());
+    }
+
+    protected void createCollisionShape() {
+        if (spatial == null) {
+            return;
+        }
+        if (spatial instanceof Geometry) {
+            Geometry geom = (Geometry) spatial;
+            Mesh mesh = geom.getMesh();
+            if (mesh instanceof Sphere) {
+                collisionShape = new SphereCollisionShape(((Sphere) mesh).getRadius());
+                return;
+            } else if (mesh instanceof Box) {
+                collisionShape = new BoxCollisionShape(new Vector3f(((Box) mesh).getXExtent(), ((Box) mesh).getYExtent(), ((Box) mesh).getZExtent()));
+                return;
+            }
+        }
+        if (mass > 0) {
+            collisionShape = CollisionShapeFactory.createDynamicMeshShape(spatial);
+        } else {
+            collisionShape = CollisionShapeFactory.createMeshShape(spatial);
+        }
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        if (space != null) {
+            if (enabled && !added) {
+                if (spatial != null) {
+                    setPhysicsLocation(getSpatialTranslation());
+                    setPhysicsRotation(getSpatialRotation());
+                }
+                space.addCollisionObject(this);
+                added = true;
+            } else if (!enabled && added) {
+                space.removeCollisionObject(this);
+                added = false;
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Checks if this control is in kinematic spatial mode.
+     * @return true if the spatial location is applied to this kinematic rigidbody
+     */
+    public boolean isKinematicSpatial() {
+        return kinematicSpatial;
+    }
+
+    /**
+     * Sets this control to kinematic spatial mode so that the spatials transform will
+     * be applied to the rigidbody in kinematic mode, defaults to true.
+     * @param kinematicSpatial
+     */
+    public void setKinematicSpatial(boolean kinematicSpatial) {
+        this.kinematicSpatial = kinematicSpatial;
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return motionState.isApplyPhysicsLocal();
+    }
+
+    /**
+     * When set to true, the physics coordinates will be applied to the local
+     * translation of the Spatial instead of the world traslation.
+     * @param applyPhysicsLocal
+     */
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        motionState.setApplyPhysicsLocal(applyPhysicsLocal);
+    }
+
+    private Vector3f getSpatialTranslation(){
+        if(motionState.isApplyPhysicsLocal()){
+            return spatial.getLocalTranslation();
+        }
+        return spatial.getWorldTranslation();
+    }
+
+    private Quaternion getSpatialRotation(){
+        if(motionState.isApplyPhysicsLocal()){
+            return spatial.getLocalRotation();
+        }
+        return spatial.getWorldRotation();
+    }
+
+    public void update(float tpf) {
+        if (enabled && spatial != null) {
+            if (isKinematic() && kinematicSpatial) {
+                super.setPhysicsLocation(getSpatialTranslation());
+                super.setPhysicsRotation(getSpatialRotation());
+            } else {
+                getMotionState().applyTransform(spatial);
+            }
+        }
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (debugShape == null) {
+                attachDebugShape(space.getDebugManager());
+            }
+            //TODO: using spatial traslation/rotation..
+            debugShape.setLocalTranslation(spatial.getWorldTranslation());
+            debugShape.setLocalRotation(spatial.getWorldRotation());
+            debugShape.updateLogicalState(0);
+            debugShape.updateGeometricState();
+            rm.renderScene(debugShape, vp);
+        }
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        if (space == null) {
+            if (this.space != null) {
+                this.space.removeCollisionObject(this);
+                added = false;
+            }
+        } else {
+            if(this.space==space) return;
+            space.addCollisionObject(this);
+            added = true;
+        }
+        this.space = space;
+    }
+
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(motionState.isApplyPhysicsLocal(), "applyLocalPhysics", false);
+        oc.write(kinematicSpatial, "kinematicSpatial", true);
+        oc.write(spatial, "spatial", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        kinematicSpatial = ic.readBoolean("kinematicSpatial", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+        motionState.setApplyPhysicsLocal(ic.readBoolean("applyLocalPhysics", false));
+        setUserObject(spatial);
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/VehicleControl.java b/engine/src/bullet-common/com/jme3/bullet/control/VehicleControl.java
new file mode 100644
index 0000000..0f04f41
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/VehicleControl.java
@@ -0,0 +1,268 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.bullet.objects.VehicleWheel;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
+
+    protected Spatial spatial;
+    protected boolean enabled = true;
+    protected PhysicsSpace space = null;
+    protected boolean added = false;
+
+    public VehicleControl() {
+    }
+
+    /**
+     * Creates a new PhysicsNode with the supplied collision shape
+     * @param shape
+     */
+    public VehicleControl(CollisionShape shape) {
+        super(shape);
+    }
+
+    public VehicleControl(CollisionShape shape, float mass) {
+        super(shape, mass);
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return motionState.isApplyPhysicsLocal();
+    }
+
+    /**
+     * When set to true, the physics coordinates will be applied to the local
+     * translation of the Spatial
+     * @param applyPhysicsLocal
+     */
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        motionState.setApplyPhysicsLocal(applyPhysicsLocal);
+        for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+            VehicleWheel vehicleWheel = it.next();
+            vehicleWheel.setApplyLocal(applyPhysicsLocal);
+        }
+    }
+
+    private Vector3f getSpatialTranslation(){
+        if(motionState.isApplyPhysicsLocal()){
+            return spatial.getLocalTranslation();
+        }
+        return spatial.getWorldTranslation();
+    }
+
+    private Quaternion getSpatialRotation(){
+        if(motionState.isApplyPhysicsLocal()){
+            return spatial.getLocalRotation();
+        }
+        return spatial.getWorldRotation();
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        VehicleControl control = new VehicleControl(collisionShape, mass);
+        control.setAngularFactor(getAngularFactor());
+        control.setAngularSleepingThreshold(getAngularSleepingThreshold());
+        control.setAngularVelocity(getAngularVelocity());
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setDamping(getLinearDamping(), getAngularDamping());
+        control.setFriction(getFriction());
+        control.setGravity(getGravity());
+        control.setKinematic(isKinematic());
+        control.setLinearSleepingThreshold(getLinearSleepingThreshold());
+        control.setLinearVelocity(getLinearVelocity());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setPhysicsRotation(getPhysicsRotationMatrix());
+        control.setRestitution(getRestitution());
+
+        control.setFrictionSlip(getFrictionSlip());
+        control.setMaxSuspensionTravelCm(getMaxSuspensionTravelCm());
+        control.setSuspensionStiffness(getSuspensionStiffness());
+        control.setSuspensionCompression(tuning.suspensionCompression);
+        control.setSuspensionDamping(tuning.suspensionDamping);
+        control.setMaxSuspensionForce(getMaxSuspensionForce());
+
+        for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+            VehicleWheel wheel = it.next();
+            VehicleWheel newWheel = control.addWheel(wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), wheel.isFrontWheel());
+            newWheel.setFrictionSlip(wheel.getFrictionSlip());
+            newWheel.setMaxSuspensionTravelCm(wheel.getMaxSuspensionTravelCm());
+            newWheel.setSuspensionStiffness(wheel.getSuspensionStiffness());
+            newWheel.setWheelsDampingCompression(wheel.getWheelsDampingCompression());
+            newWheel.setWheelsDampingRelaxation(wheel.getWheelsDampingRelaxation());
+            newWheel.setMaxSuspensionForce(wheel.getMaxSuspensionForce());
+
+            //TODO: bad way finding children!
+            if (spatial instanceof Node) {
+                Node node = (Node) spatial;
+                Spatial wheelSpat = node.getChild(wheel.getWheelSpatial().getName());
+                if (wheelSpat != null) {
+                    newWheel.setWheelSpatial(wheelSpat);
+                }
+            }
+        }
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+
+        control.setSpatial(spatial);
+        return control;
+    }
+
+    public void setSpatial(Spatial spatial) {
+        if (getUserObject() == null || getUserObject() == this.spatial) {
+            setUserObject(spatial);
+        }
+        this.spatial = spatial;
+        if (spatial == null) {
+            if (getUserObject() == spatial) {
+                setUserObject(null);
+            }
+            this.spatial = null;
+            this.collisionShape = null;
+            return;
+        }
+        setPhysicsLocation(getSpatialTranslation());
+        setPhysicsRotation(getSpatialRotation());
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        if (space != null) {
+            if (enabled && !added) {
+                if(spatial!=null){
+                    setPhysicsLocation(getSpatialTranslation());
+                    setPhysicsRotation(getSpatialRotation());
+                }
+                space.addCollisionObject(this);
+                added = true;
+            } else if (!enabled && added) {
+                space.removeCollisionObject(this);
+                added = false;
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void update(float tpf) {
+        if (enabled && spatial != null) {
+            if (getMotionState().applyTransform(spatial)) {
+                spatial.getWorldTransform();
+                applyWheelTransforms();
+            }
+        } else if (enabled) {
+            applyWheelTransforms();
+        }
+    }
+
+    @Override
+    protected Spatial getDebugShape() {
+        return super.getDebugShape();
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (debugShape == null) {
+                attachDebugShape(space.getDebugManager());
+            }
+            Node debugNode = (Node) debugShape;
+            debugShape.setLocalTranslation(spatial.getWorldTranslation());
+            debugShape.setLocalRotation(spatial.getWorldRotation());
+            int i = 0;
+            for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+                VehicleWheel physicsVehicleWheel = it.next();
+                Vector3f location = physicsVehicleWheel.getLocation().clone();
+                Vector3f direction = physicsVehicleWheel.getDirection().clone();
+                Vector3f axle = physicsVehicleWheel.getAxle().clone();
+                float restLength = physicsVehicleWheel.getRestLength();
+                float radius = physicsVehicleWheel.getRadius();
+
+                Geometry locGeom = (Geometry) debugNode.getChild("WheelLocationDebugShape" + i);
+                Geometry dirGeom = (Geometry) debugNode.getChild("WheelDirectionDebugShape" + i);
+                Geometry axleGeom = (Geometry) debugNode.getChild("WheelAxleDebugShape" + i);
+                Geometry wheelGeom = (Geometry) debugNode.getChild("WheelRadiusDebugShape" + i);
+
+                Arrow locArrow = (Arrow) locGeom.getMesh();
+                locArrow.setArrowExtent(location);
+                Arrow axleArrow = (Arrow) axleGeom.getMesh();
+                axleArrow.setArrowExtent(axle.normalizeLocal().multLocal(0.3f));
+                Arrow wheelArrow = (Arrow) wheelGeom.getMesh();
+                wheelArrow.setArrowExtent(direction.normalizeLocal().multLocal(radius));
+                Arrow dirArrow = (Arrow) dirGeom.getMesh();
+                dirArrow.setArrowExtent(direction.normalizeLocal().multLocal(restLength));
+
+                dirGeom.setLocalTranslation(location);
+                axleGeom.setLocalTranslation(location.addLocal(direction));
+                wheelGeom.setLocalTranslation(location);
+                i++;
+            }
+            debugShape.updateLogicalState(0);
+            debugShape.updateGeometricState();
+            rm.renderScene(debugShape, vp);
+        }
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        createVehicle(space);
+        if (space == null) {
+            if (this.space != null) {
+                this.space.removeCollisionObject(this);
+                added = false;
+            }
+        } else {
+            if(this.space==space) return;
+            space.addCollisionObject(this);
+            added = true;
+        }
+        this.space = space;
+    }
+
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(motionState.isApplyPhysicsLocal(), "applyLocalPhysics", false);
+        oc.write(spatial, "spatial", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+        motionState.setApplyPhysicsLocal(ic.readBoolean("applyLocalPhysics", false));
+        setUserObject(spatial);
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java
new file mode 100644
index 0000000..06eeab0
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java
@@ -0,0 +1,99 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control.ragdoll;
+
+import com.jme3.math.FastMath;
+
+/**
+ *
+ * @author Nehon
+ */
+public class HumanoidRagdollPreset extends RagdollPreset {
+
+    @Override
+    protected void initBoneMap() {
+        boneMap.put("head", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI));
+
+        boneMap.put("torso", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, 0, 0, FastMath.QUARTER_PI, -FastMath.QUARTER_PI));
+
+        boneMap.put("upperleg", new JointPreset(FastMath.PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI/2, -FastMath.QUARTER_PI/2, FastMath.QUARTER_PI, -FastMath.QUARTER_PI));
+
+        boneMap.put("lowerleg", new JointPreset(0, -FastMath.PI, 0, 0, 0, 0));
+
+        boneMap.put("foot", new JointPreset(0, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI));
+
+        boneMap.put("upperarm", new JointPreset(FastMath.HALF_PI, -FastMath.QUARTER_PI, 0, 0, FastMath.HALF_PI, -FastMath.QUARTER_PI));
+
+        boneMap.put("lowerarm", new JointPreset(FastMath.HALF_PI, 0, 0, 0, 0, 0));
+
+        boneMap.put("hand", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI));
+
+    }
+
+    @Override
+    protected void initLexicon() {
+        LexiconEntry entry = new LexiconEntry();
+        entry.addSynonym("head", 100);        
+        lexicon.put("head", entry);
+
+        entry = new LexiconEntry();
+        entry.addSynonym("torso", 100);
+        entry.addSynonym("chest", 100);
+        entry.addSynonym("spine", 45);
+        entry.addSynonym("high", 25);
+        lexicon.put("torso", entry);
+
+        entry = new LexiconEntry();
+        entry.addSynonym("upperleg", 100);
+        entry.addSynonym("thigh", 100);
+        entry.addSynonym("hip", 75);
+        entry.addSynonym("leg", 40);
+        entry.addSynonym("high", 10);
+        entry.addSynonym("up", 15);
+        entry.addSynonym("upper", 15);
+        lexicon.put("upperleg", entry);
+
+        entry = new LexiconEntry();
+        entry.addSynonym("lowerleg", 100);
+        entry.addSynonym("calf", 100);
+        entry.addSynonym("knee", 75);
+        entry.addSynonym("leg", 50);
+        entry.addSynonym("low", 10);
+        entry.addSynonym("lower", 10);
+        lexicon.put("lowerleg", entry);
+        
+        entry = new LexiconEntry();
+        entry.addSynonym("foot", 100);
+        entry.addSynonym("ankle", 75);   
+        lexicon.put("foot", entry);
+        
+        
+        entry = new LexiconEntry();
+        entry.addSynonym("upperarm", 100);
+        entry.addSynonym("humerus", 100); 
+        entry.addSynonym("shoulder", 50);
+        entry.addSynonym("arm", 40);
+        entry.addSynonym("high", 10);
+        entry.addSynonym("up", 15);
+        entry.addSynonym("upper", 15);
+        lexicon.put("upperarm", entry);
+
+        entry = new LexiconEntry();
+        entry.addSynonym("lowerarm", 100);
+        entry.addSynonym("ulna", 100);
+        entry.addSynonym("elbow", 75);
+        entry.addSynonym("arm", 50);
+        entry.addSynonym("low", 10);
+        entry.addSynonym("lower", 10);
+        lexicon.put("lowerarm", entry);
+        
+        entry = new LexiconEntry();
+        entry.addSynonym("hand", 100);
+        entry.addSynonym("fist", 100);   
+        entry.addSynonym("wrist", 75);           
+        lexicon.put("hand", entry);
+
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollPreset.java b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollPreset.java
new file mode 100644
index 0000000..51bf4ba
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollPreset.java
@@ -0,0 +1,106 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control.ragdoll;
+
+import com.jme3.bullet.joints.SixDofJoint;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ */
+public abstract class RagdollPreset {
+
+    protected static final Logger logger = Logger.getLogger(RagdollPreset.class.getName());
+    protected Map<String, JointPreset> boneMap = new HashMap<String, JointPreset>();
+    protected Map<String, LexiconEntry> lexicon = new HashMap<String, LexiconEntry>();
+
+    protected abstract void initBoneMap();
+
+    protected abstract void initLexicon();
+
+    public void setupJointForBone(String boneName, SixDofJoint joint) {
+
+        if (boneMap.isEmpty()) {
+            initBoneMap();
+        }
+        if (lexicon.isEmpty()) {
+            initLexicon();
+        }
+        String resultName = "";
+        int resultScore = 0;
+
+        for (String key : lexicon.keySet()) {
+        
+            int score = lexicon.get(key).getScore(boneName);        
+            if (score > resultScore) {
+                resultScore = score;
+                resultName = key;
+            }
+            
+        }
+        
+        JointPreset preset = boneMap.get(resultName);
+
+        if (preset != null && resultScore >= 50) {
+            logger.log(Level.INFO, "Found matching joint for bone {0} : {1} with score {2}", new Object[]{boneName, resultName, resultScore});
+            preset.setupJoint(joint);
+        } else {
+            logger.log(Level.INFO, "No joint match found for bone {0}", boneName);
+            if (resultScore > 0) {
+                logger.log(Level.INFO, "Best match found is {0} with score {1}", new Object[]{resultName, resultScore});
+            }
+            new JointPreset().setupJoint(joint);
+        }
+
+    }
+
+    protected class JointPreset {
+
+        private float maxX, minX, maxY, minY, maxZ, minZ;
+
+        public JointPreset() {
+        }
+
+        public JointPreset(float maxX, float minX, float maxY, float minY, float maxZ, float minZ) {
+            this.maxX = maxX;
+            this.minX = minX;
+            this.maxY = maxY;
+            this.minY = minY;
+            this.maxZ = maxZ;
+            this.minZ = minZ;
+        }
+
+        public void setupJoint(SixDofJoint joint) {
+            joint.getRotationalLimitMotor(0).setHiLimit(maxX);
+            joint.getRotationalLimitMotor(0).setLoLimit(minX);
+            joint.getRotationalLimitMotor(1).setHiLimit(maxY);
+            joint.getRotationalLimitMotor(1).setLoLimit(minY);
+            joint.getRotationalLimitMotor(2).setHiLimit(maxZ);
+            joint.getRotationalLimitMotor(2).setLoLimit(minZ);
+        }
+    }
+
+    protected class LexiconEntry extends HashMap<String, Integer> {
+
+        public void addSynonym(String word, int score) {
+            put(word.toLowerCase(), score);
+        }
+
+        public int getScore(String word) {
+            int score = 0;
+            String searchWord = word.toLowerCase();
+            for (String key : this.keySet()) {
+                if (searchWord.indexOf(key) >= 0) {
+                    score += get(key);
+                }
+            }
+            return score;
+        }
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollUtils.java b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollUtils.java
new file mode 100644
index 0000000..02bab98
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/control/ragdoll/RagdollUtils.java
@@ -0,0 +1,268 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.control.ragdoll;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.bullet.collision.shapes.HullCollisionShape;
+import com.jme3.bullet.joints.SixDofJoint;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.*;
+
+/**
+ *
+ * @author Nehon
+ */
+public class RagdollUtils {
+
+    public static void setJointLimit(SixDofJoint joint, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) {
+
+        joint.getRotationalLimitMotor(0).setHiLimit(maxX);
+        joint.getRotationalLimitMotor(0).setLoLimit(minX);
+        joint.getRotationalLimitMotor(1).setHiLimit(maxY);
+        joint.getRotationalLimitMotor(1).setLoLimit(minY);
+        joint.getRotationalLimitMotor(2).setHiLimit(maxZ);
+        joint.getRotationalLimitMotor(2).setLoLimit(minZ);
+    }
+
+    public static Map<Integer, List<Float>> buildPointMap(Spatial model) {
+
+
+        Map<Integer, List<Float>> map = new HashMap<Integer, List<Float>>();
+        if (model instanceof Geometry) {
+            Geometry g = (Geometry) model;
+            buildPointMapForMesh(g.getMesh(), map);
+        } else if (model instanceof Node) {
+            Node node = (Node) model;
+            for (Spatial s : node.getChildren()) {
+                if (s instanceof Geometry) {
+                    Geometry g = (Geometry) s;
+                    buildPointMapForMesh(g.getMesh(), map);
+                }
+            }
+        }
+        return map;
+    }
+
+    private static Map<Integer, List<Float>> buildPointMapForMesh(Mesh mesh, Map<Integer, List<Float>> map) {
+
+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
+        ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        vertices.rewind();
+        boneIndices.rewind();
+        boneWeight.rewind();
+
+        int vertexComponents = mesh.getVertexCount() * 3;
+        int k, start, index;
+        float maxWeight = 0;
+
+        for (int i = 0; i < vertexComponents; i += 3) {
+
+
+            start = i / 3 * 4;
+            index = 0;
+            maxWeight = -1;
+            for (k = start; k < start + 4; k++) {
+                float weight = boneWeight.get(k);
+                if (weight > maxWeight) {
+                    maxWeight = weight;
+                    index = boneIndices.get(k);
+                }
+            }
+            List<Float> points = map.get(index);
+            if (points == null) {
+                points = new ArrayList<Float>();
+                map.put(index, points);
+            }
+            points.add(vertices.get(i));
+            points.add(vertices.get(i + 1));
+            points.add(vertices.get(i + 2));
+        }
+        return map;
+    }
+
+    /**
+     * Create a hull collision shape from linked vertices to this bone.
+     * Vertices have to be previoulsly gathered in a map using buildPointMap method
+     * @param link
+     * @param model
+     * @return 
+     */
+    public static HullCollisionShape makeShapeFromPointMap(Map<Integer, List<Float>> pointsMap, List<Integer> boneIndices, Vector3f initialScale, Vector3f initialPosition) {
+
+        ArrayList<Float> points = new ArrayList<Float>();
+        for (Integer index : boneIndices) {
+            List<Float> l = pointsMap.get(index);
+            if (l != null) {
+
+                for (int i = 0; i < l.size(); i += 3) {
+                    Vector3f pos = new Vector3f();
+                    pos.x = l.get(i);
+                    pos.y = l.get(i + 1);
+                    pos.z = l.get(i + 2);
+                    pos.subtractLocal(initialPosition).multLocal(initialScale);
+                    points.add(pos.x);
+                    points.add(pos.y);
+                    points.add(pos.z);
+                }
+            }
+        }
+
+        float[] p = new float[points.size()];
+        for (int i = 0; i < points.size(); i++) {
+            p[i] = points.get(i);
+        }
+
+
+        return new HullCollisionShape(p);
+    }
+
+    //retruns the list of bone indices of the given bone and its child(if they are not in the boneList)
+    public static List<Integer> getBoneIndices(Bone bone, Skeleton skeleton, Set<String> boneList) {
+        List<Integer> list = new LinkedList<Integer>();
+        if (boneList.isEmpty()) {
+            list.add(skeleton.getBoneIndex(bone));
+        } else {
+            list.add(skeleton.getBoneIndex(bone));
+            for (Bone chilBone : bone.getChildren()) {
+                if (!boneList.contains(chilBone.getName())) {
+                    list.addAll(getBoneIndices(chilBone, skeleton, boneList));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Create a hull collision shape from linked vertices to this bone.
+     * 
+     * @param link
+     * @param model
+     * @return 
+     */
+    public static HullCollisionShape makeShapeFromVerticeWeights(Spatial model, List<Integer> boneIndices, Vector3f initialScale, Vector3f initialPosition, float weightThreshold) {
+
+        ArrayList<Float> points = new ArrayList<Float>();
+        if (model instanceof Geometry) {
+            Geometry g = (Geometry) model;
+            for (Integer index : boneIndices) {
+                points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold));
+            }
+        } else if (model instanceof Node) {
+            Node node = (Node) model;
+            for (Spatial s : node.getChildren()) {
+                if (s instanceof Geometry) {
+                    Geometry g = (Geometry) s;
+                    for (Integer index : boneIndices) {
+                        points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold));
+                    }
+
+                }
+            }
+        }
+        float[] p = new float[points.size()];
+        for (int i = 0; i < points.size(); i++) {
+            p[i] = points.get(i);
+        }
+
+
+        return new HullCollisionShape(p);
+    }
+
+    /**
+     * returns a list of points for the given bone
+     * @param mesh
+     * @param boneIndex
+     * @param offset
+     * @param link
+     * @return 
+     */
+    private static List<Float> getPoints(Mesh mesh, int boneIndex, Vector3f initialScale, Vector3f offset, float weightThreshold) {
+
+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
+        ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        vertices.rewind();
+        boneIndices.rewind();
+        boneWeight.rewind();
+
+        ArrayList<Float> results = new ArrayList<Float>();
+
+        int vertexComponents = mesh.getVertexCount() * 3;
+
+        for (int i = 0; i < vertexComponents; i += 3) {
+            int k;
+            boolean add = false;
+            int start = i / 3 * 4;
+            for (k = start; k < start + 4; k++) {
+                if (boneIndices.get(k) == boneIndex && boneWeight.get(k) >= weightThreshold) {
+                    add = true;
+                    break;
+                }
+            }
+            if (add) {
+
+                Vector3f pos = new Vector3f();
+                pos.x = vertices.get(i);
+                pos.y = vertices.get(i + 1);
+                pos.z = vertices.get(i + 2);
+                pos.subtractLocal(offset).multLocal(initialScale);
+                results.add(pos.x);
+                results.add(pos.y);
+                results.add(pos.z);
+
+            }
+        }
+
+        return results;
+    }
+
+    /**
+     * Updates a bone position and rotation.
+     * if the child bones are not in the bone list this means, they are not associated with a physic shape.
+     * So they have to be updated
+     * @param bone the bone
+     * @param pos the position
+     * @param rot the rotation
+     */
+    public static void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean restoreBoneControl, Set<String> boneList) {
+        //we ensure that we have the control
+        if (restoreBoneControl) {
+            bone.setUserControl(true);
+        }
+        //we set te user transforms of the bone
+        bone.setUserTransformsWorld(pos, rot);
+        for (Bone childBone : bone.getChildren()) {
+            //each child bone that is not in the list is updated
+            if (!boneList.contains(childBone.getName())) {
+                Transform t = childBone.getCombinedTransform(pos, rot);
+                setTransform(childBone, t.getTranslation(), t.getRotation(), restoreBoneControl, boneList);
+            }
+        }
+        //we give back the control to the keyframed animation
+        if (restoreBoneControl) {
+            bone.setUserControl(false);
+        }
+    }
+
+    public static void setUserControl(Bone bone, boolean bool) {
+        bone.setUserControl(bool);
+        for (Bone child : bone.getChildren()) {
+            setUserControl(child, bool);
+        }
+    }
+}
diff --git a/engine/src/bullet-common/com/jme3/bullet/util/CollisionShapeFactory.java b/engine/src/bullet-common/com/jme3/bullet/util/CollisionShapeFactory.java
new file mode 100644
index 0000000..b0031f2
--- /dev/null
+++ b/engine/src/bullet-common/com/jme3/bullet/util/CollisionShapeFactory.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.util;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bullet.collision.shapes.*;
+import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.*;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ *
+ * @author normenhansen, tim8dev
+ */
+public class CollisionShapeFactory {
+
+    /**
+     * returns the correct transform for a collisionshape in relation
+     * to the ancestor for which the collisionshape is generated
+     * @param spat
+     * @param parent
+     * @return
+     */
+    private static Transform getTransform(Spatial spat, Spatial parent) {
+        Transform shapeTransform = new Transform();
+        Spatial parentNode = spat.getParent() != null ? spat.getParent() : spat;
+        Spatial currentSpatial = spat;
+        //if we have parents combine their transforms
+        while (parentNode != null) {
+            if (parent == currentSpatial) {
+                //real parent -> only apply scale, not transform
+                Transform trans = new Transform();
+                trans.setScale(currentSpatial.getLocalScale());
+                shapeTransform.combineWithParent(trans);
+                parentNode = null;
+            } else {
+                shapeTransform.combineWithParent(currentSpatial.getLocalTransform());
+                parentNode = currentSpatial.getParent();
+                currentSpatial = parentNode;
+            }
+        }
+        return shapeTransform;
+    }
+
+    private static CompoundCollisionShape createCompoundShape(Node realRootNode,
+            Node rootNode, CompoundCollisionShape shape, boolean meshAccurate, boolean dynamic) {
+        for (Spatial spatial : rootNode.getChildren()) {
+            if (spatial instanceof TerrainQuad) {
+                Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE);
+                if (bool != null && bool.booleanValue()) {
+                    continue; // go to the next child in the loop
+                }
+                TerrainQuad terrain = (TerrainQuad) spatial;
+                Transform trans = getTransform(spatial, realRootNode);
+                shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), trans.getScale()),
+                        trans.getTranslation(),
+                        trans.getRotation().toRotationMatrix());
+            } else if (spatial instanceof Node) {
+                createCompoundShape(realRootNode, (Node) spatial, shape, meshAccurate, dynamic);
+            } else if (spatial instanceof TerrainPatch) {
+                Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE);
+                if (bool != null && bool.booleanValue()) {
+                    continue; // go to the next child in the loop
+                }
+                TerrainPatch terrain = (TerrainPatch) spatial;
+                Transform trans = getTransform(spatial, realRootNode);
+                shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()),
+                        trans.getTranslation(),
+                        trans.getRotation().toRotationMatrix());
+            } else if (spatial instanceof Geometry) {
+                Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE);
+                if (bool != null && bool.booleanValue()) {
+                    continue; // go to the next child in the loop
+                }
+
+                if (meshAccurate) {
+                    CollisionShape childShape = dynamic
+                            ? createSingleDynamicMeshShape((Geometry) spatial, realRootNode)
+                            : createSingleMeshShape((Geometry) spatial, realRootNode);
+                    if (childShape != null) {
+                        Transform trans = getTransform(spatial, realRootNode);
+                        shape.addChildShape(childShape,
+                                trans.getTranslation(),
+                                trans.getRotation().toRotationMatrix());
+                    }
+                } else {
+                    Transform trans = getTransform(spatial, realRootNode);
+                    shape.addChildShape(createSingleBoxShape(spatial, realRootNode),
+                            trans.getTranslation(),
+                            trans.getRotation().toRotationMatrix());
+                }
+            }
+        }
+        return shape;
+    }
+
+    private static CompoundCollisionShape createCompoundShape(
+            Node rootNode, CompoundCollisionShape shape, boolean meshAccurate) {
+        return createCompoundShape(rootNode, rootNode, shape, meshAccurate, false);
+    }
+
+    /**
+     * This type of collision shape is mesh-accurate and meant for immovable "world objects".
+     * Examples include terrain, houses or whole shooter levels.<br>
+     * Objects with "mesh" type collision shape will not collide with each other.
+     */
+    private static CompoundCollisionShape createMeshCompoundShape(Node rootNode) {
+        return createCompoundShape(rootNode, new CompoundCollisionShape(), true);
+    }
+
+    /**
+     * This type of collision shape creates a CompoundShape made out of boxes that
+     * are based on the bounds of the Geometries  in the tree.
+     * @param rootNode
+     * @return
+     */
+    private static CompoundCollisionShape createBoxCompoundShape(Node rootNode) {
+        return createCompoundShape(rootNode, new CompoundCollisionShape(), false);
+    }
+
+    /**
+     * This type of collision shape is mesh-accurate and meant for immovable "world objects".
+     * Examples include terrain, houses or whole shooter levels.<br/>
+     * Objects with "mesh" type collision shape will not collide with each other.<br/>
+     * Creates a HeightfieldCollisionShape if the supplied spatial is a TerrainQuad.
+     * @return A MeshCollisionShape or a CompoundCollisionShape with MeshCollisionShapes as children if the supplied spatial is a Node. A HeightieldCollisionShape if a TerrainQuad was supplied.
+     */
+    public static CollisionShape createMeshShape(Spatial spatial) {
+        if (spatial instanceof TerrainQuad) {
+            TerrainQuad terrain = (TerrainQuad) spatial;
+            return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale());
+        } else if (spatial instanceof TerrainPatch) {
+            TerrainPatch terrain = (TerrainPatch) spatial;
+            return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale());
+        } else if (spatial instanceof Geometry) {
+            return createSingleMeshShape((Geometry) spatial, spatial);
+        } else if (spatial instanceof Node) {
+            return createMeshCompoundShape((Node) spatial);
+        } else {
+            throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!");
+        }
+    }
+
+    /**
+     * This method creates a hull shape for the given Spatial.<br>
+     * If you want to have mesh-accurate dynamic shapes (CPU intense!!!) use GImpact shapes, its probably best to do so with a low-poly version of your model.
+     * @return A HullCollisionShape or a CompoundCollisionShape with HullCollisionShapes as children if the supplied spatial is a Node.
+     */
+    public static CollisionShape createDynamicMeshShape(Spatial spatial) {
+        if (spatial instanceof Geometry) {
+            return createSingleDynamicMeshShape((Geometry) spatial, spatial);
+        } else if (spatial instanceof Node) {
+            return createCompoundShape((Node) spatial, (Node) spatial, new CompoundCollisionShape(), true, true);
+        } else {
+            throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!");
+        }
+
+    }
+
+    public static CollisionShape createBoxShape(Spatial spatial) {
+        if (spatial instanceof Geometry) {
+            return createSingleBoxShape((Geometry) spatial, spatial);
+        } else if (spatial instanceof Node) {
+            return createBoxCompoundShape((Node) spatial);
+        } else {
+            throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!");
+        }
+    }
+
+    /**
+     * This type of collision shape is mesh-accurate and meant for immovable "world objects".
+     * Examples include terrain, houses or whole shooter levels.<br>
+     * Objects with "mesh" type collision shape will not collide with each other.
+     */
+    private static MeshCollisionShape createSingleMeshShape(Geometry geom, Spatial parent) {
+        Mesh mesh = geom.getMesh();
+        Transform trans = getTransform(geom, parent);
+        if (mesh != null) {
+            MeshCollisionShape mColl = new MeshCollisionShape(mesh);
+            mColl.setScale(trans.getScale());
+            return mColl;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Uses the bounding box of the supplied spatial to create a BoxCollisionShape
+     * @param spatial
+     * @return BoxCollisionShape with the size of the spatials BoundingBox
+     */
+    private static BoxCollisionShape createSingleBoxShape(Spatial spatial, Spatial parent) {
+        spatial.setModelBound(new BoundingBox());
+        //TODO: using world bound here instead of "local world" bound...
+        BoxCollisionShape shape = new BoxCollisionShape(
+                ((BoundingBox) spatial.getWorldBound()).getExtent(new Vector3f()));
+        return shape;
+    }
+
+    /**
+     * This method creates a hull collision shape for the given mesh.<br>
+     */
+    private static HullCollisionShape createSingleDynamicMeshShape(Geometry geom, Spatial parent) {
+        Mesh mesh = geom.getMesh();
+        Transform trans = getTransform(geom, parent);
+        if (mesh != null) {
+            HullCollisionShape dynamicShape = new HullCollisionShape(mesh);
+            dynamicShape.setScale(trans.getScale());
+            return dynamicShape;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * This method moves each child shape of a compound shape by the given vector
+     * @param vector
+     */
+    public static void shiftCompoundShapeContents(CompoundCollisionShape compoundShape, Vector3f vector) {
+        for (Iterator<ChildCollisionShape> it = new LinkedList(compoundShape.getChildren()).iterator(); it.hasNext();) {
+            ChildCollisionShape childCollisionShape = it.next();
+            CollisionShape child = childCollisionShape.shape;
+            Vector3f location = childCollisionShape.location;
+            Matrix3f rotation = childCollisionShape.rotation;
+            compoundShape.removeChildShape(child);
+            compoundShape.addChildShape(child, location.add(vector), rotation);
+        }
+    }
+}
diff --git a/engine/src/bullet-native/android/Android.mk b/engine/src/bullet-native/android/Android.mk
new file mode 100644
index 0000000..3db0351
--- /dev/null
+++ b/engine/src/bullet-native/android/Android.mk
@@ -0,0 +1,259 @@
+# /*
+# Bullet Continuous Collision Detection and Physics Library for Android NDK
+# Copyright (c) 2006-2009 Noritsuna Imamura  <a href="http://www.siprop.org/" rel="nofollow">http://www.siprop.org/</a>
+#
+# This software is provided 'as-is', without any express or implied warranty.
+# In no event will the authors be held liable for any damages arising from the use of this software.
+# Permission is granted to anyone to use this software for any purpose,
+# including commercial applications, and to alter it and redistribute it freely,
+# subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+# 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+# 3. This notice may not be removed or altered from any source distribution.
+# */
+LOCAL_PATH:= $(call my-dir)
+JME3_PATH:=
+BULLET_PATH:=
+ 
+include $(CLEAR_VARS)
+ 
+LOCAL_MODULE    := bulletjme
+LOCAL_C_INCLUDES := $(BULLET_PATH)/\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver\
+    $(BULLET_PATH)/BulletDynamics/Dynamics\
+    $(BULLET_PATH)/BulletDynamics/Vehicle\
+    $(BULLET_PATH)/LinearMath\
+    $(BULLET_PATH)/BulletCollision\
+    $(BULLET_PATH)/BulletDynamics\
+    $(BULLET_PATH)/BulletMultiThreaded\
+    $(BULLET_PATH)/BulletSoftBody\
+    $(BULLET_PATH)/ibmsdk\
+    $(BULLET_PATH)/LinearMath\
+    $(BULLET_PATH)/MiniCL\
+    $(BULLET_PATH)/vectormath\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes\
+    $(BULLET_PATH)/BulletCollision/Gimpact\
+    $(BULLET_PATH)/BulletCollision/ibmsdk\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision\
+    $(BULLET_PATH)/BulletDynamics/Character\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver\
+    $(BULLET_PATH)/BulletDynamics/Dynamics\
+    $(BULLET_PATH)/BulletDynamics/ibmsdk\
+    $(BULLET_PATH)/BulletDynamics/Vehicle\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers\
+    $(BULLET_PATH)/BulletMultiThreaded/out\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTask\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/CPU\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11/HLSL\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/AMD\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/Apple\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/MiniCL\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/NVidia\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC10\
+    $(BULLET_PATH)/LinearMath/ibmsdk\
+    $(BULLET_PATH)/MiniCL/MiniCLTask\
+    $(BULLET_PATH)/vectormath/scalar\
+    $(BULLET_PATH)/vectormath/sse
+ 
+LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%)
+LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl -lm -llog
+ 
+LOCAL_SRC_FILES := $(JME3_PATH)/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_PhysicsCollisionObject.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_CollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_ConeJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_HingeJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_PhysicsJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_Point2PointJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_SixDofJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_SixDofSpringJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_joints_SliderJoint.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_PhysicsCharacter.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_PhysicsGhostObject.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_PhysicsRigidBody.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_PhysicsVehicle.cpp\
+    $(JME3_PATH)/com_jme3_bullet_objects_VehicleWheel.cpp\
+    $(JME3_PATH)/com_jme3_bullet_PhysicsSpace.cpp\
+    $(JME3_PATH)/com_jme3_bullet_util_DebugShapeFactory.cpp\
+    $(JME3_PATH)/com_jme3_bullet_util_NativeMeshUtil.cpp\
+    $(JME3_PATH)/jmeBulletUtil.cpp\
+    $(JME3_PATH)/jmeClasses.cpp\
+    $(JME3_PATH)/jmeMotionState.cpp\
+    $(JME3_PATH)/jmePhysicsSpace.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btAxisSweep3.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDbvt.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDbvtBroadphase.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDispatcher.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btMultiSapBroadphase.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btQuantizedBvh.cpp\
+    $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btActivatingCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBox2dBox2dCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBoxBoxCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBoxBoxDetector.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionDispatcher.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionObject.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionWorld.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCompoundCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvex2dConvex2dAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexConvexAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexPlaneCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btEmptyCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btGhostObject.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btInternalEdgeUtility.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btManifoldResult.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSimulationIslandManager.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereBoxCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereSphereCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereTriangleCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/btUnionFind.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionDispatch/SphereTriangleDetector.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btBox2dShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btBoxShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btBvhTriangleMeshShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btCapsuleShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btCollisionShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btCompoundShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConcaveShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConeShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvex2dShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexHullShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexInternalShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexPointCloudShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexPolyhedron.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexTriangleMeshShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btCylinderShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btEmptyShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btHeightfieldTerrainShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btMinkowskiSumShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btMultimaterialTriangleMeshShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btMultiSphereShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btOptimizedBvh.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btPolyhedralConvexShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btShapeHull.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btSphereShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btStaticPlaneShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btStridingMeshInterface.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTetrahedronShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleBuffer.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleCallback.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleIndexVertexArray.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleIndexVertexMaterialArray.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleMesh.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleMeshShape.cpp\
+    $(BULLET_PATH)/BulletCollision/CollisionShapes/btUniformScalingShape.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btContactProcessing.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btGenericPoolAllocator.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactBvh.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactQuantizedBvh.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactShape.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/btTriangleShapeEx.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/gim_box_set.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/gim_contact.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/gim_memory.cpp\
+    $(BULLET_PATH)/BulletCollision/Gimpact/gim_tri_collision.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btContinuousConvexCollision.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btConvexCast.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkEpa2.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkPairDetector.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btMinkowskiPenetrationDepthSolver.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btPersistentManifold.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btPolyhedralContactClipping.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btRaycastCallback.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.cpp\
+    $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.cpp\
+    $(BULLET_PATH)/BulletDynamics/Character/btKinematicCharacterController.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btConeTwistConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btContactConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btGeneric6DofConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btGeneric6DofSpringConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btHinge2Constraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btHingeConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btPoint2PointConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSliderConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSolve2LinearConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btTypedConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btUniversalConstraint.cpp\
+    $(BULLET_PATH)/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp\
+    $(BULLET_PATH)/BulletDynamics/Dynamics/btRigidBody.cpp\
+    $(BULLET_PATH)/BulletDynamics/Dynamics/btSimpleDynamicsWorld.cpp\
+    $(BULLET_PATH)/BulletDynamics/Dynamics/Bullet-C-API.cpp\
+    $(BULLET_PATH)/BulletDynamics/Vehicle/btRaycastVehicle.cpp\
+    $(BULLET_PATH)/BulletDynamics/Vehicle/btWheelInfo.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/btGpu3DGridBroadphase.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/btParallelConstraintSolver.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/btThreadSupportInterface.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/PosixThreadSupport.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SequentialThreadSupport.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuCollisionObjectWrapper.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuCollisionTaskProcess.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuContactManifoldCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuFakeDma.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuGatheringCollisionDispatcher.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuLibspe2Support.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTaskProcess.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/Win32ThreadSupport.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/CPU/btSoftBodySolver_CPU.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/MiniCL/MiniCLTaskWrap.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/boxBoxDistance.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuCollisionShapes.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuContactResult.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuGatheringCollisionTask.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuMinkowskiPenetrationDepthSolver.cpp\
+    $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTask/SpuSampleTask.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btDefaultSoftBodySolver.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftBody.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftBodyConcaveCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftBodyHelpers.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftRigidCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftRigidDynamicsWorld.cpp\
+    $(BULLET_PATH)/BulletSoftBody/btSoftSoftCollisionAlgorithm.cpp\
+    $(BULLET_PATH)/LinearMath/btAlignedAllocator.cpp\
+    $(BULLET_PATH)/LinearMath/btConvexHull.cpp\
+    $(BULLET_PATH)/LinearMath/btConvexHullComputer.cpp\
+    $(BULLET_PATH)/LinearMath/btGeometryUtil.cpp\
+    $(BULLET_PATH)/LinearMath/btQuickprof.cpp\
+    $(BULLET_PATH)/LinearMath/btSerializer.cpp\
+    $(BULLET_PATH)/MiniCL/MiniCL.cpp\
+    $(BULLET_PATH)/MiniCL/MiniCLTaskScheduler.cpp\
+    $(BULLET_PATH)/MiniCL/MiniCLTask/MiniCLTask.cpp
+ 
+include $(BUILD_SHARED_LIBRARY)
diff --git a/engine/src/bullet-native/android/Application.mk b/engine/src/bullet-native/android/Application.mk
new file mode 100644
index 0000000..9e8be35
--- /dev/null
+++ b/engine/src/bullet-native/android/Application.mk
@@ -0,0 +1,2 @@
+APP_MODULES      := bulletjme
+APP_ABI          := all
\ No newline at end of file
diff --git a/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.cpp b/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.cpp
new file mode 100644
index 0000000..8116f90
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.cpp
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include "com_jme3_bullet_PhysicsSpace.h"
+#include "jmePhysicsSpace.h"
+#include "jmeBulletUtil.h"
+
+/**
+ * Author: Normen Hansen
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    createPhysicsSpace
+     * Signature: (FFFFFFI)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace
+    (JNIEnv * env, jobject object, jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphase, jboolean threading) {
+        jmeClasses::initJavaClasses(env);
+        jmePhysicsSpace* space = new jmePhysicsSpace(env, object);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space has not been created.");
+            return 0;
+        }
+        space->createPhysicsSpace(minX, minY, minZ, maxX, maxY, maxZ, broadphase, threading);
+        return reinterpret_cast<jlong>(space);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    stepSimulation
+     * Signature: (JFIF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation
+    (JNIEnv * env, jobject object, jlong spaceId, jfloat tpf, jint maxSteps, jfloat accuracy) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        space->stepSimulation(tpf, maxSteps, accuracy);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addCollisionObject
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = space;
+
+        space->getDynamicsWorld()->addCollisionObject(collisionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeCollisionObject
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->removeCollisionObject(collisionObject);
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = NULL;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addRigidBody
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody
+    (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btRigidBody* collisionObject = reinterpret_cast<btRigidBody*>(rigidBodyId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = space;
+        space->getDynamicsWorld()->addRigidBody(collisionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeRigidBody
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody
+    (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btRigidBody* collisionObject = reinterpret_cast<btRigidBody*>(rigidBodyId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = NULL;
+        space->getDynamicsWorld()->removeRigidBody(collisionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addCharacterObject
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = space;
+        space->getDynamicsWorld()->addCollisionObject(collisionObject,
+                btBroadphaseProxy::CharacterFilter,
+                btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter
+        );
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeCharacterObject
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        userPointer -> space = NULL;
+        space->getDynamicsWorld()->removeCollisionObject(collisionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addAction
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btActionInterface* actionObject = reinterpret_cast<btActionInterface*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (actionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The action object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->addAction(actionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeAction
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btActionInterface* actionObject = reinterpret_cast<btActionInterface*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (actionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The action object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->removeAction(actionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addVehicle
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btActionInterface* actionObject = reinterpret_cast<btActionInterface*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (actionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The vehicle object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->addVehicle(actionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeVehicle
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btActionInterface* actionObject = reinterpret_cast<btActionInterface*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (actionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The action object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->removeVehicle(actionObject);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    addConstraint
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btTypedConstraint* constraint = reinterpret_cast<btTypedConstraint*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (constraint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The constraint object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->addConstraint(constraint);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    removeConstraint
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint
+    (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        btTypedConstraint* constraint = reinterpret_cast<btTypedConstraint*>(objectId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        if (constraint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The constraint object does not exist.");
+            return;
+        }
+        space->getDynamicsWorld()->removeConstraint(constraint);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    setGravity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity
+    (JNIEnv * env, jobject object, jlong spaceId, jobject vector) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+        btVector3 gravity = btVector3();
+        jmeBulletUtil::convert(env, vector, &gravity);
+        space->getDynamicsWorld()->setGravity(gravity);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    initNativePhysics
+     * Signature: ()V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics
+    (JNIEnv * env, jclass clazz) {
+        jmeClasses::initJavaClasses(env);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_PhysicsSpace
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative
+    (JNIEnv * env, jobject object, jlong spaceId) {
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        if (space == NULL) {
+            return;
+        }
+        delete(space);
+    }
+    
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native
+    (JNIEnv * env, jobject object, jobject to, jobject from, jlong spaceId, jobject resultlist) {
+
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*> (spaceId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+
+        struct AllRayResultCallback : public btCollisionWorld::RayResultCallback {
+
+            AllRayResultCallback(const btVector3& rayFromWorld, const btVector3 & rayToWorld) : m_rayFromWorld(rayFromWorld), m_rayToWorld(rayToWorld) {
+            }
+            jobject resultlist;
+            JNIEnv* env;
+            btVector3 m_rayFromWorld; //used to calculate hitPointWorld from hitFraction
+            btVector3 m_rayToWorld;
+
+            btVector3 m_hitNormalWorld;
+            btVector3 m_hitPointWorld;
+
+            virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) {
+                if (normalInWorldSpace) {
+                    m_hitNormalWorld = rayResult.m_hitNormalLocal;
+                } else {
+                    m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis() * rayResult.m_hitNormalLocal;
+                }
+                m_hitPointWorld.setInterpolate3(m_rayFromWorld, m_rayToWorld, rayResult.m_hitFraction);
+
+                jmeBulletUtil::addResult(env, resultlist, m_hitNormalWorld, m_hitPointWorld, rayResult.m_hitFraction, rayResult.m_collisionObject);
+
+                return 1.f;
+            }
+        };
+
+        btVector3 native_to = btVector3();
+        jmeBulletUtil::convert(env, to, &native_to);
+
+        btVector3 native_from = btVector3();
+        jmeBulletUtil::convert(env, from, &native_from);
+
+        AllRayResultCallback resultCallback(native_from, native_to);
+        resultCallback.env = env;
+        resultCallback.resultlist = resultlist;
+        space->getDynamicsWorld()->rayTest(native_from, native_to, resultCallback);
+        return;
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.h b/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.h
new file mode 100644
index 0000000..3b14a75
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_PhysicsSpace.h
@@ -0,0 +1,165 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_PhysicsSpace */
+
+#ifndef _Included_com_jme3_bullet_PhysicsSpace
+#define _Included_com_jme3_bullet_PhysicsSpace
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_PhysicsSpace_AXIS_X
+#define com_jme3_bullet_PhysicsSpace_AXIS_X 0L
+#undef com_jme3_bullet_PhysicsSpace_AXIS_Y
+#define com_jme3_bullet_PhysicsSpace_AXIS_Y 1L
+#undef com_jme3_bullet_PhysicsSpace_AXIS_Z
+#define com_jme3_bullet_PhysicsSpace_AXIS_Z 2L
+/* Inaccessible static: pQueueTL */
+/* Inaccessible static: physicsSpaceTL */
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    createPhysicsSpace
+ * Signature: (FFFFFFIZ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace
+  (JNIEnv *, jobject, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jint, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    stepSimulation
+ * Signature: (JFIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation
+  (JNIEnv *, jobject, jlong, jfloat, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addCollisionObject
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeCollisionObject
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addRigidBody
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeRigidBody
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addCharacterObject
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeCharacterObject
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addAction
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeAction
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addVehicle
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeVehicle
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    addConstraint
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    removeConstraint
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    setGravity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    rayTest_native
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;JLjava/util/List;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native
+  (JNIEnv *, jobject, jobject, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    initNativePhysics
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     com_jme3_bullet_PhysicsSpace
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp
new file mode 100644
index 0000000..e9199d4
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp
@@ -0,0 +1,308 @@
+#include "jmeBulletUtil.h"
+#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h"
+#include "com_jme3_bullet_collision_PhysicsCollisionEvent.h"
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulse
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_appliedImpulse;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral1
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral1
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_appliedImpulseLateral1;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral2
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral2
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_appliedImpulseLateral2;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getCombinedFriction
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedFriction
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_combinedFriction;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getCombinedRestitution
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedRestitution
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_combinedRestitution;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getDistance1
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getDistance1
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_distance1;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getIndex0
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex0
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_index0;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getIndex1
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex1
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_index1;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLateralFrictionDir1
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir1
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir1) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir1, lateralFrictionDir1);
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLateralFrictionDir2
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir2
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir2) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir2, lateralFrictionDir2);
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    isLateralFrictionInitialized
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_isLateralFrictionInitialized
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_lateralFrictionInitialized;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLifeTime
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLifeTime
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_lifeTime;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLocalPointA
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointA
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointA) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_localPointA, localPointA);
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLocalPointB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointB
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointB) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_localPointB, localPointB);
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getNormalWorldOnB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getNormalWorldOnB
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject normalWorldOnB) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_normalWorldOnB, normalWorldOnB);
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPartId0
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId0
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_partId0;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPartId1
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId1
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return 0;
+    }
+    return mp -> m_partId1;
+}
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPositionWorldOnA
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnA
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnA) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_positionWorldOnA, positionWorldOnA);
+}
+
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPositionWorldOnB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnB
+  (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnB) {
+    btManifoldPoint* mp = reinterpret_cast<btManifoldPoint*>(manifoldPointObjectId);
+    if (mp == NULL) {
+        jclass newExc = env->FindClass("java/lang/NullPointerException");
+        env->ThrowNew(newExc, "The manifoldPoint does not exist.");
+        return;
+    }
+    jmeBulletUtil::convert(env, &mp -> m_positionWorldOnB, positionWorldOnB);
+}
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.h b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.h
new file mode 100644
index 0000000..2dd9dcf
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionEvent.h
@@ -0,0 +1,173 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_PhysicsCollisionEvent */
+
+#ifndef _Included_com_jme3_bullet_collision_PhysicsCollisionEvent
+#define _Included_com_jme3_bullet_collision_PhysicsCollisionEvent
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_collision_PhysicsCollisionEvent_serialVersionUID
+#define com_jme3_bullet_collision_PhysicsCollisionEvent_serialVersionUID 5516075349620653480LL
+#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_ADDED
+#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_ADDED 0L
+#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_PROCESSED
+#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_PROCESSED 1L
+#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_DESTROYED
+#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_DESTROYED 2L
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulse
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral1
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral1
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getAppliedImpulseLateral2
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral2
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getCombinedFriction
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedFriction
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getCombinedRestitution
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedRestitution
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getDistance1
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getDistance1
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getIndex0
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex0
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getIndex1
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex1
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLateralFrictionDir1
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir1
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLateralFrictionDir2
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir2
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    isLateralFrictionInitialized
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_isLateralFrictionInitialized
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLifeTime
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLifeTime
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLocalPointA
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointA
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getLocalPointB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointB
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getNormalWorldOnB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getNormalWorldOnB
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPartId0
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId0
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPartId1
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId1
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPositionWorldOnA
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnA
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionEvent
+ * Method:    getPositionWorldOnB
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnB
+  (JNIEnv *, jobject, jlong, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.cpp
new file mode 100644
index 0000000..5dc75b3
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_PhysicsCollisionObject.h"
+#include "jmeBulletUtil.h"
+#include "jmePhysicsSpace.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+     * Method:    attachCollisionShape
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_attachCollisionShape
+    (JNIEnv * env, jobject object, jlong objectId, jlong shapeId) {
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/IllegalStateException");
+            env->ThrowNew(newExc, "The collision object does not exist.");
+            return;
+        }
+        btCollisionShape* collisionShape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (collisionShape == NULL) {
+            jclass newExc = env->FindClass("java/lang/IllegalStateException");
+            env->ThrowNew(newExc, "The collision shape does not exist.");
+            return;
+        }
+        collisionObject->setCollisionShape(collisionShape);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_finalizeNative
+    (JNIEnv * env, jobject object, jlong objectId) {
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        if (collisionObject -> getUserPointer() != NULL){
+            jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+            delete(userPointer);
+        }
+        delete(collisionObject);
+    }
+    /*
+     * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+     * Method:    initUserPointer
+     * Signature: (JII)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_initUserPointer
+      (JNIEnv *env, jobject object, jlong objectId, jint group, jint groups) {
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        if (userPointer != NULL) {
+//            delete(userPointer);
+        }
+        userPointer = new jmeUserPointer();
+        userPointer -> javaCollisionObject = env->NewWeakGlobalRef(object);
+        userPointer -> group = group;
+        userPointer -> groups = groups;
+        userPointer -> space = NULL;
+        collisionObject -> setUserPointer(userPointer);
+    }
+    /*
+     * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+     * Method:    setCollisionGroup
+     * Signature: (JI)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionGroup
+      (JNIEnv *env, jobject object, jlong objectId, jint group) {
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        if (userPointer != NULL){
+            userPointer -> group = group;
+        }
+    }
+    /*
+     * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+     * Method:    setCollideWithGroups
+     * Signature: (JI)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollideWithGroups
+      (JNIEnv *env, jobject object, jlong objectId, jint groups) {
+        btCollisionObject* collisionObject = reinterpret_cast<btCollisionObject*>(objectId);
+        if (collisionObject == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+        if (userPointer != NULL){
+            userPointer -> groups = groups;
+        }
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.h b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.h
new file mode 100644
index 0000000..db5bf7a
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_PhysicsCollisionObject.h
@@ -0,0 +1,87 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_PhysicsCollisionObject */
+
+#ifndef _Included_com_jme3_bullet_collision_PhysicsCollisionObject
+#define _Included_com_jme3_bullet_collision_PhysicsCollisionObject
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_NONE
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_NONE 0L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_01
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_01 1L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_02
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_02 2L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_03
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_03 4L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_04
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_04 8L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_05
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_05 16L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_06
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_06 32L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_07
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_07 64L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_08
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_08 128L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_09
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_09 256L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_10
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_10 512L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_11
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_11 1024L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_12
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_12 2048L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_13
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_13 4096L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_14
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_14 8192L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_15
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_15 16384L
+#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_16
+#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_16 32768L
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method:    initUserPointer
+ * Signature: (JII)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_initUserPointer
+  (JNIEnv *, jobject, jlong, jint, jint);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method:    attachCollisionShape
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_attachCollisionShape
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method:    setCollisionGroup
+ * Signature: (JI)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionGroup
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method:    setCollideWithGroups
+ * Signature: (JI)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollideWithGroups
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp
new file mode 100644
index 0000000..3d2f0e3
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_BoxCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_BoxCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_BoxCollisionShape_createShape
+    (JNIEnv *env, jobject object, jobject halfExtents) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 extents =  btVector3();
+        jmeBulletUtil::convert(env, halfExtents, &extents);
+        btBoxShape* shape = new btBoxShape(extents);
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.h
new file mode 100644
index 0000000..5602a0d
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_BoxCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_BoxCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_BoxCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_BoxCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_BoxCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_BoxCollisionShape_createShape
+  (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp
new file mode 100644
index 0000000..b3d4a8e
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CapsuleCollisionShape
+     * Method:    createShape
+     * Signature: (IFF)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CapsuleCollisionShape_createShape
+    (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) {
+        jmeClasses::initJavaClasses(env);
+        btCollisionShape* shape;
+        switch(axis){
+            case 0:
+                shape = new btCapsuleShapeX(radius, height);
+                break;
+            case 1:
+                shape = new btCapsuleShape(radius, height);
+                break;
+            case 2:
+                shape = new btCapsuleShapeZ(radius, height);
+                break;
+        }
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h
new file mode 100644
index 0000000..4d70674
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_CapsuleCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_CapsuleCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_CapsuleCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CapsuleCollisionShape
+ * Method:    createShape
+ * Signature: (IFF)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CapsuleCollisionShape_createShape
+  (JNIEnv *, jobject, jint, jfloat, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.cpp
new file mode 100644
index 0000000..efd7990
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_CollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+     * Method:    getMargin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_getMargin
+    (JNIEnv * env, jobject object, jlong shapeId) {
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return shape->getMargin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+     * Method:    setLocalScaling
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setLocalScaling
+    (JNIEnv * env, jobject object, jlong shapeId, jobject scale) {
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 scl = btVector3();
+        jmeBulletUtil::convert(env, scale, &scl);
+        shape->setLocalScaling(scl);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+     * Method:    setMargin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setMargin
+    (JNIEnv * env, jobject object, jlong shapeId, jfloat newMargin) {
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        shape->setMargin(newMargin);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_finalizeNative
+    (JNIEnv * env, jobject object, jlong shapeId) {
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        delete(shape);
+    }
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.h
new file mode 100644
index 0000000..cd5d70f
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CollisionShape.h
@@ -0,0 +1,45 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_CollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_CollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_CollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    getMargin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_getMargin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    setLocalScaling
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setLocalScaling
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    setMargin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setMargin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CollisionShape
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp
new file mode 100644
index 0000000..5282175
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_CompoundCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+     * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+     * Method:    createShape
+     * Signature: ()J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_createShape
+    (JNIEnv *env, jobject object) {
+        jmeClasses::initJavaClasses(env);
+        btCompoundShape* shape = new btCompoundShape();
+        return reinterpret_cast<jlong>(shape);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+     * Method:    addChildShape
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_addChildShape
+    (JNIEnv *env, jobject object, jlong compoundId, jlong childId, jobject childLocation, jobject childRotation) {
+        btCompoundShape* shape = reinterpret_cast<btCompoundShape*>(compoundId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btCollisionShape* child = reinterpret_cast<btCollisionShape*>(childId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btMatrix3x3 mtx = btMatrix3x3();
+        btTransform trans = btTransform(mtx);
+        jmeBulletUtil::convert(env, childLocation, &trans.getOrigin());
+        jmeBulletUtil::convert(env, childRotation, &trans.getBasis());
+        shape->addChildShape(trans, child);
+        return 0;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+     * Method:    removeChildShape
+     * Signature: (JJ)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_removeChildShape
+    (JNIEnv * env, jobject object, jlong compoundId, jlong childId) {
+        btCompoundShape* shape = reinterpret_cast<btCompoundShape*>(compoundId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btCollisionShape* child = reinterpret_cast<btCollisionShape*>(childId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        shape->removeChildShape(child);
+        return 0;
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h
new file mode 100644
index 0000000..18783ce
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_CompoundCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_CompoundCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_CompoundCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    createShape
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_createShape
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    addChildShape
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_addChildShape
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CompoundCollisionShape
+ * Method:    removeChildShape
+ * Signature: (JJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_removeChildShape
+  (JNIEnv *, jobject, jlong, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp
new file mode 100644
index 0000000..06de8dd
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_ConeCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_ConeCollisionShape
+     * Method:    createShape
+     * Signature: (IFF)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_ConeCollisionShape_createShape
+    (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) {
+        jmeClasses::initJavaClasses(env);
+        btCollisionShape* shape;
+        switch (axis) {
+            case 0:
+                shape = new btConeShapeX(radius, height);
+                break;
+            case 1:
+                shape = new btConeShape(radius, height);
+                break;
+            case 2:
+                shape = new btConeShapeZ(radius, height);
+                break;
+        }
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.h
new file mode 100644
index 0000000..711276e
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_ConeCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_ConeCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_ConeCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_ConeCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_ConeCollisionShape
+ * Method:    createShape
+ * Signature: (IFF)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_ConeCollisionShape_createShape
+  (JNIEnv *, jobject, jint, jfloat, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp
new file mode 100644
index 0000000..fd82f13
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_CylinderCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_CylinderCollisionShape
+     * Method:    createShape
+     * Signature: (ILcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CylinderCollisionShape_createShape
+    (JNIEnv * env, jobject object, jint axis, jobject halfExtents) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 extents = btVector3();
+        jmeBulletUtil::convert(env, halfExtents, &extents);
+        btCollisionShape* shape;
+        switch (axis) {
+            case 0:
+                shape = new btCylinderShapeX(extents);
+                break;
+            case 1:
+                shape = new btCylinderShape(extents);
+                break;
+            case 2:
+                shape = new btCylinderShapeZ(extents);
+                break;
+        }
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h
new file mode 100644
index 0000000..48a665a
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_CylinderCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_CylinderCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_CylinderCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_CylinderCollisionShape
+ * Method:    createShape
+ * Signature: (ILcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CylinderCollisionShape_createShape
+  (JNIEnv *, jobject, jint, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp
new file mode 100644
index 0000000..43ceb3d
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_GImpactCollisionShape.h"
+#include "jmeBulletUtil.h"
+#include <BulletCollision/Gimpact/btGImpactShape.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+     * Method:    createShape
+     * Signature: (J)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_createShape
+    (JNIEnv * env, jobject object, jlong meshId) {
+        jmeClasses::initJavaClasses(env);
+        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*>(meshId);
+        btGImpactMeshShape* shape = new btGImpactMeshShape(array);
+        return reinterpret_cast<jlong>(shape);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_finalizeNative
+    (JNIEnv * env, jobject object, jlong meshId) {
+        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*> (meshId);
+        delete(array);
+    }
+    
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h
new file mode 100644
index 0000000..f08d3eb
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_GImpactCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_GImpactCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_GImpactCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+ * Method:    createShape
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_createShape
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_GImpactCollisionShape
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp
new file mode 100644
index 0000000..06698a1
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_HeightfieldCollisionShape
+     * Method:    createShape
+     * Signature: (II[FFFFIZ)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape_createShape
+    (JNIEnv * env, jobject object, jint heightStickWidth, jint heightStickLength, jobject heightfieldData, jfloat heightScale, jfloat minHeight, jfloat maxHeight, jint upAxis, jboolean flipQuadEdges) {
+        jmeClasses::initJavaClasses(env);
+        void* data = env->GetDirectBufferAddress(heightfieldData);
+        btHeightfieldTerrainShape* shape=new btHeightfieldTerrainShape(heightStickWidth, heightStickLength, data, heightScale, minHeight, maxHeight, upAxis, PHY_FLOAT, flipQuadEdges);
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h
new file mode 100644
index 0000000..a3d1621
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_HeightfieldCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_HeightfieldCollisionShape
+ * Method:    createShape
+ * Signature: (IILjava/nio/ByteBuffer;FFFIZ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape_createShape
+  (JNIEnv *, jobject, jint, jint, jobject, jfloat, jfloat, jfloat, jint, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp
new file mode 100644
index 0000000..5789bd9
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_HullCollisionShape.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionShapes/btConvexHullShape.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_HullCollisionShape
+     * Method:    createShape
+     * Signature: ([F)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HullCollisionShape_createShape
+    (JNIEnv *env, jobject object, jobject array) {
+        jmeClasses::initJavaClasses(env);
+        float* data = (float*) env->GetDirectBufferAddress(array);
+        //TODO: capacity will not always be length!
+        int length = env->GetDirectBufferCapacity(array)/4;
+        btConvexHullShape* shape = new btConvexHullShape();
+        for (int i = 0; i < length; i+=3) {
+            btVector3 vect = btVector3(data[i],
+                    data[i + 1],
+                    data[i + 2]);
+            
+            shape->addPoint(vect);
+        }
+
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.h
new file mode 100644
index 0000000..42a2672
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_HullCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_HullCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_HullCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_HullCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_HullCollisionShape
+ * Method:    createShape
+ * Signature: (Ljava/nio/ByteBuffer;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HullCollisionShape_createShape
+  (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp
new file mode 100644
index 0000000..346d47e
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_MeshCollisionShape.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+     * Method:    createShape
+     * Signature: (J)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape
+    (JNIEnv * env, jobject object, jlong arrayId) {
+        jmeClasses::initJavaClasses(env);
+        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*>(arrayId);
+        btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(array, true, true);
+        return reinterpret_cast<jlong>(shape);
+    }
+    
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative
+    (JNIEnv * env, jobject object, jlong arrayId){
+        btTriangleIndexVertexArray* array = reinterpret_cast<btTriangleIndexVertexArray*>(arrayId);
+        delete(array);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.h
new file mode 100644
index 0000000..4dd6aa2
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_MeshCollisionShape.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_MeshCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_MeshCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_MeshCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+ * Method:    createShape
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_MeshCollisionShape
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp
new file mode 100644
index 0000000..b8c9adc
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_PlaneCollisionShape.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionShapes/btStaticPlaneShape.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_PlaneCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;F)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_PlaneCollisionShape_createShape
+    (JNIEnv * env, jobject object, jobject normal, jfloat constant) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 norm = btVector3();
+        jmeBulletUtil::convert(env, normal, &norm);
+        btStaticPlaneShape* shape = new btStaticPlaneShape(norm, constant);
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h
new file mode 100644
index 0000000..7e7a22b
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_PlaneCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_PlaneCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_PlaneCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_PlaneCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;F)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_PlaneCollisionShape_createShape
+  (JNIEnv *, jobject, jobject, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp
new file mode 100644
index 0000000..7337945
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_SimplexCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2
+    (JNIEnv *env, jobject object, jobject vector1) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, vector1, &vec1);
+        btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1);
+        return reinterpret_cast<jlong>(simplexShape);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+    (JNIEnv *env, jobject object, jobject vector1, jobject vector2) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, vector1, &vec1);
+        btVector3 vec2 = btVector3();
+        jmeBulletUtil::convert(env, vector2, &vec2);
+        btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2);
+        return reinterpret_cast<jlong>(simplexShape);
+    }
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+    (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, vector1, &vec1);
+        btVector3 vec2 = btVector3();
+        jmeBulletUtil::convert(env, vector2, &vec2);
+        btVector3 vec3 = btVector3();
+        jmeBulletUtil::convert(env, vector3, &vec3);
+        btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3);
+        return reinterpret_cast<jlong>(simplexShape);
+    }
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+     * Method:    createShape
+     * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+    (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3, jobject vector4) {
+        jmeClasses::initJavaClasses(env);
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, vector1, &vec1);
+        btVector3 vec2 = btVector3();
+        jmeBulletUtil::convert(env, vector2, &vec2);
+        btVector3 vec3 = btVector3();
+        jmeBulletUtil::convert(env, vector3, &vec3);
+        btVector3 vec4 = btVector3();
+        jmeBulletUtil::convert(env, vector4, &vec4);
+        btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3, vec4);
+        return reinterpret_cast<jlong>(simplexShape);
+    }
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h
new file mode 100644
index 0000000..e50d062
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h
@@ -0,0 +1,45 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_SimplexCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_SimplexCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_SimplexCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2
+  (JNIEnv *, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+  (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+  (JNIEnv *, jobject, jobject, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SimplexCollisionShape
+ * Method:    createShape
+ * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2
+  (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp
new file mode 100644
index 0000000..8f4b984
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_collision_shapes_SphereCollisionShape.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_collision_shapes_SphereCollisionShape
+     * Method:    createShape
+     * Signature: (F)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SphereCollisionShape_createShape
+    (JNIEnv *env, jobject object, jfloat radius) {
+        jmeClasses::initJavaClasses(env);
+        btSphereShape* shape=new btSphereShape(radius);
+        return reinterpret_cast<jlong>(shape);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.h b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.h
new file mode 100644
index 0000000..ef1b2cb
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_collision_shapes_SphereCollisionShape.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_collision_shapes_SphereCollisionShape */
+
+#ifndef _Included_com_jme3_bullet_collision_shapes_SphereCollisionShape
+#define _Included_com_jme3_bullet_collision_shapes_SphereCollisionShape
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_collision_shapes_SphereCollisionShape
+ * Method:    createShape
+ * Signature: (F)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SphereCollisionShape_createShape
+  (JNIEnv *, jobject, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.cpp
new file mode 100644
index 0000000..cade120
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_ConeJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_ConeJoint
+     * Method:    setLimit
+     * Signature: (JFFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setLimit
+    (JNIEnv * env, jobject object, jlong jointId, jfloat swingSpan1, jfloat swingSpan2, jfloat twistSpan) {
+        btConeTwistConstraint* joint = reinterpret_cast<btConeTwistConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        //TODO: extended setLimit!
+        joint->setLimit(swingSpan1, swingSpan2, twistSpan);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_ConeJoint
+     * Method:    setAngularOnly
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setAngularOnly
+    (JNIEnv * env, jobject object, jlong jointId, jboolean angularOnly) {
+        btConeTwistConstraint* joint = reinterpret_cast<btConeTwistConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setAngularOnly(angularOnly);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_ConeJoint
+     * Method:    createJoint
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_ConeJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        btMatrix3x3 mtx1 = btMatrix3x3();
+        btMatrix3x3 mtx2 = btMatrix3x3();
+        btTransform transA = btTransform(mtx1);
+        jmeBulletUtil::convert(env, pivotA, &transA.getOrigin());
+        jmeBulletUtil::convert(env, rotA, &transA.getBasis());
+        btTransform transB = btTransform(mtx2);
+        jmeBulletUtil::convert(env, pivotB, &transB.getOrigin());
+        jmeBulletUtil::convert(env, rotB, &transB.getBasis());
+        btConeTwistConstraint* joint = new btConeTwistConstraint(*bodyA, *bodyB, transA, transB);
+        return reinterpret_cast<jlong>(joint);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.h
new file mode 100644
index 0000000..327b47f
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_ConeJoint.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_ConeJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_ConeJoint
+#define _Included_com_jme3_bullet_joints_ConeJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    setLimit
+ * Signature: (JFFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setLimit
+  (JNIEnv *, jobject, jlong, jfloat, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    setAngularOnly
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setAngularOnly
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_joints_ConeJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_ConeJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.cpp
new file mode 100644
index 0000000..56a6fec
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_HingeJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    enableMotor
+     * Signature: (JZFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_enableMotor
+    (JNIEnv * env, jobject object, jlong jointId, jboolean enable, jfloat targetVelocity, jfloat maxMotorImpulse) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->enableAngularMotor(enable, targetVelocity, maxMotorImpulse);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getEnableAngularMotor
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_HingeJoint_getEnableAngularMotor
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return joint->getEnableAngularMotor();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getMotorTargetVelocity
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMotorTargetVelocity
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getMotorTargetVelosity();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getMaxMotorImpulse
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMaxMotorImpulse
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getMaxMotorImpulse();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    setLimit
+     * Signature: (JFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFF
+    (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        return joint->setLimit(low, high);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    setLimit
+     * Signature: (JFFFFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFFFFF
+    (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high, jfloat softness, jfloat biasFactor, jfloat relaxationFactor) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        return joint->setLimit(low, high, softness, biasFactor, relaxationFactor);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getUpperLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getUpperLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getUpperLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getLowerLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getLowerLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getLowerLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    setAngularOnly
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setAngularOnly
+    (JNIEnv * env, jobject object, jlong jointId, jboolean angular) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setAngularOnly(angular);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    getHingeAngle
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getHingeAngle
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btHingeConstraint* joint = reinterpret_cast<btHingeConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getHingeAngle();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_HingeJoint
+     * Method:    createJoint
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_HingeJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject axisA, jobject pivotB, jobject axisB) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        btVector3 vec1 = btVector3();
+        btVector3 vec2 = btVector3();
+        btVector3 vec3 = btVector3();
+        btVector3 vec4 = btVector3();
+        jmeBulletUtil::convert(env, pivotA, &vec1);
+        jmeBulletUtil::convert(env, pivotB, &vec2);
+        jmeBulletUtil::convert(env, axisA, &vec3);
+        jmeBulletUtil::convert(env, axisB, &vec4);
+        btHingeConstraint* joint = new btHingeConstraint(*bodyA, *bodyB, vec1, vec2, vec3, vec4);
+        return reinterpret_cast<jlong>(joint);
+    }
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.h
new file mode 100644
index 0000000..ab6e003
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_HingeJoint.h
@@ -0,0 +1,101 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_HingeJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_HingeJoint
+#define _Included_com_jme3_bullet_joints_HingeJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    enableMotor
+ * Signature: (JZFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_enableMotor
+  (JNIEnv *, jobject, jlong, jboolean, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getEnableAngularMotor
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_HingeJoint_getEnableAngularMotor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getMotorTargetVelocity
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMotorTargetVelocity
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getMaxMotorImpulse
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMaxMotorImpulse
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    setLimit
+ * Signature: (JFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFF
+  (JNIEnv *, jobject, jlong, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    setLimit
+ * Signature: (JFFFFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFFFFF
+  (JNIEnv *, jobject, jlong, jfloat, jfloat, jfloat, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getUpperLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getUpperLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getLowerLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getLowerLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    setAngularOnly
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setAngularOnly
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    getHingeAngle
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getHingeAngle
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_HingeJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_HingeJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.cpp
new file mode 100644
index 0000000..c5ae9c1
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_PhysicsJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_PhysicsJoint
+     * Method:    getAppliedImpulse
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_getAppliedImpulse
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btTypedConstraint* joint = reinterpret_cast<btTypedConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getAppliedImpulse();
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.h
new file mode 100644
index 0000000..63e21a7
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_PhysicsJoint.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_PhysicsJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_PhysicsJoint
+#define _Included_com_jme3_bullet_joints_PhysicsJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_PhysicsJoint
+ * Method:    getAppliedImpulse
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_getAppliedImpulse
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_PhysicsJoint
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.cpp
new file mode 100644
index 0000000..e019b9b
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_Point2PointJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    setDamping
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setDamping
+    (JNIEnv * env, jobject object, jlong jointId, jfloat damping) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->m_setting.m_damping = damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    setImpulseClamp
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setImpulseClamp
+    (JNIEnv * env, jobject object, jlong jointId, jfloat clamp) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->m_setting.m_impulseClamp = clamp;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    setTau
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setTau
+    (JNIEnv * env, jobject object, jlong jointId, jfloat tau) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->m_setting.m_tau = tau;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    getDamping
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getDamping
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->m_setting.m_damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    getImpulseClamp
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getImpulseClamp
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->m_setting.m_damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    getTau
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getTau
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btPoint2PointConstraint* joint = reinterpret_cast<btPoint2PointConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->m_setting.m_damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_Point2PointJoint
+     * Method:    createJoint
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject pivotB) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        //TODO: matrix not needed?
+        btMatrix3x3 mtx1 = btMatrix3x3();
+        btMatrix3x3 mtx2 = btMatrix3x3();
+        btTransform transA = btTransform(mtx1);
+        jmeBulletUtil::convert(env, pivotA, &transA.getOrigin());
+        btTransform transB = btTransform(mtx2);
+        jmeBulletUtil::convert(env, pivotB, &transB.getOrigin());
+        btHingeConstraint* joint = new btHingeConstraint(*bodyA, *bodyB, transA, transB);
+        return reinterpret_cast<jlong>(joint);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.h
new file mode 100644
index 0000000..5cb8188
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_Point2PointJoint.h
@@ -0,0 +1,69 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_Point2PointJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_Point2PointJoint
+#define _Included_com_jme3_bullet_joints_Point2PointJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setDamping
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setDamping
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setImpulseClamp
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setImpulseClamp
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    setTau
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setTau
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    getDamping
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getDamping
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    getImpulseClamp
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getImpulseClamp
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    getTau
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getTau
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_Point2PointJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.cpp
new file mode 100644
index 0000000..9611a14
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_SixDofJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    getRotationalLimitMotor
+     * Signature: (JI)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getRotationalLimitMotor
+    (JNIEnv * env, jobject object, jlong jointId, jint index) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return reinterpret_cast<jlong>(joint->getRotationalLimitMotor(index));
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    getTranslationalLimitMotor
+     * Signature: (J)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getTranslationalLimitMotor
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return reinterpret_cast<jlong>(joint->getTranslationalLimitMotor());
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    setLinearUpperLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearUpperLimit
+    (JNIEnv * env, jobject object, jlong jointId, jobject vector) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        joint->setLinearUpperLimit(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    setLinearLowerLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearLowerLimit
+    (JNIEnv * env, jobject object, jlong jointId, jobject vector) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        joint->setLinearLowerLimit(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    setAngularUpperLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularUpperLimit
+    (JNIEnv * env, jobject object, jlong jointId, jobject vector) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        joint->setAngularUpperLimit(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    setAngularLowerLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularLowerLimit
+    (JNIEnv * env, jobject object, jlong jointId, jobject vector) {
+        btGeneric6DofConstraint* joint = reinterpret_cast<btGeneric6DofConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        joint->setAngularLowerLimit(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SixDofJoint
+     * Method:    createJoint
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        btMatrix3x3 mtx1 = btMatrix3x3();
+        btMatrix3x3 mtx2 = btMatrix3x3();
+        btTransform transA = btTransform(mtx1);
+        jmeBulletUtil::convert(env, pivotA, &transA.getOrigin());
+        jmeBulletUtil::convert(env, rotA, &transA.getBasis());
+        btTransform transB = btTransform(mtx2);
+        jmeBulletUtil::convert(env, pivotB, &transB.getOrigin());
+        jmeBulletUtil::convert(env, rotB, &transB.getBasis());
+        btGeneric6DofConstraint* joint = new btGeneric6DofConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA);
+        return reinterpret_cast<jlong>(joint);
+    }
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.h
new file mode 100644
index 0000000..86e6102
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofJoint.h
@@ -0,0 +1,69 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_SixDofJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_SixDofJoint
+#define _Included_com_jme3_bullet_joints_SixDofJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    getRotationalLimitMotor
+ * Signature: (JI)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getRotationalLimitMotor
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    getTranslationalLimitMotor
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getTranslationalLimitMotor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    setLinearUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearUpperLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    setLinearLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearLowerLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    setAngularUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularUpperLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    setAngularLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularLowerLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.cpp
new file mode 100644
index 0000000..97724c6
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.cpp
@@ -0,0 +1,94 @@
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_SixDofSpringJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    enableString
+ * Signature: (JIZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_enableSpring
+  (JNIEnv *env, jobject object, jlong jointId, jint index, jboolean onOff) {
+    btGeneric6DofSpringConstraint* joint = reinterpret_cast<btGeneric6DofSpringConstraint*>(jointId);
+    joint -> enableSpring(index, onOff);
+}
+
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setStiffness
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setStiffness
+  (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat stiffness) {
+    btGeneric6DofSpringConstraint* joint = reinterpret_cast<btGeneric6DofSpringConstraint*>(jointId);
+    joint -> setStiffness(index, stiffness);
+}
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setDamping
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setDamping
+  (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat damping) {
+    btGeneric6DofSpringConstraint* joint = reinterpret_cast<btGeneric6DofSpringConstraint*>(jointId);
+    joint -> setDamping(index, damping);
+}
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setEquilibriumPoint
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__J
+  (JNIEnv *env, jobject object, jlong jointId) {
+    btGeneric6DofSpringConstraint* joint = reinterpret_cast<btGeneric6DofSpringConstraint*>(jointId);
+    joint -> setEquilibriumPoint();
+}
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setEquilibriumPoint
+ * Signature: (JI)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__JI
+  (JNIEnv *env, jobject object, jlong jointId, jint index) {
+    btGeneric6DofSpringConstraint* joint = reinterpret_cast<btGeneric6DofSpringConstraint*>(jointId);
+    joint -> setEquilibriumPoint(index);
+}
+
+
+
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        btTransform transA;
+        jmeBulletUtil::convert(env, pivotA, &transA.getOrigin());
+        jmeBulletUtil::convert(env, rotA, &transA.getBasis());
+        btTransform transB;
+        jmeBulletUtil::convert(env, pivotB, &transB.getOrigin());
+        jmeBulletUtil::convert(env, rotB, &transB.getBasis());
+
+        btGeneric6DofSpringConstraint* joint = new btGeneric6DofSpringConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA);
+        return reinterpret_cast<jlong>(joint);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.h
new file mode 100644
index 0000000..b4fced0
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SixDofSpringJoint.h
@@ -0,0 +1,61 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_SixDofSpringJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_SixDofSpringJoint
+#define _Included_com_jme3_bullet_joints_SixDofSpringJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    enableSpring
+ * Signature: (JIZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_enableSpring
+  (JNIEnv *, jobject, jlong, jint, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setStiffness
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setStiffness
+  (JNIEnv *, jobject, jlong, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setDamping
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setDamping
+  (JNIEnv *, jobject, jlong, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setEquilibriumPoint
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__J
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    setEquilibriumPoint
+ * Signature: (JI)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__JI
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_joints_SixDofSpringJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.cpp
new file mode 100644
index 0000000..c6e704d
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.cpp
@@ -0,0 +1,963 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_SliderJoint.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getLowerLinLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerLinLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getLowerLinLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setLowerLinLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerLinLimit
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setLowerLinLimit(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getUpperLinLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperLinLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getUpperLinLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setUpperLinLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperLinLimit
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setUpperLinLimit(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getLowerAngLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerAngLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getLowerAngLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setLowerAngLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerAngLimit
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setLowerAngLimit(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getUpperAngLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperAngLimit
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getUpperAngLimit();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setUpperAngLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperAngLimit
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setUpperAngLimit(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessDirLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessDirLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessDirLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessDirLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionDirLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionDirLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionDirLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionDirLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingDirLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingDirLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingDirLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingDirLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessDirAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessDirAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessDirAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessDirAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionDirAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionDirAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionDirAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionDirAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingDirAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingDirAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingDirAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingDirAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessLimLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessLimLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessLimLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessLimLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionLimLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionLimLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionLimLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionLimLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingLimLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingLimLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingLimLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingLimLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessLimAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessLimAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessLimAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessLimAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionLimAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionLimAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionLimAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionLimAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingLimAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingLimAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingLimAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingLimAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessOrthoLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessOrthoLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessOrthoLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessOrthoLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionOrthoLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionOrthoLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionOrthoLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionOrthoLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingOrthoLin
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingOrthoLin();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingOrthoLin
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoLin
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingOrthoLin(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getSoftnessOrthoAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getSoftnessOrthoAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setSoftnessOrthoAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setSoftnessOrthoAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getRestitutionOrthoAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getRestitutionOrthoAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setRestitutionOrthoAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setRestitutionOrthoAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getDampingOrthoAng
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getDampingOrthoAng();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setDampingOrthoAng
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoAng
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setDampingOrthoAng(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    isPoweredLinMotor
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredLinMotor
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return joint->getPoweredLinMotor();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setPoweredLinMotor
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredLinMotor
+    (JNIEnv * env, jobject object, jlong jointId, jboolean value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setPoweredLinMotor(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getTargetLinMotorVelocity
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetLinMotorVelocity
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getTargetLinMotorVelocity();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setTargetLinMotorVelocity
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetLinMotorVelocity
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setTargetLinMotorVelocity(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getMaxLinMotorForce
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxLinMotorForce
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getMaxLinMotorForce();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setMaxLinMotorForce
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxLinMotorForce
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setMaxLinMotorForce(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    isPoweredAngMotor
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredAngMotor
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return joint->getPoweredAngMotor();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setPoweredAngMotor
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredAngMotor
+    (JNIEnv * env, jobject object, jlong jointId, jboolean value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setPoweredAngMotor(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getTargetAngMotorVelocity
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetAngMotorVelocity
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getTargetAngMotorVelocity();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setTargetAngMotorVelocity
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetAngMotorVelocity
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setTargetAngMotorVelocity(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    getMaxAngMotorForce
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxAngMotorForce
+    (JNIEnv * env, jobject object, jlong jointId) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return joint->getMaxAngMotorForce();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    setMaxAngMotorForce
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxAngMotorForce
+    (JNIEnv * env, jobject object, jlong jointId, jfloat value) {
+        btSliderConstraint* joint = reinterpret_cast<btSliderConstraint*>(jointId);
+        if (joint == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        joint->setMaxAngMotorForce(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_SliderJoint
+     * Method:    createJoint
+     * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SliderJoint_createJoint
+    (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* bodyA = reinterpret_cast<btRigidBody*>(bodyIdA);
+        btRigidBody* bodyB = reinterpret_cast<btRigidBody*>(bodyIdB);
+        btMatrix3x3 mtx1 = btMatrix3x3();
+        btMatrix3x3 mtx2 = btMatrix3x3();
+        btTransform transA = btTransform(mtx1);
+        jmeBulletUtil::convert(env, pivotA, &transA.getOrigin());
+        jmeBulletUtil::convert(env, rotA, &transA.getBasis());
+        btTransform transB = btTransform(mtx2);
+        jmeBulletUtil::convert(env, pivotB, &transB.getOrigin());
+        jmeBulletUtil::convert(env, rotB, &transB.getBasis());
+        btSliderConstraint* joint = new btSliderConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA);
+        return reinterpret_cast<jlong>(joint);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.h b/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.h
new file mode 100644
index 0000000..7afd66a
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_SliderJoint.h
@@ -0,0 +1,469 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_SliderJoint */
+
+#ifndef _Included_com_jme3_bullet_joints_SliderJoint
+#define _Included_com_jme3_bullet_joints_SliderJoint
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getLowerLinLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerLinLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setLowerLinLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerLinLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getUpperLinLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperLinLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setUpperLinLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperLinLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getLowerAngLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerAngLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setLowerAngLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerAngLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getUpperAngLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperAngLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setUpperAngLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperAngLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessDirLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessDirLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionDirLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionDirLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingDirLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingDirLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessDirAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessDirAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionDirAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionDirAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingDirAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingDirAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessLimLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessLimLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionLimLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionLimLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingLimLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingLimLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessLimAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessLimAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionLimAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionLimAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingLimAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingLimAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessOrthoLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessOrthoLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionOrthoLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionOrthoLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingOrthoLin
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoLin
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingOrthoLin
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoLin
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getSoftnessOrthoAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setSoftnessOrthoAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getRestitutionOrthoAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setRestitutionOrthoAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getDampingOrthoAng
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoAng
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setDampingOrthoAng
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoAng
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    isPoweredLinMotor
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredLinMotor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setPoweredLinMotor
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredLinMotor
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getTargetLinMotorVelocity
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetLinMotorVelocity
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setTargetLinMotorVelocity
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetLinMotorVelocity
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getMaxLinMotorForce
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxLinMotorForce
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setMaxLinMotorForce
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxLinMotorForce
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    isPoweredAngMotor
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredAngMotor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setPoweredAngMotor
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredAngMotor
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getTargetAngMotorVelocity
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetAngMotorVelocity
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setTargetAngMotorVelocity
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetAngMotorVelocity
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    getMaxAngMotorForce
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxAngMotorForce
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    setMaxAngMotorForce
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxAngMotorForce
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_SliderJoint
+ * Method:    createJoint
+ * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SliderJoint_createJoint
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp
new file mode 100644
index 0000000..e9da298
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_motors_RotationalLimitMotor.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getLoLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLoLimit
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_loLimit;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setLoLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLoLimit
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_loLimit = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getHiLimit
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getHiLimit
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_hiLimit;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setHiLimit
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setHiLimit
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_hiLimit = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getTargetVelocity
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getTargetVelocity
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_targetVelocity;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setTargetVelocity
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setTargetVelocity
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_targetVelocity = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getMaxMotorForce
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxMotorForce
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_maxMotorForce;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setMaxMotorForce
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxMotorForce
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_maxMotorForce = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getMaxLimitForce
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxLimitForce
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_maxLimitForce;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setMaxLimitForce
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxLimitForce
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_maxLimitForce = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getDamping
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getDamping
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setDamping
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setDamping
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_damping = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getLimitSoftness
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLimitSoftness
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_limitSoftness;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setLimitSoftness
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLimitSoftness
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_limitSoftness = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getERP
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getERP
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_stopERP;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setERP
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setERP
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_stopERP = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    getBounce
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getBounce
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_bounce;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setBounce
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setBounce
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_bounce = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    isEnableMotor
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_isEnableMotor
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return motor->m_enableMotor;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+     * Method:    setEnableMotor
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setEnableMotor
+    (JNIEnv *env, jobject object, jlong motorId, jboolean value) {
+        btRotationalLimitMotor* motor = reinterpret_cast<btRotationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_enableMotor = value;
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.h b/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.h
new file mode 100644
index 0000000..b14bf1d
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_motors_RotationalLimitMotor.h
@@ -0,0 +1,173 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_motors_RotationalLimitMotor */
+
+#ifndef _Included_com_jme3_bullet_joints_motors_RotationalLimitMotor
+#define _Included_com_jme3_bullet_joints_motors_RotationalLimitMotor
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getLoLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLoLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setLoLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLoLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getHiLimit
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getHiLimit
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setHiLimit
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setHiLimit
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getTargetVelocity
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getTargetVelocity
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setTargetVelocity
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setTargetVelocity
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getMaxMotorForce
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxMotorForce
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setMaxMotorForce
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxMotorForce
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getMaxLimitForce
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxLimitForce
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setMaxLimitForce
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxLimitForce
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getDamping
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getDamping
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setDamping
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setDamping
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getLimitSoftness
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLimitSoftness
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setLimitSoftness
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLimitSoftness
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getERP
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getERP
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setERP
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setERP
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    getBounce
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getBounce
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setBounce
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setBounce
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    isEnableMotor
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_isEnableMotor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_RotationalLimitMotor
+ * Method:    setEnableMotor
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setEnableMotor
+  (JNIEnv *, jobject, jlong, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp b/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp
new file mode 100644
index 0000000..64f0de0
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_joints_motors_TranslationalLimitMotor.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getLowerLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLowerLimit
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &motor->m_lowerLimit, vector);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setLowerLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLowerLimit
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, vector, &motor->m_lowerLimit);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getUpperLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getUpperLimit
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &motor->m_upperLimit, vector);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setUpperLimit
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setUpperLimit
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, vector, &motor->m_upperLimit);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getAccumulatedImpulse
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getAccumulatedImpulse
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &motor->m_accumulatedImpulse, vector);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setAccumulatedImpulse
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setAccumulatedImpulse
+    (JNIEnv *env, jobject object, jlong motorId, jobject vector) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, vector, &motor->m_accumulatedImpulse);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getLimitSoftness
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLetLimitSoftness
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_limitSoftness;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setLimitSoftness
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLimitSoftness
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_limitSoftness = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getDamping
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getDamping
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_damping;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setDamping
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setDamping
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_damping = value;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    getRestitution
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getRestitution
+    (JNIEnv *env, jobject object, jlong motorId) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return motor->m_restitution;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+     * Method:    setRestitution
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setRestitution
+    (JNIEnv *env, jobject object, jlong motorId, jfloat value) {
+        btTranslationalLimitMotor* motor = reinterpret_cast<btTranslationalLimitMotor*>(motorId);
+        if (motor == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        motor->m_restitution = value;
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h b/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h
new file mode 100644
index 0000000..0ea93e2
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h
@@ -0,0 +1,109 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_joints_motors_TranslationalLimitMotor */
+
+#ifndef _Included_com_jme3_bullet_joints_motors_TranslationalLimitMotor
+#define _Included_com_jme3_bullet_joints_motors_TranslationalLimitMotor
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLowerLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setLowerLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLowerLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getUpperLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setUpperLimit
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setUpperLimit
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getAccumulatedImpulse
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getAccumulatedImpulse
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setAccumulatedImpulse
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setAccumulatedImpulse
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getLimitSoftness
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLimitSoftness
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setLimitSoftness
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLimitSoftness
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getDamping
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getDamping
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setDamping
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setDamping
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    getRestitution
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getRestitution
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_joints_motors_TranslationalLimitMotor
+ * Method:    setRestitution
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setRestitution
+  (JNIEnv *, jobject, jlong, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.cpp
new file mode 100644
index 0000000..f64cc3a
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+
+#include "com_jme3_bullet_objects_PhysicsCharacter.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionDispatch/btGhostObject.h"
+#include "BulletDynamics/Character/btKinematicCharacterController.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    createGhostObject
+     * Signature: ()J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createGhostObject
+    (JNIEnv * env, jobject object) {
+        jmeClasses::initJavaClasses(env);
+        btPairCachingGhostObject* ghost = new btPairCachingGhostObject();
+        return reinterpret_cast<jlong>(ghost);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setCharacterFlags
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCharacterFlags
+    (JNIEnv *env, jobject object, jlong ghostId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(ghostId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCollisionFlags(/*ghost->getCollisionFlags() |*/ btCollisionObject::CF_CHARACTER_OBJECT);
+        ghost->setCollisionFlags(ghost->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    createCharacterObject
+     * Signature: (JJF)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createCharacterObject
+    (JNIEnv *env, jobject object, jlong objectId, jlong shapeId, jfloat stepHeight) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        //TODO: check convexshape!
+        btConvexShape* shape = reinterpret_cast<btConvexShape*>(shapeId);
+        btKinematicCharacterController* character = new btKinematicCharacterController(ghost, shape, stepHeight);
+        return reinterpret_cast<jlong>(character);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    warp
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_warp
+    (JNIEnv *env, jobject object, jlong objectId, jobject vector) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        character->warp(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setWalkDirection
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDirection
+    (JNIEnv *env, jobject object, jlong objectId, jobject vector) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, vector, &vec);
+        character->setWalkDirection(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setUpAxis
+     * Signature: (JI)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis
+    (JNIEnv *env, jobject object, jlong objectId, jint value) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->setUpAxis(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setFallSpeed
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setFallSpeed
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->setFallSpeed(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setJumpSpeed
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpeed
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->setJumpSpeed(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setGravity
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->setGravity(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getGravity
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return character->getGravity();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setMaxSlope
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->setMaxSlope(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getMaxSlope
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return character->getMaxSlope();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    onGround
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGround
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return character->onGround();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    jump
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        character->jump();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getPhysicsLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getPhysicsLocation
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setCcdSweptSphereRadius
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCcdSweptSphereRadius(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    setCcdMotionThreshold
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCcdMotionThreshold(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getCcdSweptSphereRadius
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdSweptSphereRadius();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getCcdMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdMotionThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    getCcdSquareMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSquareMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btGhostObject* ghost = reinterpret_cast<btGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdSquareMotionThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsCharacter
+     * Method:    finalizeNativeCharacter
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_finalizeNativeCharacter
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btKinematicCharacterController* character = reinterpret_cast<btKinematicCharacterController*>(objectId);
+        if (character == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        delete(character);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.h b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.h
new file mode 100644
index 0000000..210198c
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsCharacter.h
@@ -0,0 +1,215 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_PhysicsCharacter */
+
+#ifndef _Included_com_jme3_bullet_objects_PhysicsCharacter
+#define _Included_com_jme3_bullet_objects_PhysicsCharacter
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_NONE
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_NONE 0L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_01
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_01 1L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_02
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_02 2L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_03
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_03 4L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_04
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_04 8L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_05
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_05 16L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_06
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_06 32L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_07
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_07 64L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_08
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_08 128L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_09
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_09 256L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_10
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_10 512L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_11
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_11 1024L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_12
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_12 2048L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_13
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_13 4096L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_14
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_14 8192L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_15
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_15 16384L
+#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_16
+#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_16 32768L
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    createGhostObject
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createGhostObject
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setCharacterFlags
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCharacterFlags
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    createCharacterObject
+ * Signature: (JJF)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createCharacterObject
+  (JNIEnv *, jobject, jlong, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    warp
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_warp
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setWalkDirection
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDirection
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setUpAxis
+ * Signature: (JI)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setFallSpeed
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setFallSpeed
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setJumpSpeed
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpeed
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setGravity
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getGravity
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setMaxSlope
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getMaxSlope
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    onGround
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGround
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    jump
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getPhysicsLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getPhysicsLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setCcdSweptSphereRadius
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    setCcdMotionThreshold
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdMotionThreshold
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getCcdSweptSphereRadius
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getCcdMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    getCcdSquareMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSquareMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsCharacter
+ * Method:    finalizeNativeCharacter
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_finalizeNativeCharacter
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.cpp
new file mode 100644
index 0000000..2fb48f5
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+
+#include <BulletCollision/CollisionDispatch/btGhostObject.h>
+
+#include "com_jme3_bullet_objects_PhysicsGhostObject.h"
+#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
+#include "jmeBulletUtil.h"
+#include "jmePhysicsSpace.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    createGhostObject
+     * Signature: ()J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_createGhostObject
+    (JNIEnv * env, jobject object) {
+        jmeClasses::initJavaClasses(env);
+        btPairCachingGhostObject* ghost = new btPairCachingGhostObject();
+        return reinterpret_cast<jlong>(ghost);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setGhostFlags
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setGhostFlags
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCollisionFlags(ghost->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setPhysicsLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsLocation
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getOrigin());
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setPhysicsRotation
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getBasis());
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setPhysicsRotation
+     * Signature: (JLcom/jme3/math/Quaternion;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Quaternion_2
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convertQuat(env, value, &ghost->getWorldTransform().getBasis());
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getPhysicsLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsLocation
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getPhysicsRotation
+     * Signature: (JLcom/jme3/math/Quaternion;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotation
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convertQuat(env, &ghost->getWorldTransform().getBasis(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getPhysicsRotationMatrix
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotationMatrix
+    (JNIEnv *env, jobject object, jlong objectId, jobject value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &ghost->getWorldTransform().getBasis(), value);
+    }
+
+    class jmeGhostOverlapCallback : public btOverlapCallback {
+        JNIEnv* m_env;
+        jobject m_object;
+    public:
+        jmeGhostOverlapCallback(JNIEnv *env, jobject object)
+                :m_env(env),
+                 m_object(object)
+        {
+        }
+        virtual ~jmeGhostOverlapCallback() {}
+        virtual bool    processOverlap(btBroadphasePair& pair)
+        {
+            btCollisionObject *co1 = (btCollisionObject *)pair.m_pProxy1->m_clientObject;
+            jmeUserPointer *up1 = (jmeUserPointer*)co1 -> getUserPointer();
+            jobject javaCollisionObject1 = m_env->NewLocalRef(up1->javaCollisionObject);
+            m_env->CallVoidMethod(m_object, jmeClasses::PhysicsGhostObject_addOverlappingObject, javaCollisionObject1);
+            m_env->DeleteLocalRef(javaCollisionObject1);
+            if (m_env->ExceptionCheck()) {
+                m_env->Throw(m_env->ExceptionOccurred());
+                return false;
+            }
+
+            return false;
+        }
+    };
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getOverlappingObjects
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingObjects
+      (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btHashedOverlappingPairCache * pc = ghost->getOverlappingPairCache();
+        jmeGhostOverlapCallback cb(env, object);
+        pc -> processAllOverlappingPairs(&cb, NULL);
+    }
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getOverlappingCount
+     * Signature: (J)I
+     */
+    JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingCount
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getNumOverlappingObjects();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setCcdSweptSphereRadius
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCcdSweptSphereRadius(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    setCcdMotionThreshold
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId, jfloat value) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        ghost->setCcdMotionThreshold(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getCcdSweptSphereRadius
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdSweptSphereRadius();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getCcdMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdMotionThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+     * Method:    getCcdSquareMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSquareMotionThreshold
+    (JNIEnv *env, jobject object, jlong objectId) {
+        btPairCachingGhostObject* ghost = reinterpret_cast<btPairCachingGhostObject*>(objectId);
+        if (ghost == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return ghost->getCcdSquareMotionThreshold();
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.h b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.h
new file mode 100644
index 0000000..cf98e59
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsGhostObject.h
@@ -0,0 +1,167 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_PhysicsGhostObject */
+
+#ifndef _Included_com_jme3_bullet_objects_PhysicsGhostObject
+#define _Included_com_jme3_bullet_objects_PhysicsGhostObject
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_NONE
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_NONE 0L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_01
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_01 1L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_02
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_02 2L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_03
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_03 4L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_04
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_04 8L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_05
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_05 16L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_06
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_06 32L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_07
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_07 64L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_08
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_08 128L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_09
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_09 256L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_10
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_10 512L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_11
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_11 1024L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_12
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_12 2048L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_13
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_13 4096L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_14
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_14 8192L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_15
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_15 16384L
+#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_16
+#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_16 32768L
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    createGhostObject
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_createGhostObject
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setGhostFlags
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setGhostFlags
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setPhysicsLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setPhysicsRotation
+ * Signature: (JLcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setPhysicsRotation
+ * Signature: (JLcom/jme3/math/Quaternion;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Quaternion_2
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getPhysicsLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getPhysicsRotation
+ * Signature: (JLcom/jme3/math/Quaternion;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getPhysicsRotationMatrix
+ * Signature: (JLcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotationMatrix
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getOverlappingObjects
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingObjects
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getOverlappingCount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingCount
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setCcdSweptSphereRadius
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    setCcdMotionThreshold
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdMotionThreshold
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getCcdSweptSphereRadius
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getCcdMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsGhostObject
+ * Method:    getCcdSquareMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSquareMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.cpp
new file mode 100644
index 0000000..4794cde
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.cpp
@@ -0,0 +1,849 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_objects_PhysicsRigidBody.h"
+#include "jmeBulletUtil.h"
+#include "jmeMotionState.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    createRigidBody
+     * Signature: (FJJ)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_createRigidBody
+    (JNIEnv *env, jobject object, jfloat mass, jlong motionstatId, jlong shapeId) {
+        jmeClasses::initJavaClasses(env);
+        btMotionState* motionState = reinterpret_cast<btMotionState*>(motionstatId);
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        btVector3 localInertia = btVector3();
+        shape->calculateLocalInertia(mass, localInertia);
+        btRigidBody* body = new btRigidBody(mass, motionState, shape, localInertia);
+        body->setUserPointer(NULL);
+        return reinterpret_cast<jlong>(body);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    isInWorld
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isInWorld
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return body->isInWorld();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setPhysicsLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsLocation
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        //        if (body->isStaticOrKinematicObject() || !body->isInWorld())
+        ((jmeMotionState*) body->getMotionState())->setKinematicLocation(env, value);
+        body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform);
+        //        else{
+        //            btMatrix3x3* mtx = &btMatrix3x3();
+        //            btTransform* trans = &btTransform(*mtx);
+        //            trans->setBasis(body->getCenterOfMassTransform().getBasis());
+        //            jmeBulletUtil::convert(env, value, &trans->getOrigin());
+        //            body->setCenterOfMassTransform(*trans);
+        //        }
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setPhysicsRotation
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        //        if (body->isStaticOrKinematicObject() || !body->isInWorld())
+        ((jmeMotionState*) body->getMotionState())->setKinematicRotation(env, value);
+        body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform);
+        //        else{
+        //            btMatrix3x3* mtx = &btMatrix3x3();
+        //            btTransform* trans = &btTransform(*mtx);
+        //            trans->setOrigin(body->getCenterOfMassTransform().getOrigin());
+        //            jmeBulletUtil::convert(env, value, &trans->getBasis());
+        //            body->setCenterOfMassTransform(*trans);
+        //        }
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setPhysicsRotation
+     * Signature: (JLcom/jme3/math/Quaternion;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Quaternion_2
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        //        if (body->isStaticOrKinematicObject() || !body->isInWorld())
+        ((jmeMotionState*) body->getMotionState())->setKinematicRotationQuat(env, value);
+        body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform);
+        //        else{
+        //            btMatrix3x3* mtx = &btMatrix3x3();
+        //            btTransform* trans = &btTransform(*mtx);
+        //            trans->setOrigin(body->getCenterOfMassTransform().getOrigin());
+        //            jmeBulletUtil::convertQuat(env, value, &trans->getBasis());
+        //            body->setCenterOfMassTransform(*trans);
+        //        }
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getPhysicsLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &body->getWorldTransform().getOrigin(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getPhysicsRotation
+     * Signature: (JLcom/jme3/math/Quaternion;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotation
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convertQuat(env, &body->getWorldTransform().getBasis(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getPhysicsRotationMatrix
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotationMatrix
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &body->getWorldTransform().getBasis(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setKinematic
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setKinematic
+    (JNIEnv *env, jobject object, jlong bodyId, jboolean value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        if (value) {
+            body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
+            body->setActivationState(DISABLE_DEACTIVATION);
+        } else {
+            body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
+            body->setActivationState(ACTIVE_TAG);
+        }
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setCcdSweptSphereRadius
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setCcdSweptSphereRadius(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setCcdMotionThreshold
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setCcdMotionThreshold(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getCcdSweptSphereRadius
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSweptSphereRadius
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getCcdSweptSphereRadius();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getCcdMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdMotionThreshold
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getCcdMotionThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getCcdSquareMotionThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSquareMotionThreshold
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getCcdSquareMotionThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setStatic
+     * Signature: (JZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setStatic
+    (JNIEnv *env, jobject object, jlong bodyId, jboolean value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        if (value) {
+            body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT);
+        } else {
+            body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_STATIC_OBJECT);
+        }
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    updateMassProps
+     * Signature: (JJF)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_updateMassProps
+    (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId, jfloat mass) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        btVector3 localInertia = btVector3();
+        shape->calculateLocalInertia(mass, localInertia);
+        body->setMassProps(mass, localInertia);
+        return reinterpret_cast<jlong>(body);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getGravity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getGravity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &body->getGravity(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setGravity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setGravity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, value, &vec);
+        body->setGravity(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getFriction
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getFriction
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getFriction();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setFriction
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setFriction
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setFriction(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setDamping
+     * Signature: (JFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setDamping
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value1, jfloat value2) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setDamping(value1, value2);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setAngularDamping
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularDamping
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setDamping(body->getAngularDamping(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getLinearDamping
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearDamping
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getLinearDamping();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getAngularDamping
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularDamping
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getAngularDamping();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getRestitution
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getRestitution
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getRestitution();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setRestitution
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setRestitution
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setRestitution(value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getAngularVelocity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularVelocity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &body->getAngularVelocity(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setAngularVelocity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularVelocity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, value, &vec);
+        body->setAngularVelocity(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getLinearVelocity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearVelocity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &body->getLinearVelocity(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setLinearVelocity
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearVelocity
+    (JNIEnv *env, jobject object, jlong bodyId, jobject value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec = btVector3();
+        jmeBulletUtil::convert(env, value, &vec);
+        body->setLinearVelocity(vec);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    applyForce
+     * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyForce
+    (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        btVector3 vec2 = btVector3();
+        jmeBulletUtil::convert(env, force, &vec1);
+        jmeBulletUtil::convert(env, location, &vec2);
+        body->applyForce(vec1, vec2);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    applyCentralForce
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyCentralForce
+    (JNIEnv *env, jobject object, jlong bodyId, jobject force) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, force, &vec1);
+        body->applyCentralForce(vec1);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    applyTorque
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorque
+    (JNIEnv *env, jobject object, jlong bodyId, jobject force) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, force, &vec1);
+        body->applyTorque(vec1);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    applyImpulse
+     * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyImpulse
+    (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        btVector3 vec2 = btVector3();
+        jmeBulletUtil::convert(env, force, &vec1);
+        jmeBulletUtil::convert(env, location, &vec2);
+        body->applyImpulse(vec1, vec2);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    applyTorqueImpulse
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorqueImpulse
+    (JNIEnv *env, jobject object, jlong bodyId, jobject force) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        jmeBulletUtil::convert(env, force, &vec1);
+        body->applyTorqueImpulse(vec1);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    clearForces
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_clearForces
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->clearForces();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setCollisionShape
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCollisionShape
+    (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        body->setCollisionShape(shape);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    activate
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_activate
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->activate(false);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    isActive
+     * Signature: (J)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isActive
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return body->isActive();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setSleepingThresholds
+     * Signature: (JFF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setSleepingThresholds
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat linear, jfloat angular) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setSleepingThresholds(linear, angular);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setLinearSleepingThreshold
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearSleepingThreshold
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setSleepingThresholds(value, body->getLinearSleepingThreshold());
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setAngularSleepingThreshold
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularSleepingThreshold
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        body->setSleepingThresholds(body->getAngularSleepingThreshold(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getLinearSleepingThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearSleepingThreshold
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getLinearSleepingThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getAngularSleepingThreshold
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularSleepingThreshold
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getAngularSleepingThreshold();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    getAngularFactor
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularFactor
+    (JNIEnv *env, jobject object, jlong bodyId) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return body->getAngularFactor().getX();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+     * Method:    setAngularFactor
+     * Signature: (JF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularFactor
+    (JNIEnv *env, jobject object, jlong bodyId, jfloat value) {
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(bodyId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 vec1 = btVector3();
+        vec1.setX(value);
+        vec1.setY(value);
+        vec1.setZ(value);
+        body->setAngularFactor(vec1);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.h b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.h
new file mode 100644
index 0000000..aa09a62
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsRigidBody.h
@@ -0,0 +1,415 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_PhysicsRigidBody */
+
+#ifndef _Included_com_jme3_bullet_objects_PhysicsRigidBody
+#define _Included_com_jme3_bullet_objects_PhysicsRigidBody
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_NONE
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_NONE 0L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_01
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_01 1L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_02
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_02 2L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_03
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_03 4L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_04
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_04 8L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_05
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_05 16L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_06
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_06 32L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_07
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_07 64L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_08
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_08 128L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_09
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_09 256L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_10
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_10 512L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_11
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_11 1024L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_12
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_12 2048L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_13
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_13 4096L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_14
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_14 8192L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_15
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_15 16384L
+#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_16
+#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_16 32768L
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    createRigidBody
+ * Signature: (FJJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_createRigidBody
+  (JNIEnv *, jobject, jfloat, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    isInWorld
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isInWorld
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setPhysicsLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setPhysicsRotation
+ * Signature: (JLcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setPhysicsRotation
+ * Signature: (JLcom/jme3/math/Quaternion;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Quaternion_2
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getPhysicsLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getPhysicsRotation
+ * Signature: (JLcom/jme3/math/Quaternion;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getPhysicsRotationMatrix
+ * Signature: (JLcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotationMatrix
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setKinematic
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setKinematic
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setCcdSweptSphereRadius
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setCcdMotionThreshold
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdMotionThreshold
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getCcdSweptSphereRadius
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSweptSphereRadius
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getCcdMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getCcdSquareMotionThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSquareMotionThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setStatic
+ * Signature: (JZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setStatic
+  (JNIEnv *, jobject, jlong, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    updateMassProps
+ * Signature: (JJF)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_updateMassProps
+  (JNIEnv *, jobject, jlong, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getGravity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getGravity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setGravity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setGravity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getFriction
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getFriction
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setFriction
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setFriction
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setDamping
+ * Signature: (JFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setDamping
+  (JNIEnv *, jobject, jlong, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setAngularDamping
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularDamping
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getLinearDamping
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearDamping
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getAngularDamping
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularDamping
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getRestitution
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getRestitution
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setRestitution
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setRestitution
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getAngularVelocity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularVelocity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setAngularVelocity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularVelocity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getLinearVelocity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearVelocity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setLinearVelocity
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearVelocity
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    applyForce
+ * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyForce
+  (JNIEnv *, jobject, jlong, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    applyCentralForce
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyCentralForce
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    applyTorque
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorque
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    applyImpulse
+ * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyImpulse
+  (JNIEnv *, jobject, jlong, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    applyTorqueImpulse
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorqueImpulse
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    clearForces
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_clearForces
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setCollisionShape
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCollisionShape
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    activate
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_activate
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    isActive
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isActive
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setSleepingThresholds
+ * Signature: (JFF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setSleepingThresholds
+  (JNIEnv *, jobject, jlong, jfloat, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setLinearSleepingThreshold
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearSleepingThreshold
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setAngularSleepingThreshold
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularSleepingThreshold
+  (JNIEnv *, jobject, jlong, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getLinearSleepingThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearSleepingThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getAngularSleepingThreshold
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularSleepingThreshold
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    getAngularFactor
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularFactor
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsRigidBody
+ * Method:    setAngularFactor
+ * Signature: (JF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularFactor
+  (JNIEnv *, jobject, jlong, jfloat);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.cpp
new file mode 100644
index 0000000..b96f08b
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+
+#include "com_jme3_bullet_objects_PhysicsVehicle.h"
+#include "jmeBulletUtil.h"
+#include "jmePhysicsSpace.h"
+#include "BulletDynamics/Vehicle/btRaycastVehicle.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    updateWheelTransform
+     * Signature: (JIZ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_updateWheelTransform
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jboolean interpolated) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->updateWheelTransform(wheel, interpolated);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    createVehicleRaycaster
+     * Signature: (JJ)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createVehicleRaycaster
+    (JNIEnv *env, jobject object, jlong bodyId, jlong spaceId) {
+        //btRigidBody* body = reinterpret_cast<btRigidBody*> bodyId;
+        jmeClasses::initJavaClasses(env);
+        jmePhysicsSpace *space = reinterpret_cast<jmePhysicsSpace*>(spaceId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btDefaultVehicleRaycaster* caster = new btDefaultVehicleRaycaster(space->getDynamicsWorld());
+        return reinterpret_cast<jlong>(caster);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    createRaycastVehicle
+     * Signature: (JJ)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createRaycastVehicle
+    (JNIEnv *env, jobject object, jlong objectId, jlong casterId) {
+        jmeClasses::initJavaClasses(env);
+        btRigidBody* body = reinterpret_cast<btRigidBody*>(objectId);
+        if (body == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        body->setActivationState(DISABLE_DEACTIVATION);
+        btVehicleRaycaster* caster = reinterpret_cast<btDefaultVehicleRaycaster*>(casterId);
+        if (caster == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btRaycastVehicle::btVehicleTuning tuning;
+        btRaycastVehicle* vehicle = new btRaycastVehicle(tuning, body, caster);
+        return reinterpret_cast<jlong>(vehicle);
+
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    setCoordinateSystem
+     * Signature: (JIII)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_setCoordinateSystem
+    (JNIEnv *env, jobject object, jlong vehicleId, jint right, jint up, jint forward) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->setCoordinateSystem(right, up, forward);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    addWheel
+     * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;FFLcom/jme3/bullet/objects/infos/VehicleTuning;Z)J
+     */
+    JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_addWheel
+    (JNIEnv *env, jobject object, jlong vehicleId, jobject location, jobject direction, jobject axle, jfloat restLength, jfloat radius, jobject tuning, jboolean frontWheel) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        btVector3 vec1 = btVector3();
+        btVector3 vec2 = btVector3();
+        btVector3 vec3 = btVector3();
+        jmeBulletUtil::convert(env, location, &vec1);
+        jmeBulletUtil::convert(env, direction, &vec2);
+        jmeBulletUtil::convert(env, axle, &vec3);
+        btRaycastVehicle::btVehicleTuning tune;
+        btWheelInfo* info = &vehicle->addWheel(vec1, vec2, vec3, restLength, radius, tune, frontWheel);
+        int idx = vehicle->getNumWheels();
+        return idx-1;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    resetSuspension
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_resetSuspension
+    (JNIEnv *env, jobject object, jlong vehicleId) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->resetSuspension();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    applyEngineForce
+     * Signature: (JIF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_applyEngineForce
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat force) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->applyEngineForce(force, wheel);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    steer
+     * Signature: (JIF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_steer
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->setSteeringValue(value, wheel);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    brake
+     * Signature: (JIF)F
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_brake
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        vehicle->setBrake(value, wheel);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    getCurrentVehicleSpeedKmHour
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getCurrentVehicleSpeedKmHour
+    (JNIEnv *env, jobject object, jlong vehicleId) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return vehicle->getCurrentSpeedKmHour();
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    getForwardVector
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getForwardVector
+    (JNIEnv *env, jobject object, jlong vehicleId, jobject out) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        btVector3 forwardVector = vehicle->getForwardVector();
+        jmeBulletUtil::convert(env, &forwardVector, out);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_PhysicsVehicle
+     * Method:    finalizeNative
+     * Signature: (JJ)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_finalizeNative
+    (JNIEnv *env, jobject object, jlong casterId, jlong vehicleId) {
+        btVehicleRaycaster* rayCaster = reinterpret_cast<btVehicleRaycaster*>(casterId);
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        delete(vehicle);
+        if (rayCaster == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        delete(rayCaster);
+    }
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.h b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.h
new file mode 100644
index 0000000..821e384
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_PhysicsVehicle.h
@@ -0,0 +1,143 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_PhysicsVehicle */
+
+#ifndef _Included_com_jme3_bullet_objects_PhysicsVehicle
+#define _Included_com_jme3_bullet_objects_PhysicsVehicle
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_NONE
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_NONE 0L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_01
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_01 1L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_02
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_02 2L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_03
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_03 4L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_04
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_04 8L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_05
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_05 16L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_06
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_06 32L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_07
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_07 64L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_08
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_08 128L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_09
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_09 256L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_10
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_10 512L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_11
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_11 1024L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_12
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_12 2048L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_13
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_13 4096L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_14
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_14 8192L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_15
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_15 16384L
+#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_16
+#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_16 32768L
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    updateWheelTransform
+ * Signature: (JIZ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_updateWheelTransform
+  (JNIEnv *, jobject, jlong, jint, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    createVehicleRaycaster
+ * Signature: (JJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createVehicleRaycaster
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    createRaycastVehicle
+ * Signature: (JJ)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createRaycastVehicle
+  (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    setCoordinateSystem
+ * Signature: (JIII)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_setCoordinateSystem
+  (JNIEnv *, jobject, jlong, jint, jint, jint);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    addWheel
+ * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;FFLcom/jme3/bullet/objects/infos/VehicleTuning;Z)I
+ */
+JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_addWheel
+  (JNIEnv *, jobject, jlong, jobject, jobject, jobject, jfloat, jfloat, jobject, jboolean);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    resetSuspension
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_resetSuspension
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    applyEngineForce
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_applyEngineForce
+  (JNIEnv *, jobject, jlong, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    steer
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_steer
+  (JNIEnv *, jobject, jlong, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    brake
+ * Signature: (JIF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_brake
+  (JNIEnv *, jobject, jlong, jint, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    getCurrentVehicleSpeedKmHour
+ * Signature: (J)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getCurrentVehicleSpeedKmHour
+  (JNIEnv *, jobject, jlong);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    getForwardVector
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getForwardVector
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_PhysicsVehicle
+ * Method:    finalizeNative
+ * Signature: (JJ)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_finalizeNative
+  (JNIEnv *, jobject, jlong, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.cpp
new file mode 100644
index 0000000..f05e57f
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+
+#include "com_jme3_bullet_objects_VehicleWheel.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getWheelLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelLocation
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getOrigin(), out);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getWheelRotation
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelRotation
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getBasis(), out);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    applyInfo
+     * Signature: (JFFFFFFFFZF)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_applyInfo
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jfloat suspensionStiffness, jfloat wheelsDampingRelaxation, jfloat wheelsDampingCompression, jfloat frictionSlip, jfloat rollInfluence, jfloat maxSuspensionTravelCm, jfloat maxSuspensionForce, jfloat radius, jboolean frontWheel, jfloat restLength) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        vehicle->getWheelInfo(wheelIndex).m_suspensionStiffness = suspensionStiffness;
+        vehicle->getWheelInfo(wheelIndex).m_wheelsDampingRelaxation = wheelsDampingRelaxation;
+        vehicle->getWheelInfo(wheelIndex).m_wheelsDampingCompression = wheelsDampingCompression;
+        vehicle->getWheelInfo(wheelIndex).m_frictionSlip = frictionSlip;
+        vehicle->getWheelInfo(wheelIndex).m_rollInfluence = rollInfluence;
+        vehicle->getWheelInfo(wheelIndex).m_maxSuspensionTravelCm = maxSuspensionTravelCm;
+        vehicle->getWheelInfo(wheelIndex).m_maxSuspensionForce = maxSuspensionForce;
+        vehicle->getWheelInfo(wheelIndex).m_wheelsRadius = radius;
+        vehicle->getWheelInfo(wheelIndex).m_bIsFrontWheel = frontWheel;
+        vehicle->getWheelInfo(wheelIndex).m_suspensionRestLength1 = restLength;
+
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getCollisionLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionLocation
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactPointWS, out);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getCollisionNormal
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionNormal
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactNormalWS, out);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getSkidInfo
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getSkidInfo
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return vehicle->getWheelInfo(wheelIndex).m_skidInfo;
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_VehicleWheel
+     * Method:    getDeltaRotation
+     * Signature: (J)F
+     */
+    JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getDeltaRotation
+    (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) {
+        btRaycastVehicle* vehicle = reinterpret_cast<btRaycastVehicle*>(vehicleId);
+        if (vehicle == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return 0;
+        }
+        return vehicle->getWheelInfo(wheelIndex).m_deltaRotation;
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.h b/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.h
new file mode 100644
index 0000000..f4ab208
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_VehicleWheel.h
@@ -0,0 +1,69 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_VehicleWheel */
+
+#ifndef _Included_com_jme3_bullet_objects_VehicleWheel
+#define _Included_com_jme3_bullet_objects_VehicleWheel
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getWheelLocation
+ * Signature: (JILcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelLocation
+  (JNIEnv *, jobject, jlong, jint, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getWheelRotation
+ * Signature: (JILcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelRotation
+  (JNIEnv *, jobject, jlong, jint, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    applyInfo
+ * Signature: (JIFFFFFFFFZF)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_applyInfo
+  (JNIEnv *, jobject, jlong, jint, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jboolean, jfloat);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getCollisionLocation
+ * Signature: (JILcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionLocation
+  (JNIEnv *, jobject, jlong, jint, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getCollisionNormal
+ * Signature: (JILcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionNormal
+  (JNIEnv *, jobject, jlong, jint, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getSkidInfo
+ * Signature: (JI)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getSkidInfo
+  (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class:     com_jme3_bullet_objects_VehicleWheel
+ * Method:    getDeltaRotation
+ * Signature: (JI)F
+ */
+JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getDeltaRotation
+  (JNIEnv *, jobject, jlong, jint);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp b/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp
new file mode 100644
index 0000000..f61a376
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_objects_infos_RigidBodyMotionState.h"
+#include "jmeBulletUtil.h"
+#include "jmeMotionState.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    createMotionState
+     * Signature: ()J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_createMotionState
+    (JNIEnv *env, jobject object) {
+        jmeClasses::initJavaClasses(env);
+        jmeMotionState* motionState = new jmeMotionState();
+        return reinterpret_cast<jlong>(motionState);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    applyTransform
+     * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z
+     */
+    JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_applyTransform
+    (JNIEnv *env, jobject object, jlong stateId, jobject location, jobject rotation) {
+        jmeMotionState* motionState = reinterpret_cast<jmeMotionState*>(stateId);
+        if (motionState == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return false;
+        }
+        return motionState->applyTransform(env, location, rotation);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    getWorldLocation
+     * Signature: (JLcom/jme3/math/Vector3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldLocation
+    (JNIEnv *env, jobject object, jlong stateId, jobject value) {
+        jmeMotionState* motionState = reinterpret_cast<jmeMotionState*>(stateId);
+        if (motionState == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &motionState->worldTransform.getOrigin(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    getWorldRotation
+     * Signature: (JLcom/jme3/math/Matrix3f;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotation
+    (JNIEnv *env, jobject object, jlong stateId, jobject value) {
+        jmeMotionState* motionState = reinterpret_cast<jmeMotionState*>(stateId);
+        if (motionState == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convert(env, &motionState->worldTransform.getBasis(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    getWorldRotationQuat
+     * Signature: (JLcom/jme3/math/Quaternion;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotationQuat
+    (JNIEnv *env, jobject object, jlong stateId, jobject value) {
+        jmeMotionState* motionState = reinterpret_cast<jmeMotionState*>(stateId);
+        if (motionState == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        jmeBulletUtil::convertQuat(env, &motionState->worldTransform.getBasis(), value);
+    }
+
+    /*
+     * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+     * Method:    finalizeNative
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_finalizeNative
+    (JNIEnv *env, jobject object, jlong stateId) {
+        jmeMotionState* motionState = reinterpret_cast<jmeMotionState*>(stateId);
+        if (motionState == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The native object does not exist.");
+            return;
+        }
+        delete(motionState);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.h b/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.h
new file mode 100644
index 0000000..7939038
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_objects_infos_RigidBodyMotionState.h
@@ -0,0 +1,61 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_objects_infos_RigidBodyMotionState */
+
+#ifndef _Included_com_jme3_bullet_objects_infos_RigidBodyMotionState
+#define _Included_com_jme3_bullet_objects_infos_RigidBodyMotionState
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    createMotionState
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_createMotionState
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    applyTransform
+ * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_applyTransform
+  (JNIEnv *, jobject, jlong, jobject, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    getWorldLocation
+ * Signature: (JLcom/jme3/math/Vector3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldLocation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    getWorldRotation
+ * Signature: (JLcom/jme3/math/Matrix3f;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotation
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    getWorldRotationQuat
+ * Signature: (JLcom/jme3/math/Quaternion;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotationQuat
+  (JNIEnv *, jobject, jlong, jobject);
+
+/*
+ * Class:     com_jme3_bullet_objects_infos_RigidBodyMotionState
+ * Method:    finalizeNative
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_finalizeNative
+  (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.cpp b/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.cpp
new file mode 100644
index 0000000..7d6c0b0
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen, CJ Hare
+ */
+#include "com_jme3_bullet_util_DebugShapeFactory.h"
+#include "jmeBulletUtil.h"
+#include "BulletCollision/CollisionShapes/btShapeHull.h"
+
+class DebugCallback : public btTriangleCallback, public btInternalTriangleIndexCallback {
+public:
+    JNIEnv* env;
+    jobject callback;
+
+    DebugCallback(JNIEnv* env, jobject object) {
+        this->env = env;
+        this->callback = object;
+    }
+
+    virtual void internalProcessTriangleIndex(btVector3* triangle, int partId, int triangleIndex) {
+        processTriangle(triangle, partId, triangleIndex);
+    }
+
+    virtual void processTriangle(btVector3* triangle, int partId, int triangleIndex) {
+        btVector3 vertexA, vertexB, vertexC;
+        vertexA = triangle[0];
+        vertexB = triangle[1];
+        vertexC = triangle[2];
+        env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ(), partId, triangleIndex);
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+//        triangle = 
+        env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ(), partId, triangleIndex);
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+        env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ(), partId, triangleIndex);
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+    }
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /* Inaccessible static: _00024assertionsDisabled */
+
+    /*
+     * Class:     com_jme3_bullet_util_DebugShapeFactory
+     * Method:    getVertices
+     * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V
+     */
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_util_DebugShapeFactory_getVertices
+    (JNIEnv *env, jclass clazz, jlong shapeId, jobject callback) {
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*>(shapeId);
+        if (shape->isConcave()) {
+            btConcaveShape* concave = reinterpret_cast<btConcaveShape*>(shape);
+            DebugCallback* clb = new DebugCallback(env, callback);
+            btVector3 min = btVector3(-1e30, -1e30, -1e30);
+            btVector3 max = btVector3(1e30, 1e30, 1e30);
+            concave->processAllTriangles(clb, min, max);
+            delete(clb);
+        } else if (shape->isConvex()) {
+            btConvexShape* convexShape = reinterpret_cast<btConvexShape*>(shape);
+            // Check there is a hull shape to render
+            if (convexShape->getUserPointer() == NULL) {
+                // create a hull approximation
+                btShapeHull* hull = new btShapeHull(convexShape);
+                float margin = convexShape->getMargin();
+                hull->buildHull(margin);
+                convexShape->setUserPointer(hull);
+            }
+
+            btShapeHull* hull = (btShapeHull*) convexShape->getUserPointer();
+
+            int numberOfTriangles = hull->numTriangles();
+            int numberOfFloats = 3 * 3 * numberOfTriangles;
+            int byteBufferSize = numberOfFloats * 4;
+            
+            // Loop variables
+            const unsigned int* hullIndices = hull->getIndexPointer();
+            const btVector3* hullVertices = hull->getVertexPointer();
+            btVector3 vertexA, vertexB, vertexC;
+            int index = 0;
+
+            for (int i = 0; i < numberOfTriangles; i++) {
+                // Grab the data for this triangle from the hull
+                vertexA = hullVertices[hullIndices[index++]];
+                vertexB = hullVertices[hullIndices[index++]];
+                vertexC = hullVertices[hullIndices[index++]];
+
+                // Put the verticies into the vertex buffer
+                env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ());
+                if (env->ExceptionCheck()) {
+                    env->Throw(env->ExceptionOccurred());
+                    return;
+                }
+                env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ());
+                if (env->ExceptionCheck()) {
+                    env->Throw(env->ExceptionOccurred());
+                    return;
+                }
+                env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ());
+                if (env->ExceptionCheck()) {
+                    env->Throw(env->ExceptionOccurred());
+                    return;
+                }
+            }
+            delete hull;
+            convexShape->setUserPointer(NULL);
+        }
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.h b/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.h
new file mode 100644
index 0000000..757696f
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_util_DebugShapeFactory.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_util_DebugShapeFactory */
+
+#ifndef _Included_com_jme3_bullet_util_DebugShapeFactory
+#define _Included_com_jme3_bullet_util_DebugShapeFactory
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_util_DebugShapeFactory
+ * Method:    getVertices
+ * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V
+ */
+JNIEXPORT void JNICALL Java_com_jme3_bullet_util_DebugShapeFactory_getVertices
+  (JNIEnv *, jclass, jlong, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.cpp b/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.cpp
new file mode 100644
index 0000000..bf0b478
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+/**
+ * Author: Normen Hansen
+ */
+#include "com_jme3_bullet_util_NativeMeshUtil.h"
+#include "jmeBulletUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /*
+     * Class:     com_jme3_bullet_util_NativeMeshUtil
+     * Method:    createTriangleIndexVertexArray
+     * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J
+     */
+    JNIEXPORT jlong JNICALL Java_com_jme3_bullet_util_NativeMeshUtil_createTriangleIndexVertexArray
+    (JNIEnv * env, jclass cls, jobject triangleIndexBase, jobject vertexIndexBase, jint numTriangles, jint numVertices, jint vertexStride, jint triangleIndexStride) {
+        jmeClasses::initJavaClasses(env);
+        int* triangles = (int*) env->GetDirectBufferAddress(triangleIndexBase);
+        float* vertices = (float*) env->GetDirectBufferAddress(vertexIndexBase);
+        btTriangleIndexVertexArray* array = new btTriangleIndexVertexArray(numTriangles, triangles, triangleIndexStride, numVertices, vertices, vertexStride);
+        return reinterpret_cast<jlong>(array);
+    }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.h b/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.h
new file mode 100644
index 0000000..0be9f4d
--- /dev/null
+++ b/engine/src/bullet-native/com_jme3_bullet_util_NativeMeshUtil.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_jme3_bullet_util_NativeMeshUtil */
+
+#ifndef _Included_com_jme3_bullet_util_NativeMeshUtil
+#define _Included_com_jme3_bullet_util_NativeMeshUtil
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     com_jme3_bullet_util_NativeMeshUtil
+ * Method:    createTriangleIndexVertexArray
+ * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J
+ */
+JNIEXPORT jlong JNICALL Java_com_jme3_bullet_util_NativeMeshUtil_createTriangleIndexVertexArray
+  (JNIEnv *, jclass, jobject, jobject, jint, jint, jint, jint);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/engine/src/bullet-native/jmeBulletUtil.cpp b/engine/src/bullet-native/jmeBulletUtil.cpp
new file mode 100644
index 0000000..4bc899d
--- /dev/null
+++ b/engine/src/bullet-native/jmeBulletUtil.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include <math.h>
+#include "jmeBulletUtil.h"
+
+/**
+ * Author: Normen Hansen,Empire Phoenix, Lutherion
+ */
+void jmeBulletUtil::convert(JNIEnv* env, jobject in, btVector3* out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    float x = env->GetFloatField(in, jmeClasses::Vector3f_x); //env->CallFloatMethod(in, jmeClasses::Vector3f_getX);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float y = env->GetFloatField(in, jmeClasses::Vector3f_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float z = env->GetFloatField(in, jmeClasses::Vector3f_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    out->setX(x);
+    out->setY(y);
+    out->setZ(z);
+}
+
+void jmeBulletUtil::convert(JNIEnv* env, const btVector3* in, jobject out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    float x = in->getX();
+    float y = in->getY();
+    float z = in->getZ();
+    env->SetFloatField(out, jmeClasses::Vector3f_x, x);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Vector3f_y, y);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Vector3f_z, z);
+    //    env->CallObjectMethod(out, jmeClasses::Vector3f_set, x, y, z);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
+
+void jmeBulletUtil::convert(JNIEnv* env, jobject in, btMatrix3x3* out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    float m00 = env->GetFloatField(in, jmeClasses::Matrix3f_m00);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m01 = env->GetFloatField(in, jmeClasses::Matrix3f_m01);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m02 = env->GetFloatField(in, jmeClasses::Matrix3f_m02);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m10 = env->GetFloatField(in, jmeClasses::Matrix3f_m10);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m11 = env->GetFloatField(in, jmeClasses::Matrix3f_m11);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m12 = env->GetFloatField(in, jmeClasses::Matrix3f_m12);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m20 = env->GetFloatField(in, jmeClasses::Matrix3f_m20);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m21 = env->GetFloatField(in, jmeClasses::Matrix3f_m21);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float m22 = env->GetFloatField(in, jmeClasses::Matrix3f_m22);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    out->setValue(m00, m01, m02, m10, m11, m12, m20, m21, m22);
+}
+
+void jmeBulletUtil::convert(JNIEnv* env, const btMatrix3x3* in, jobject out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    float m00 = in->getRow(0).m_floats[0];
+    float m01 = in->getRow(0).m_floats[1];
+    float m02 = in->getRow(0).m_floats[2];
+    float m10 = in->getRow(1).m_floats[0];
+    float m11 = in->getRow(1).m_floats[1];
+    float m12 = in->getRow(1).m_floats[2];
+    float m20 = in->getRow(2).m_floats[0];
+    float m21 = in->getRow(2).m_floats[1];
+    float m22 = in->getRow(2).m_floats[2];
+    env->SetFloatField(out, jmeClasses::Matrix3f_m00, m00);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m01, m01);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m02, m02);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m10, m10);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m11, m11);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m12, m12);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m20, m20);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m21, m21);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Matrix3f_m22, m22);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
+
+void jmeBulletUtil::convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    float x = env->GetFloatField(in, jmeClasses::Quaternion_x);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float y = env->GetFloatField(in, jmeClasses::Quaternion_y);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float z = env->GetFloatField(in, jmeClasses::Quaternion_z);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    float w = env->GetFloatField(in, jmeClasses::Quaternion_w);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    float norm = w * w + x * x + y * y + z * z;
+    float s = (norm == 1.0) ? 2.0 : (norm > 0.1) ? 2.0 / norm : 0.0;
+
+    // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+    // will be used 2-4 times each.
+    float xs = x * s;
+    float ys = y * s;
+    float zs = z * s;
+    float xx = x * xs;
+    float xy = x * ys;
+    float xz = x * zs;
+    float xw = w * xs;
+    float yy = y * ys;
+    float yz = y * zs;
+    float yw = w * ys;
+    float zz = z * zs;
+    float zw = w * zs;
+
+    // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+    out->setValue(1.0 - (yy + zz), (xy - zw), (xz + yw),
+            (xy + zw), 1 - (xx + zz), (yz - xw),
+            (xz - yw), (yz + xw), 1.0 - (xx + yy));
+}
+
+void jmeBulletUtil::convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out) {
+    if (in == NULL || out == NULL) {
+        jmeClasses::throwNPE(env);
+    }
+    // the trace is the sum of the diagonal elements; see
+    // http://mathworld.wolfram.com/MatrixTrace.html
+    float t = in->getRow(0).m_floats[0] + in->getRow(1).m_floats[1] + in->getRow(2).m_floats[2];
+    float w, x, y, z;
+    // we protect the division by s by ensuring that s>=1
+    if (t >= 0) { // |w| >= .5
+        float s = sqrt(t + 1); // |s|>=1 ...
+        w = 0.5f * s;
+        s = 0.5f / s; // so this division isn't bad
+        x = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s;
+        y = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s;
+        z = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s;
+    } else if ((in->getRow(0).m_floats[0] > in->getRow(1).m_floats[1]) && (in->getRow(0).m_floats[0] > in->getRow(2).m_floats[2])) {
+        float s = sqrt(1.0f + in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1] - in->getRow(2).m_floats[2]); // |s|>=1
+        x = s * 0.5f; // |x| >= .5
+        s = 0.5f / s;
+        y = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s;
+        z = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s;
+        w = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s;
+    } else if (in->getRow(1).m_floats[1] > in->getRow(2).m_floats[2]) {
+        float s = sqrt(1.0f + in->getRow(1).m_floats[1] - in->getRow(0).m_floats[0] - in->getRow(2).m_floats[2]); // |s|>=1
+        y = s * 0.5f; // |y| >= .5
+        s = 0.5f / s;
+        x = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s;
+        z = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s;
+        w = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s;
+    } else {
+        float s = sqrt(1.0f + in->getRow(2).m_floats[2] - in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1]); // |s|>=1
+        z = s * 0.5f; // |z| >= .5
+        s = 0.5f / s;
+        x = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s;
+        y = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s;
+        w = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s;
+    }
+
+    env->SetFloatField(out, jmeClasses::Quaternion_x, x);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Quaternion_y, y);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Quaternion_z, z);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    env->SetFloatField(out, jmeClasses::Quaternion_w, w);
+    //    env->CallObjectMethod(out, jmeClasses::Quaternion_set, x, y, z, w);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
+
+void jmeBulletUtil::addResult(JNIEnv* env, jobject resultlist, btVector3 hitnormal, btVector3 m_hitPointWorld, btScalar m_hitFraction, btCollisionObject* hitobject) {
+
+    jobject singleresult = env->AllocObject(jmeClasses::PhysicsRay_Class);
+    jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f);
+
+    convert(env, const_cast<btVector3*> (&hitnormal), hitnormalvec);
+    jmeUserPointer *up1 = (jmeUserPointer*) hitobject -> getUserPointer();
+
+    env->SetObjectField(singleresult, jmeClasses::PhysicsRay_normalInWorldSpace, hitnormalvec);
+    env->SetFloatField(singleresult, jmeClasses::PhysicsRay_hitfraction, m_hitFraction);
+
+    env->SetObjectField(singleresult, jmeClasses::PhysicsRay_collisionObject, up1->javaCollisionObject);
+    env->CallVoidMethod(resultlist, jmeClasses::PhysicsRay_addmethod, singleresult);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
diff --git a/engine/src/bullet-native/jmeBulletUtil.h b/engine/src/bullet-native/jmeBulletUtil.h
new file mode 100644
index 0000000..bd211fd
--- /dev/null
+++ b/engine/src/bullet-native/jmeBulletUtil.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include "jmeClasses.h"
+#include "btBulletDynamicsCommon.h"
+#include "btBulletCollisionCommon.h"
+#include "LinearMath/btVector3.h"
+
+/**
+ * Author: Normen Hansen
+ */
+class jmeBulletUtil{
+public:
+    static void convert(JNIEnv* env, jobject in, btVector3* out);
+    static void convert(JNIEnv* env, const btVector3* in, jobject out);
+    static void convert(JNIEnv* env, jobject in, btMatrix3x3* out);
+    static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out);
+    static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out);
+    static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out);
+    static void addResult(JNIEnv* env, jobject resultlist, const btVector3 hitnormal,const btVector3 m_hitPointWorld,const btScalar  m_hitFraction,btCollisionObject* hitobject);
+private:
+    jmeBulletUtil(){};
+    ~jmeBulletUtil(){};
+    
+};
+
+class jmeUserPointer {
+public:
+    jobject javaCollisionObject;
+    jint group;
+    jint groups;
+    void *space;
+};
\ No newline at end of file
diff --git a/engine/src/bullet-native/jmeClasses.cpp b/engine/src/bullet-native/jmeClasses.cpp
new file mode 100644
index 0000000..ce79ffa
--- /dev/null
+++ b/engine/src/bullet-native/jmeClasses.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include "jmeClasses.h"
+#include <stdio.h>
+
+/**
+ * Author: Normen Hansen,Empire Phoenix, Lutherion
+ */
+//public fields
+jclass jmeClasses::PhysicsSpace;
+jmethodID jmeClasses::PhysicsSpace_preTick;
+jmethodID jmeClasses::PhysicsSpace_postTick;
+jmethodID jmeClasses::PhysicsSpace_addCollisionEvent;
+
+jclass jmeClasses::PhysicsGhostObject;
+jmethodID jmeClasses::PhysicsGhostObject_addOverlappingObject;
+
+jclass jmeClasses::Vector3f;
+jmethodID jmeClasses::Vector3f_set;
+jmethodID jmeClasses::Vector3f_toArray;
+jmethodID jmeClasses::Vector3f_getX;
+jmethodID jmeClasses::Vector3f_getY;
+jmethodID jmeClasses::Vector3f_getZ;
+jfieldID jmeClasses::Vector3f_x;
+jfieldID jmeClasses::Vector3f_y;
+jfieldID jmeClasses::Vector3f_z;
+
+jclass jmeClasses::Quaternion;
+jmethodID jmeClasses::Quaternion_set;
+jmethodID jmeClasses::Quaternion_getX;
+jmethodID jmeClasses::Quaternion_getY;
+jmethodID jmeClasses::Quaternion_getZ;
+jmethodID jmeClasses::Quaternion_getW;
+jfieldID jmeClasses::Quaternion_x;
+jfieldID jmeClasses::Quaternion_y;
+jfieldID jmeClasses::Quaternion_z;
+jfieldID jmeClasses::Quaternion_w;
+
+jclass jmeClasses::Matrix3f;
+jmethodID jmeClasses::Matrix3f_set;
+jmethodID jmeClasses::Matrix3f_get;
+jfieldID jmeClasses::Matrix3f_m00;
+jfieldID jmeClasses::Matrix3f_m01;
+jfieldID jmeClasses::Matrix3f_m02;
+jfieldID jmeClasses::Matrix3f_m10;
+jfieldID jmeClasses::Matrix3f_m11;
+jfieldID jmeClasses::Matrix3f_m12;
+jfieldID jmeClasses::Matrix3f_m20;
+jfieldID jmeClasses::Matrix3f_m21;
+jfieldID jmeClasses::Matrix3f_m22;
+
+jclass jmeClasses::DebugMeshCallback;
+jmethodID jmeClasses::DebugMeshCallback_addVector;
+
+jclass jmeClasses::PhysicsRay_Class;
+jmethodID jmeClasses::PhysicsRay_newSingleResult;
+
+jfieldID jmeClasses::PhysicsRay_normalInWorldSpace;
+jfieldID jmeClasses::PhysicsRay_hitfraction;
+jfieldID jmeClasses::PhysicsRay_collisionObject;
+
+jclass jmeClasses::PhysicsRay_listresult;
+jmethodID jmeClasses::PhysicsRay_addmethod;
+
+//private fields
+//JNIEnv* jmeClasses::env;
+JavaVM* jmeClasses::vm;
+
+void jmeClasses::initJavaClasses(JNIEnv* env) {
+//    if (env != NULL) {
+//        fprintf(stdout, "Check Java VM state\n");
+//        fflush(stdout);
+//        int res = vm->AttachCurrentThread((void**) &jmeClasses::env, NULL);
+//        if (res < 0) {
+//            fprintf(stdout, "** ERROR: getting Java env!\n");
+//            if (res == JNI_EVERSION) fprintf(stdout, "GetEnv Error because of different JNI Version!\n");
+//            fflush(stdout);
+//        }
+//        return;
+//    }
+    if(PhysicsSpace!=NULL) return;
+    fprintf(stdout, "Bullet-Native: Initializing java classes\n");
+    fflush(stdout);
+//    jmeClasses::env = env;
+    env->GetJavaVM(&vm);
+
+    PhysicsSpace = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/PhysicsSpace"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsSpace_preTick = env->GetMethodID(PhysicsSpace, "preTick_native", "(F)V");
+    PhysicsSpace_postTick = env->GetMethodID(PhysicsSpace, "postTick_native", "(F)V");
+    PhysicsSpace_addCollisionEvent = env->GetMethodID(PhysicsSpace, "addCollisionEvent_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;J)V");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsGhostObject = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/objects/PhysicsGhostObject"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    PhysicsGhostObject_addOverlappingObject = env->GetMethodID(PhysicsGhostObject, "addOverlappingObject_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;)V");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    Vector3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Vector3f"));
+    Vector3f_set = env->GetMethodID(Vector3f, "set", "(FFF)Lcom/jme3/math/Vector3f;");
+    Vector3f_toArray = env->GetMethodID(Vector3f, "toArray", "([F)[F");
+    Vector3f_getX = env->GetMethodID(Vector3f, "getX", "()F");
+    Vector3f_getY = env->GetMethodID(Vector3f, "getY", "()F");
+    Vector3f_getZ = env->GetMethodID(Vector3f, "getZ", "()F");
+    Vector3f_x = env->GetFieldID(Vector3f, "x", "F");
+    Vector3f_y = env->GetFieldID(Vector3f, "y", "F");
+    Vector3f_z = env->GetFieldID(Vector3f, "z", "F");
+
+    Quaternion = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Quaternion"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    Quaternion_set = env->GetMethodID(Quaternion, "set", "(FFFF)Lcom/jme3/math/Quaternion;");
+    Quaternion_getW = env->GetMethodID(Quaternion, "getW", "()F");
+    Quaternion_getX = env->GetMethodID(Quaternion, "getX", "()F");
+    Quaternion_getY = env->GetMethodID(Quaternion, "getY", "()F");
+    Quaternion_getZ = env->GetMethodID(Quaternion, "getZ", "()F");
+    Quaternion_x = env->GetFieldID(Quaternion, "x", "F");
+    Quaternion_y = env->GetFieldID(Quaternion, "y", "F");
+    Quaternion_z = env->GetFieldID(Quaternion, "z", "F");
+    Quaternion_w = env->GetFieldID(Quaternion, "w", "F");
+
+    Matrix3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Matrix3f"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    Matrix3f_set = env->GetMethodID(Matrix3f, "set", "(IIF)Lcom/jme3/math/Matrix3f;");
+    Matrix3f_get = env->GetMethodID(Matrix3f, "get", "(II)F");
+    Matrix3f_m00 = env->GetFieldID(Matrix3f, "m00", "F");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+    Matrix3f_m01 = env->GetFieldID(Matrix3f, "m01", "F");
+    Matrix3f_m02 = env->GetFieldID(Matrix3f, "m02", "F");
+    Matrix3f_m10 = env->GetFieldID(Matrix3f, "m10", "F");
+    Matrix3f_m11 = env->GetFieldID(Matrix3f, "m11", "F");
+    Matrix3f_m12 = env->GetFieldID(Matrix3f, "m12", "F");
+    Matrix3f_m20 = env->GetFieldID(Matrix3f, "m20", "F");
+    Matrix3f_m21 = env->GetFieldID(Matrix3f, "m21", "F");
+    Matrix3f_m22 = env->GetFieldID(Matrix3f, "m22", "F");
+
+    DebugMeshCallback = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/util/DebugMeshCallback"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    DebugMeshCallback_addVector = env->GetMethodID(DebugMeshCallback, "addVector", "(FFFII)V");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsRay_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsRayTestResult"));
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsRay_newSingleResult = env->GetMethodID(PhysicsRay_Class,"<init>","()V");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsRay_normalInWorldSpace = env->GetFieldID(PhysicsRay_Class,"hitNormalLocal","Lcom/jme3/math/Vector3f;");
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+
+
+    PhysicsRay_hitfraction = env->GetFieldID(PhysicsRay_Class,"hitFraction","F");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+
+    PhysicsRay_collisionObject = env->GetFieldID(PhysicsRay_Class,"collisionObject","Lcom/jme3/bullet/collision/PhysicsCollisionObject;");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsRay_listresult = env->FindClass("java/util/List");
+    PhysicsRay_listresult = (jclass)env->NewGlobalRef(PhysicsRay_listresult);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+
+    PhysicsRay_addmethod = env->GetMethodID(PhysicsRay_listresult,"add","(Ljava/lang/Object;)Z");
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
+
+void jmeClasses::throwNPE(JNIEnv* env) {
+    if (env == NULL) return;
+    jclass newExc = env->FindClass("java/lang/NullPointerException");
+    env->ThrowNew(newExc, "");
+    return;
+}
diff --git a/engine/src/bullet-native/jmeClasses.h b/engine/src/bullet-native/jmeClasses.h
new file mode 100644
index 0000000..731b86f
--- /dev/null
+++ b/engine/src/bullet-native/jmeClasses.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include <jni.h>
+
+/**
+ * Author: Normen Hansen
+ */
+
+class jmeClasses {
+public:
+    static void initJavaClasses(JNIEnv* env);
+//    static JNIEnv* env;
+    static JavaVM* vm;
+    static jclass PhysicsSpace;
+    static jmethodID PhysicsSpace_preTick;
+    static jmethodID PhysicsSpace_postTick;
+    static jmethodID PhysicsSpace_addCollisionEvent;
+    static jclass PhysicsGhostObject;
+    static jmethodID PhysicsGhostObject_addOverlappingObject;
+
+    static jclass Vector3f;
+    static jmethodID Vector3f_set;
+    static jmethodID Vector3f_getX;
+    static jmethodID Vector3f_getY;
+    static jmethodID Vector3f_getZ;
+    static jmethodID Vector3f_toArray;
+    static jfieldID Vector3f_x;
+    static jfieldID Vector3f_y;
+    static jfieldID Vector3f_z;
+    
+    static jclass Quaternion;
+    static jmethodID Quaternion_set;
+    static jmethodID Quaternion_getX;
+    static jmethodID Quaternion_getY;
+    static jmethodID Quaternion_getZ;
+    static jmethodID Quaternion_getW;
+    static jfieldID Quaternion_x;
+    static jfieldID Quaternion_y;
+    static jfieldID Quaternion_z;
+    static jfieldID Quaternion_w;
+
+    static jclass Matrix3f;
+    static jmethodID Matrix3f_get;
+    static jmethodID Matrix3f_set;
+    static jfieldID Matrix3f_m00;
+    static jfieldID Matrix3f_m01;
+    static jfieldID Matrix3f_m02;
+    static jfieldID Matrix3f_m10;
+    static jfieldID Matrix3f_m11;
+    static jfieldID Matrix3f_m12;
+    static jfieldID Matrix3f_m20;
+    static jfieldID Matrix3f_m21;
+    static jfieldID Matrix3f_m22;
+
+    static jclass PhysicsRay_Class;
+    static jmethodID PhysicsRay_newSingleResult;
+    static jfieldID PhysicsRay_normalInWorldSpace;
+    static jfieldID PhysicsRay_hitfraction;
+    static jfieldID PhysicsRay_collisionObject;
+    static jclass PhysicsRay_listresult;
+    static jmethodID PhysicsRay_addmethod;
+
+    static jclass DebugMeshCallback;
+    static jmethodID DebugMeshCallback_addVector;
+
+    static void throwNPE(JNIEnv* env);
+private:
+    jmeClasses(){};
+    ~jmeClasses(){};
+};
\ No newline at end of file
diff --git a/engine/src/bullet-native/jmeMotionState.cpp b/engine/src/bullet-native/jmeMotionState.cpp
new file mode 100644
index 0000000..0c61f9b
--- /dev/null
+++ b/engine/src/bullet-native/jmeMotionState.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include "jmeMotionState.h"
+#include "jmeBulletUtil.h"
+
+/**
+ * Author: Normen Hansen
+ */
+
+jmeMotionState::jmeMotionState() {
+    trans = new btTransform();
+    trans -> setIdentity();
+    worldTransform = *trans;
+    dirty = true;
+}
+
+void jmeMotionState::getWorldTransform(btTransform& worldTrans) const {
+    worldTrans = worldTransform;
+}
+
+void jmeMotionState::setWorldTransform(const btTransform& worldTrans) {
+    worldTransform = worldTrans;
+    dirty = true;
+}
+
+void jmeMotionState::setKinematicTransform(const btTransform& worldTrans) {
+    worldTransform = worldTrans;
+    dirty = true;
+}
+
+void jmeMotionState::setKinematicLocation(JNIEnv* env, jobject location) {
+    jmeBulletUtil::convert(env, location, &worldTransform.getOrigin());
+    dirty = true;
+}
+
+void jmeMotionState::setKinematicRotation(JNIEnv* env, jobject rotation) {
+    jmeBulletUtil::convert(env, rotation, &worldTransform.getBasis());
+    dirty = true;
+}
+
+void jmeMotionState::setKinematicRotationQuat(JNIEnv* env, jobject rotation) {
+    jmeBulletUtil::convertQuat(env, rotation, &worldTransform.getBasis());
+    dirty = true;
+}
+
+bool jmeMotionState::applyTransform(JNIEnv* env, jobject location, jobject rotation) {
+    if (dirty) {
+        //        fprintf(stdout, "Apply world translation\n");
+        //        fflush(stdout);
+        jmeBulletUtil::convert(env, &worldTransform.getOrigin(), location);
+        jmeBulletUtil::convertQuat(env, &worldTransform.getBasis(), rotation);
+        dirty = false;
+        return true;
+    }
+    return false;
+}
+
+jmeMotionState::~jmeMotionState() {
+    free(trans);
+}
diff --git a/engine/src/bullet-native/jmeMotionState.h b/engine/src/bullet-native/jmeMotionState.h
new file mode 100644
index 0000000..b9e6ebb
--- /dev/null
+++ b/engine/src/bullet-native/jmeMotionState.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include <jni.h>
+
+/**
+ * Author: Normen Hansen
+ */
+
+#include "btBulletDynamicsCommon.h"
+//#include "btBulletCollisionCommon.h"
+
+class jmeMotionState : public btMotionState {
+private:
+    bool dirty;
+    btTransform* trans;
+public:
+    jmeMotionState();
+    virtual ~jmeMotionState();
+
+    btTransform worldTransform;
+    virtual void getWorldTransform(btTransform& worldTrans) const;
+    virtual void setWorldTransform(const btTransform& worldTrans);
+    void setKinematicTransform(const btTransform& worldTrans);
+    void setKinematicLocation(JNIEnv*, jobject);
+    void setKinematicRotation(JNIEnv*, jobject);
+    void setKinematicRotationQuat(JNIEnv*, jobject);
+    bool applyTransform(JNIEnv* env, jobject location, jobject rotation);
+};
diff --git a/engine/src/bullet-native/jmePhysicsSpace.cpp b/engine/src/bullet-native/jmePhysicsSpace.cpp
new file mode 100644
index 0000000..f5e97ea
--- /dev/null
+++ b/engine/src/bullet-native/jmePhysicsSpace.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include "jmePhysicsSpace.h"
+#include "jmeBulletUtil.h"
+#include <stdio.h>
+
+/**
+ * Author: Normen Hansen
+ */
+jmePhysicsSpace::jmePhysicsSpace(JNIEnv* env, jobject javaSpace) {
+    //TODO: global ref? maybe not -> cleaning, rather callback class?
+    this->javaPhysicsSpace = env->NewWeakGlobalRef(javaSpace);
+    this->env = env;
+    env->GetJavaVM(&vm);
+    if (env->ExceptionCheck()) {
+        env->Throw(env->ExceptionOccurred());
+        return;
+    }
+}
+
+void jmePhysicsSpace::attachThread() {
+#ifdef ANDROID
+    vm->AttachCurrentThread((JNIEnv**) &env, NULL);
+#elif defined (JNI_VERSION_1_2)
+    vm->AttachCurrentThread((void**) &env, NULL);
+#else
+    vm->AttachCurrentThread(&env, NULL);
+#endif
+}
+
+JNIEnv* jmePhysicsSpace::getEnv() {
+    attachThread();
+    return this->env;
+}
+
+void jmePhysicsSpace::stepSimulation(jfloat tpf, jint maxSteps, jfloat accuracy) {
+    dynamicsWorld->stepSimulation(tpf, maxSteps, accuracy);
+}
+
+btThreadSupportInterface* jmePhysicsSpace::createSolverThreadSupport(int maxNumThreads) {
+#ifdef _WIN32
+    Win32ThreadSupport::Win32ThreadConstructionInfo threadConstructionInfo("solverThreads", SolverThreadFunc, SolverlsMemoryFunc, maxNumThreads);
+    Win32ThreadSupport* threadSupport = new Win32ThreadSupport(threadConstructionInfo);
+    threadSupport->startSPU();
+#elif defined (USE_PTHREADS)
+    PosixThreadSupport::ThreadConstructionInfo constructionInfo("collision", SolverThreadFunc,
+            SolverlsMemoryFunc, maxNumThreads);
+    PosixThreadSupport* threadSupport = new PosixThreadSupport(constructionInfo);
+    threadSupport->startSPU();
+#else
+    SequentialThreadSupport::SequentialThreadConstructionInfo tci("solverThreads", SolverThreadFunc, SolverlsMemoryFunc);
+    SequentialThreadSupport* threadSupport = new SequentialThreadSupport(tci);
+    threadSupport->startSPU();
+#endif
+    return threadSupport;
+}
+
+btThreadSupportInterface* jmePhysicsSpace::createDispatchThreadSupport(int maxNumThreads) {
+#ifdef _WIN32
+    Win32ThreadSupport::Win32ThreadConstructionInfo threadConstructionInfo("solverThreads", processCollisionTask, createCollisionLocalStoreMemory, maxNumThreads);
+    Win32ThreadSupport* threadSupport = new Win32ThreadSupport(threadConstructionInfo);
+    threadSupport->startSPU();
+#elif defined (USE_PTHREADS)
+    PosixThreadSupport::ThreadConstructionInfo solverConstructionInfo("solver", processCollisionTask,
+            createCollisionLocalStoreMemory, maxNumThreads);
+    PosixThreadSupport* threadSupport = new PosixThreadSupport(solverConstructionInfo);
+    threadSupport->startSPU();
+#else
+    SequentialThreadSupport::SequentialThreadConstructionInfo tci("solverThreads", processCollisionTask, createCollisionLocalStoreMemory);
+    SequentialThreadSupport* threadSupport = new SequentialThreadSupport(tci);
+    threadSupport->startSPU();
+#endif
+    return threadSupport;
+}
+
+void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphaseId, jboolean threading) {
+    // collision configuration contains default setup for memory, collision setup
+    btDefaultCollisionConstructionInfo cci;
+    //    if(threading){
+    //        cci.m_defaultMaxPersistentManifoldPoolSize = 32768;
+    //    }
+    btCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(cci);
+
+    btVector3 min = btVector3(minX, minY, minZ);
+    btVector3 max = btVector3(maxX, maxY, maxZ);
+
+    btBroadphaseInterface* broadphase;
+
+    switch (broadphaseId) {
+        case 0:
+            broadphase = new btSimpleBroadphase();
+            break;
+        case 1:
+            broadphase = new btAxisSweep3(min, max);
+            break;
+        case 2:
+            //TODO: 32bit!
+            broadphase = new btAxisSweep3(min, max);
+            break;
+        case 3:
+            broadphase = new btDbvtBroadphase();
+            break;
+        case 4:
+            //            broadphase = new btGpu3DGridBroadphase(
+            //                    min, max,
+            //                    20, 20, 20,
+            //                    10000, 1000, 25);
+            break;
+    }
+
+    btCollisionDispatcher* dispatcher;
+    btConstraintSolver* solver;
+    // use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
+    if (threading) {
+        btThreadSupportInterface* dispatchThreads = createDispatchThreadSupport(4);
+        dispatcher = new SpuGatheringCollisionDispatcher(dispatchThreads, 4, collisionConfiguration);
+        dispatcher->setDispatcherFlags(btCollisionDispatcher::CD_DISABLE_CONTACTPOOL_DYNAMIC_ALLOCATION);
+    } else {
+        dispatcher = new btCollisionDispatcher(collisionConfiguration);
+    }
+
+    // the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded)
+    if (threading) {
+        btThreadSupportInterface* solverThreads = createSolverThreadSupport(4);
+        solver = new btParallelConstraintSolver(solverThreads);
+    } else {
+        solver = new btSequentialImpulseConstraintSolver;
+    }
+
+    //create dynamics world
+    btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
+    dynamicsWorld = world;
+    dynamicsWorld->setWorldUserInfo(this);
+
+    //parallel solver requires the contacts to be in a contiguous pool, so avoid dynamic allocation
+    if (threading) {
+        world->getSimulationIslandManager()->setSplitIslands(false);
+        world->getSolverInfo().m_numIterations = 4;
+        world->getSolverInfo().m_solverMode = SOLVER_SIMD + SOLVER_USE_WARMSTARTING; //+SOLVER_RANDMIZE_ORDER;
+        world->getDispatchInfo().m_enableSPU = true;
+    }
+
+    broadphase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback());
+
+    dynamicsWorld->setGravity(btVector3(0, -9.81f, 0));
+
+    struct jmeFilterCallback : public btOverlapFilterCallback {
+        // return true when pairs need collision
+
+        virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy * proxy1) const {
+            //            bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
+            //            collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
+            bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
+            collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
+            if (collides) {
+                btCollisionObject* co0 = (btCollisionObject*) proxy0->m_clientObject;
+                btCollisionObject* co1 = (btCollisionObject*) proxy1->m_clientObject;
+                jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer();
+                jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer();
+                if (up0 != NULL && up1 != NULL) {
+                    collides = (up0->group & up1->groups) != 0;
+                    collides = collides && (up1->group & up0->groups);
+
+                    //add some additional logic here that modified 'collides'
+                    return collides;
+                }
+                return false;
+            }
+            return collides;
+        }
+    };
+    dynamicsWorld->getPairCache()->setOverlapFilterCallback(new jmeFilterCallback());
+    dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::preTickCallback, static_cast<void *> (this), true);
+    dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::postTickCallback, static_cast<void *> (this));
+    if (gContactProcessedCallback == NULL) {
+        gContactProcessedCallback = &jmePhysicsSpace::contactProcessedCallback;
+    }
+}
+
+void jmePhysicsSpace::preTickCallback(btDynamicsWorld *world, btScalar timeStep) {
+    jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo();
+    JNIEnv* env = dynamicsWorld->getEnv();
+    jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace());
+    if (javaPhysicsSpace != NULL) {
+        env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_preTick, timeStep);
+        env->DeleteLocalRef(javaPhysicsSpace);
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+    }
+}
+
+void jmePhysicsSpace::postTickCallback(btDynamicsWorld *world, btScalar timeStep) {
+    jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo();
+    JNIEnv* env = dynamicsWorld->getEnv();
+    jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace());
+    if (javaPhysicsSpace != NULL) {
+        env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_postTick, timeStep);
+        env->DeleteLocalRef(javaPhysicsSpace);
+        if (env->ExceptionCheck()) {
+            env->Throw(env->ExceptionOccurred());
+            return;
+        }
+    }
+}
+
+bool jmePhysicsSpace::contactProcessedCallback(btManifoldPoint &cp, void *body0, void *body1) {
+    //    printf("contactProcessedCallback %d %dn", body0, body1);
+    btCollisionObject* co0 = (btCollisionObject*) body0;
+    jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer();
+    btCollisionObject* co1 = (btCollisionObject*) body1;
+    jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer();
+    if (up0 != NULL) {
+        jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space;
+        if (dynamicsWorld != NULL) {
+            JNIEnv* env = dynamicsWorld->getEnv();
+            jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace());
+            if (javaPhysicsSpace != NULL) {
+                jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject);
+                jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject);
+                env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_addCollisionEvent, javaCollisionObject0, javaCollisionObject1, (jlong) & cp);
+                env->DeleteLocalRef(javaPhysicsSpace);
+                env->DeleteLocalRef(javaCollisionObject0);
+                env->DeleteLocalRef(javaCollisionObject1);
+                if (env->ExceptionCheck()) {
+                    env->Throw(env->ExceptionOccurred());
+                    return true;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+btDynamicsWorld* jmePhysicsSpace::getDynamicsWorld() {
+    return dynamicsWorld;
+}
+
+jobject jmePhysicsSpace::getJavaPhysicsSpace() {
+    return javaPhysicsSpace;
+}
+
+jmePhysicsSpace::~jmePhysicsSpace() {
+    delete(dynamicsWorld);
+}
\ No newline at end of file
diff --git a/engine/src/bullet-native/jmePhysicsSpace.h b/engine/src/bullet-native/jmePhysicsSpace.h
new file mode 100644
index 0000000..7ba4c06
--- /dev/null
+++ b/engine/src/bullet-native/jmePhysicsSpace.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+#include <jni.h>
+#include "btBulletDynamicsCommon.h"
+#include "btBulletCollisionCommon.h"
+#include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h"
+#include "BulletCollision/CollisionDispatch/btCollisionObject.h"
+#include "BulletCollision/CollisionDispatch/btGhostObject.h"
+#include "BulletDynamics/Character/btKinematicCharacterController.h"
+#ifdef _WIN32
+#include "BulletMultiThreaded/Win32ThreadSupport.h"
+#else
+#include "BulletMultiThreaded/PosixThreadSupport.h"
+#endif
+#include "BulletMultiThreaded/btParallelConstraintSolver.h"
+#include "BulletMultiThreaded/SpuGatheringCollisionDispatcher.h"
+#include "BulletMultiThreaded/SpuCollisionTaskProcess.h"
+#include "BulletMultiThreaded/SequentialThreadSupport.h"
+#include "BulletCollision/CollisionDispatch/btSimulationIslandManager.h"
+#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h"
+#include "BulletCollision/NarrowPhaseCollision/btPersistentManifold.h"
+
+/**
+ * Author: Normen Hansen
+ */
+class jmePhysicsSpace {
+private:
+	JNIEnv* env;
+	JavaVM* vm;
+	btDynamicsWorld* dynamicsWorld;
+	jobject javaPhysicsSpace;
+        btThreadSupportInterface* createSolverThreadSupport(int);
+        btThreadSupportInterface* createDispatchThreadSupport(int);
+        void attachThread();
+public:
+	jmePhysicsSpace(){};
+	~jmePhysicsSpace();
+        jmePhysicsSpace(JNIEnv*, jobject);
+	void stepSimulation(jfloat, jint, jfloat);
+        void createPhysicsSpace(jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jint, jboolean);
+        btDynamicsWorld* getDynamicsWorld();
+        jobject getJavaPhysicsSpace();
+        JNIEnv* getEnv();
+        static void preTickCallback(btDynamicsWorld*, btScalar);
+        static void postTickCallback(btDynamicsWorld*, btScalar);
+        static bool contactProcessedCallback(btManifoldPoint &, void *, void *);
+};
\ No newline at end of file
diff --git a/engine/src/bullet/com/jme3/bullet/PhysicsSpace.java b/engine/src/bullet/com/jme3/bullet/PhysicsSpace.java
new file mode 100644
index 0000000..14cc635
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/PhysicsSpace.java
@@ -0,0 +1,921 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.AppTask;
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.collision.*;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.PhysicsControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsSpace - The central jbullet-jme physics space</p>
+ * @author normenhansen
+ */
+public class PhysicsSpace {
+
+    public static final int AXIS_X = 0;
+    public static final int AXIS_Y = 1;
+    public static final int AXIS_Z = 2;
+    private long physicsSpaceId = 0;
+    private static ThreadLocal<ConcurrentLinkedQueue<AppTask<?>>> pQueueTL =
+            new ThreadLocal<ConcurrentLinkedQueue<AppTask<?>>>() {
+
+                @Override
+                protected ConcurrentLinkedQueue<AppTask<?>> initialValue() {
+                    return new ConcurrentLinkedQueue<AppTask<?>>();
+                }
+            };
+    private ConcurrentLinkedQueue<AppTask<?>> pQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+    private static ThreadLocal<PhysicsSpace> physicsSpaceTL = new ThreadLocal<PhysicsSpace>();
+    private BroadphaseType broadphaseType = BroadphaseType.DBVT;
+//    private DiscreteDynamicsWorld dynamicsWorld = null;
+//    private BroadphaseInterface broadphase;
+//    private CollisionDispatcher dispatcher;
+//    private ConstraintSolver solver;
+//    private DefaultCollisionConfiguration collisionConfiguration;
+//    private Map<GhostObject, PhysicsGhostObject> physicsGhostNodes = new ConcurrentHashMap<GhostObject, PhysicsGhostObject>();
+    private Map<Long, PhysicsRigidBody> physicsNodes = new ConcurrentHashMap<Long, PhysicsRigidBody>();
+    private List<PhysicsJoint> physicsJoints = new LinkedList<PhysicsJoint>();
+    private List<PhysicsCollisionListener> collisionListeners = new LinkedList<PhysicsCollisionListener>();
+    private List<PhysicsCollisionEvent> collisionEvents = new LinkedList<PhysicsCollisionEvent>();
+    private Map<Integer, PhysicsCollisionGroupListener> collisionGroupListeners = new ConcurrentHashMap<Integer, PhysicsCollisionGroupListener>();
+    private ConcurrentLinkedQueue<PhysicsTickListener> tickListeners = new ConcurrentLinkedQueue<PhysicsTickListener>();
+    private PhysicsCollisionEventFactory eventFactory = new PhysicsCollisionEventFactory();
+    private Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f);
+    private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f);
+    private float accuracy = 1f / 60f;
+    private int maxSubSteps = 4;
+    private AssetManager debugManager;
+
+    static {
+//        System.loadLibrary("bulletjme");
+//        initNativePhysics();
+    }
+
+    /**
+     * Get the current PhysicsSpace <b>running on this thread</b><br/>
+     * For parallel physics, this can also be called from the OpenGL thread to receive the PhysicsSpace
+     * @return the PhysicsSpace running on this thread
+     */
+    public static PhysicsSpace getPhysicsSpace() {
+        return physicsSpaceTL.get();
+    }
+
+    /**
+     * Used internally
+     * @param space
+     */
+    public static void setLocalThreadPhysicsSpace(PhysicsSpace space) {
+        physicsSpaceTL.set(space);
+    }
+
+    public PhysicsSpace() {
+        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), BroadphaseType.DBVT);
+    }
+
+    public PhysicsSpace(BroadphaseType broadphaseType) {
+        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType);
+    }
+
+    public PhysicsSpace(Vector3f worldMin, Vector3f worldMax) {
+        this(worldMin, worldMax, BroadphaseType.AXIS_SWEEP_3);
+    }
+
+    public PhysicsSpace(Vector3f worldMin, Vector3f worldMax, BroadphaseType broadphaseType) {
+        this.worldMin.set(worldMin);
+        this.worldMax.set(worldMax);
+        this.broadphaseType = broadphaseType;
+        create();
+    }
+
+    /**
+     * Has to be called from the (designated) physics thread
+     */
+    public void create() {
+        //TODO: boroadphase!
+        physicsSpaceId = createPhysicsSpace(worldMin.x, worldMin.y, worldMin.z, worldMax.x, worldMax.y, worldMax.z, 3, false);
+        pQueueTL.set(pQueue);
+        physicsSpaceTL.set(this);
+
+//        collisionConfiguration = new DefaultCollisionConfiguration();
+//        dispatcher = new CollisionDispatcher(collisionConfiguration);
+//        switch (broadphaseType) {
+//            case SIMPLE:
+//                broadphase = new SimpleBroadphase();
+//                break;
+//            case AXIS_SWEEP_3:
+//                broadphase = new AxisSweep3(Converter.convert(worldMin), Converter.convert(worldMax));
+//                break;
+//            case AXIS_SWEEP_3_32:
+//                broadphase = new AxisSweep3_32(Converter.convert(worldMin), Converter.convert(worldMax));
+//                break;
+//            case DBVT:
+//                broadphase = new DbvtBroadphase();
+//                break;
+//        }
+//
+//        solver = new SequentialImpulseConstraintSolver();
+//
+//        dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
+//        dynamicsWorld.setGravity(new javax.vecmath.Vector3f(0, -9.81f, 0));
+//
+//        broadphase.getOverlappingPairCache().setInternalGhostPairCallback(new GhostPairCallback());
+//        GImpactCollisionAlgorithm.registerAlgorithm(dispatcher);
+//
+//        //register filter callback for tick / collision
+//        setTickCallback();
+//        setContactCallbacks();
+//        //register filter callback for collision groups
+//        setOverlapFilterCallback();
+    }
+
+    private native long createPhysicsSpace(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int broadphaseType, boolean threading);
+
+    private void preTick_native(float f) {
+        AppTask task = pQueue.poll();
+        task = pQueue.poll();
+        while (task != null) {
+            while (task.isCancelled()) {
+                task = pQueue.poll();
+            }
+            try {
+                task.invoke();
+            } catch (Exception ex) {
+                Logger.getLogger(PhysicsSpace.class.getName()).log(Level.SEVERE, null, ex);
+            }
+            task = pQueue.poll();
+        }
+        for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+            PhysicsTickListener physicsTickCallback = it.next();
+            physicsTickCallback.prePhysicsTick(this, f);
+        }
+    }
+
+    private void postTick_native(float f) {
+        for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+            PhysicsTickListener physicsTickCallback = it.next();
+            physicsTickCallback.physicsTick(this, f);
+        }
+    }
+
+    private void addCollision_native() {
+    }
+
+    private boolean needCollision_native(PhysicsCollisionObject objectA, PhysicsCollisionObject objectB) {
+        return false;
+    }
+
+//    private void setOverlapFilterCallback() {
+//        OverlapFilterCallback callback = new OverlapFilterCallback() {
+//
+//            public boolean needBroadphaseCollision(BroadphaseProxy bp, BroadphaseProxy bp1) {
+//                boolean collides = (bp.collisionFilterGroup & bp1.collisionFilterMask) != 0;
+//                if (collides) {
+//                    collides = (bp1.collisionFilterGroup & bp.collisionFilterMask) != 0;
+//                }
+//                if (collides) {
+//                    assert (bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject && bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject);
+//                    com.bulletphysics.collision.dispatch.CollisionObject colOb = (com.bulletphysics.collision.dispatch.CollisionObject) bp.clientObject;
+//                    com.bulletphysics.collision.dispatch.CollisionObject colOb1 = (com.bulletphysics.collision.dispatch.CollisionObject) bp1.clientObject;
+//                    assert (colOb.getUserPointer() != null && colOb1.getUserPointer() != null);
+//                    PhysicsCollisionObject collisionObject = (PhysicsCollisionObject) colOb.getUserPointer();
+//                    PhysicsCollisionObject collisionObject1 = (PhysicsCollisionObject) colOb1.getUserPointer();
+//                    if ((collisionObject.getCollideWithGroups() & collisionObject1.getCollisionGroup()) > 0
+//                            || (collisionObject1.getCollideWithGroups() & collisionObject.getCollisionGroup()) > 0) {
+//                        PhysicsCollisionGroupListener listener = collisionGroupListeners.get(collisionObject.getCollisionGroup());
+//                        PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(collisionObject1.getCollisionGroup());
+//                        if (listener != null) {
+//                            return listener.collide(collisionObject, collisionObject1);
+//                        } else if (listener1 != null) {
+//                            return listener1.collide(collisionObject, collisionObject1);
+//                        }
+//                        return true;
+//                    } else {
+//                        return false;
+//                    }
+//                }
+//                return collides;
+//            }
+//        };
+//        dynamicsWorld.getPairCache().setOverlapFilterCallback(callback);
+//    }
+//    private void setTickCallback() {
+//        final PhysicsSpace space = this;
+//        InternalTickCallback callback2 = new InternalTickCallback() {
+//
+//            @Override
+//            public void internalTick(DynamicsWorld dw, float f) {
+//                //execute task list
+//                AppTask task = pQueue.poll();
+//                task = pQueue.poll();
+//                while (task != null) {
+//                    while (task.isCancelled()) {
+//                        task = pQueue.poll();
+//                    }
+//                    try {
+//                        task.invoke();
+//                    } catch (Exception ex) {
+//                        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.SEVERE, null, ex);
+//                    }
+//                    task = pQueue.poll();
+//                }
+//                for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+//                    PhysicsTickListener physicsTickCallback = it.next();
+//                    physicsTickCallback.prePhysicsTick(space, f);
+//                }
+//            }
+//        };
+//        dynamicsWorld.setPreTickCallback(callback2);
+//        InternalTickCallback callback = new InternalTickCallback() {
+//
+//            @Override
+//            public void internalTick(DynamicsWorld dw, float f) {
+//                for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+//                    PhysicsTickListener physicsTickCallback = it.next();
+//                    physicsTickCallback.physicsTick(space, f);
+//                }
+//            }
+//        };
+//        dynamicsWorld.setInternalTickCallback(callback, this);
+//    }
+//    private void setContactCallbacks() {
+//        BulletGlobals.setContactAddedCallback(new ContactAddedCallback() {
+//
+//            public boolean contactAdded(ManifoldPoint cp, com.bulletphysics.collision.dispatch.CollisionObject colObj0,
+//                    int partId0, int index0, com.bulletphysics.collision.dispatch.CollisionObject colObj1, int partId1,
+//                    int index1) {
+//                System.out.println("contact added");
+//                return true;
+//            }
+//        });
+//
+//        BulletGlobals.setContactProcessedCallback(new ContactProcessedCallback() {
+//
+//            public boolean contactProcessed(ManifoldPoint cp, Object body0, Object body1) {
+//                if (body0 instanceof CollisionObject && body1 instanceof CollisionObject) {
+//                    PhysicsCollisionObject node = null, node1 = null;
+//                    CollisionObject rBody0 = (CollisionObject) body0;
+//                    CollisionObject rBody1 = (CollisionObject) body1;
+//                    node = (PhysicsCollisionObject) rBody0.getUserPointer();
+//                    node1 = (PhysicsCollisionObject) rBody1.getUserPointer();
+//                    collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, cp));
+//                }
+//                return true;
+//            }
+//        });
+//
+//        BulletGlobals.setContactDestroyedCallback(new ContactDestroyedCallback() {
+//
+//            public boolean contactDestroyed(Object userPersistentData) {
+//                System.out.println("contact destroyed");
+//                return true;
+//            }
+//        });
+//    }
+    private void addCollisionEvent_native(PhysicsCollisionObject node, PhysicsCollisionObject node1, long manifoldPointObjectId) {
+//        System.out.println("addCollisionEvent:"+node.getObjectId()+" "+ node1.getObjectId());
+        collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, manifoldPointObjectId));
+    }
+
+    /**
+     * updates the physics space
+     * @param time the current time value
+     */
+    public void update(float time) {
+        update(time, maxSubSteps);
+    }
+
+    /**
+     * updates the physics space, uses maxSteps<br>
+     * @param time the current time value
+     * @param maxSteps
+     */
+    public void update(float time, int maxSteps) {
+//        if (getDynamicsWorld() == null) {
+//            return;
+//        }
+        //step simulation
+        stepSimulation(physicsSpaceId, time, maxSteps, accuracy);
+    }
+
+    private native void stepSimulation(long space, float time, int maxSteps, float accuracy);
+
+    public void distributeEvents() {
+        //add collision callbacks
+        synchronized (collisionEvents) {
+            for (Iterator<PhysicsCollisionEvent> it = collisionEvents.iterator(); it.hasNext();) {
+                PhysicsCollisionEvent physicsCollisionEvent = it.next();
+                for (PhysicsCollisionListener listener : collisionListeners) {
+                    listener.collision(physicsCollisionEvent);
+                }
+                //recycle events
+                eventFactory.recycle(physicsCollisionEvent);
+                it.remove();
+            }
+        }
+    }
+
+    public static <V> Future<V> enqueueOnThisThread(Callable<V> callable) {
+        AppTask<V> task = new AppTask<V>(callable);
+        System.out.println("created apptask");
+        pQueueTL.get().add(task);
+        return task;
+    }
+
+    /**
+     * calls the callable on the next physics tick (ensuring e.g. force applying)
+     * @param <V>
+     * @param callable
+     * @return
+     */
+    public <V> Future<V> enqueue(Callable<V> callable) {
+        AppTask<V> task = new AppTask<V>(callable);
+        pQueue.add(task);
+        return task;
+    }
+
+    /**
+     * adds an object to the physics space
+     * @param obj the PhysicsControl or Spatial with PhysicsControl to add
+     */
+    public void add(Object obj) {
+        if (obj instanceof PhysicsControl) {
+            ((PhysicsControl) obj).setPhysicsSpace(this);
+        } else if (obj instanceof Spatial) {
+            Spatial node = (Spatial) obj;
+            PhysicsControl control = node.getControl(PhysicsControl.class);
+            control.setPhysicsSpace(this);
+        } else if (obj instanceof PhysicsCollisionObject) {
+            addCollisionObject((PhysicsCollisionObject) obj);
+        } else if (obj instanceof PhysicsJoint) {
+            addJoint((PhysicsJoint) obj);
+        } else {
+            throw (new UnsupportedOperationException("Cannot add this kind of object to the physics space."));
+        }
+    }
+
+    public void addCollisionObject(PhysicsCollisionObject obj) {
+        if (obj instanceof PhysicsGhostObject) {
+            addGhostObject((PhysicsGhostObject) obj);
+        } else if (obj instanceof PhysicsRigidBody) {
+            addRigidBody((PhysicsRigidBody) obj);
+        } else if (obj instanceof PhysicsVehicle) {
+            addRigidBody((PhysicsVehicle) obj);
+        } else if (obj instanceof PhysicsCharacter) {
+            addCharacter((PhysicsCharacter) obj);
+        }
+    }
+
+    /**
+     * removes an object from the physics space
+     * @param obj the PhysicsControl or Spatial with PhysicsControl to remove
+     */
+    public void remove(Object obj) {
+        if (obj instanceof PhysicsControl) {
+            ((PhysicsControl) obj).setPhysicsSpace(null);
+        } else if (obj instanceof Spatial) {
+            Spatial node = (Spatial) obj;
+            PhysicsControl control = node.getControl(PhysicsControl.class);
+            control.setPhysicsSpace(null);
+        } else if (obj instanceof PhysicsCollisionObject) {
+            removeCollisionObject((PhysicsCollisionObject) obj);
+        } else if (obj instanceof PhysicsJoint) {
+            removeJoint((PhysicsJoint) obj);
+        } else {
+            throw (new UnsupportedOperationException("Cannot remove this kind of object from the physics space."));
+        }
+    }
+
+    public void removeCollisionObject(PhysicsCollisionObject obj) {
+        if (obj instanceof PhysicsGhostObject) {
+            removeGhostObject((PhysicsGhostObject) obj);
+        } else if (obj instanceof PhysicsRigidBody) {
+            removeRigidBody((PhysicsRigidBody) obj);
+        } else if (obj instanceof PhysicsCharacter) {
+            removeCharacter((PhysicsCharacter) obj);
+        }
+    }
+
+    /**
+     * adds all physics controls and joints in the given spatial node to the physics space
+     * (e.g. after loading from disk) - recursive if node
+     * @param spatial the rootnode containing the physics objects
+     */
+    public void addAll(Spatial spatial) {
+        if (spatial.getControl(RigidBodyControl.class) != null) {
+            RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class);
+            if (!physicsNodes.containsValue(physicsNode)) {
+                physicsNode.setPhysicsSpace(this);
+            }
+            //add joints
+            List<PhysicsJoint> joints = physicsNode.getJoints();
+            for (Iterator<PhysicsJoint> it1 = joints.iterator(); it1.hasNext();) {
+                PhysicsJoint physicsJoint = it1.next();
+                //add connected physicsnodes if they are not already added
+                if (!physicsNodes.containsValue(physicsJoint.getBodyA())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        add(physicsJoint.getBodyA());
+                    } else {
+                        addRigidBody(physicsJoint.getBodyA());
+                    }
+                }
+                if (!physicsNodes.containsValue(physicsJoint.getBodyB())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        add(physicsJoint.getBodyB());
+                    } else {
+                        addRigidBody(physicsJoint.getBodyB());
+                    }
+                }
+                if (!physicsJoints.contains(physicsJoint)) {
+                    addJoint(physicsJoint);
+                }
+            }
+        } else if (spatial.getControl(PhysicsControl.class) != null) {
+            spatial.getControl(PhysicsControl.class).setPhysicsSpace(this);
+        }
+        //recursion
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it = children.iterator(); it.hasNext();) {
+                Spatial spat = it.next();
+                addAll(spat);
+            }
+        }
+    }
+
+    /**
+     * Removes all physics controls and joints in the given spatial from the physics space
+     * (e.g. before saving to disk) - recursive if node
+     * @param spatial the rootnode containing the physics objects
+     */
+    public void removeAll(Spatial spatial) {
+        if (spatial.getControl(RigidBodyControl.class) != null) {
+            RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class);
+            if (physicsNodes.containsValue(physicsNode)) {
+                physicsNode.setPhysicsSpace(null);
+            }
+            //remove joints
+            List<PhysicsJoint> joints = physicsNode.getJoints();
+            for (Iterator<PhysicsJoint> it1 = joints.iterator(); it1.hasNext();) {
+                PhysicsJoint physicsJoint = it1.next();
+                //add connected physicsnodes if they are not already added
+                if (physicsNodes.containsValue(physicsJoint.getBodyA())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        remove(physicsJoint.getBodyA());
+                    } else {
+                        removeRigidBody(physicsJoint.getBodyA());
+                    }
+                }
+                if (physicsNodes.containsValue(physicsJoint.getBodyB())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        remove(physicsJoint.getBodyB());
+                    } else {
+                        removeRigidBody(physicsJoint.getBodyB());
+                    }
+                }
+                if (physicsJoints.contains(physicsJoint)) {
+                    removeJoint(physicsJoint);
+                }
+            }
+        } else if (spatial.getControl(PhysicsControl.class) != null) {
+            spatial.getControl(PhysicsControl.class).setPhysicsSpace(null);
+        }
+        //recursion
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it = children.iterator(); it.hasNext();) {
+                Spatial spat = it.next();
+                removeAll(spat);
+            }
+        }
+    }
+
+    private native void addCollisionObject(long space, long id);
+
+    private native void removeCollisionObject(long space, long id);
+
+    private native void addRigidBody(long space, long id);
+
+    private native void removeRigidBody(long space, long id);
+
+    private native void addCharacterObject(long space, long id);
+
+    private native void removeCharacterObject(long space, long id);
+
+    private native void addAction(long space, long id);
+
+    private native void removeAction(long space, long id);
+
+    private native void addVehicle(long space, long id);
+
+    private native void removeVehicle(long space, long id);
+
+    private native void addConstraint(long space, long id);
+
+    private native void removeConstraint(long space, long id);
+
+    private void addGhostObject(PhysicsGhostObject node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding ghost object {0} to physics space.", Long.toHexString(node.getObjectId()));
+        addCollisionObject(physicsSpaceId, node.getObjectId());
+    }
+
+    private void removeGhostObject(PhysicsGhostObject node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing ghost object {0} from physics space.", Long.toHexString(node.getObjectId()));
+        removeCollisionObject(physicsSpaceId, node.getObjectId());
+    }
+
+    private void addCharacter(PhysicsCharacter node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding character {0} to physics space.", Long.toHexString(node.getObjectId()));
+        addCharacterObject(physicsSpaceId, node.getObjectId());
+        addAction(physicsSpaceId, node.getControllerId());
+//        dynamicsWorld.addCollisionObject(node.getObjectId(), CollisionFilterGroups.CHARACTER_FILTER, (short) (CollisionFilterGroups.STATIC_FILTER | CollisionFilterGroups.DEFAULT_FILTER));
+//        dynamicsWorld.addAction(node.getControllerId());
+    }
+
+    private void removeCharacter(PhysicsCharacter node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing character {0} from physics space.", Long.toHexString(node.getObjectId()));
+        removeAction(physicsSpaceId, node.getControllerId());
+        removeCharacterObject(physicsSpaceId, node.getObjectId());
+//        dynamicsWorld.removeAction(node.getControllerId());
+//        dynamicsWorld.removeCollisionObject(node.getObjectId());
+    }
+
+    private void addRigidBody(PhysicsRigidBody node) {
+        physicsNodes.put(node.getObjectId(), node);
+
+        //Workaround
+        //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non kinematic again afterward.
+        //so we add it non kinematic, then set it kinematic again.
+        boolean kinematic = false;
+        if (node.isKinematic()) {
+            kinematic = true;
+            node.setKinematic(false);
+        }
+        addRigidBody(physicsSpaceId, node.getObjectId());
+        if (kinematic) {
+            node.setKinematic(true);
+        }
+
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding RigidBody {0} to physics space.", node.getObjectId());
+        if (node instanceof PhysicsVehicle) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding vehicle constraint {0} to physics space.", Long.toHexString(((PhysicsVehicle) node).getVehicleId()));
+            addVehicle(physicsSpaceId, ((PhysicsVehicle) node).getVehicleId());
+        }
+    }
+
+    private void removeRigidBody(PhysicsRigidBody node) {
+        if (node instanceof PhysicsVehicle) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing vehicle constraint {0} from physics space.", Long.toHexString(((PhysicsVehicle) node).getVehicleId()));
+            removeVehicle(physicsSpaceId, ((PhysicsVehicle) node).getVehicleId());
+        }
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing RigidBody {0} from physics space.", Long.toHexString(node.getObjectId()));
+        physicsNodes.remove(node.getObjectId());
+        removeRigidBody(physicsSpaceId, node.getObjectId());
+    }
+
+    private void addJoint(PhysicsJoint joint) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding Joint {0} to physics space.", Long.toHexString(joint.getObjectId()));
+        physicsJoints.add(joint);
+        addConstraint(physicsSpaceId, joint.getObjectId());
+//        dynamicsWorld.addConstraint(joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys());
+    }
+
+    private void removeJoint(PhysicsJoint joint) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing Joint {0} from physics space.", Long.toHexString(joint.getObjectId()));
+        physicsJoints.remove(joint);
+        removeConstraint(physicsSpaceId, joint.getObjectId());
+//        dynamicsWorld.removeConstraint(joint.getObjectId());
+    }
+
+    /**
+     * Sets the gravity of the PhysicsSpace, set before adding physics objects!
+     * @param gravity
+     */
+    public void setGravity(Vector3f gravity) {
+//        dynamicsWorld.setGravity(Converter.convert(gravity));
+        setGravity(physicsSpaceId, gravity);
+    }
+
+    private native void setGravity(long spaceId, Vector3f gravity);
+
+//    /**
+//     * applies gravity value to all objects
+//     */
+//    public void applyGravity() {
+////        dynamicsWorld.applyGravity();
+//    }
+//
+//    /**
+//     * clears forces of all objects
+//     */
+//    public void clearForces() {
+////        dynamicsWorld.clearForces();
+//    }
+//
+    /**
+     * Adds the specified listener to the physics tick listeners.
+     * The listeners are called on each physics step, which is not necessarily
+     * each frame but is determined by the accuracy of the physics space.
+     * @param listener
+     */
+    public void addTickListener(PhysicsTickListener listener) {
+        tickListeners.add(listener);
+    }
+
+    public void removeTickListener(PhysicsTickListener listener) {
+        tickListeners.remove(listener);
+    }
+
+    /**
+     * Adds a CollisionListener that will be informed about collision events
+     * @param listener the CollisionListener to add
+     */
+    public void addCollisionListener(PhysicsCollisionListener listener) {
+        collisionListeners.add(listener);
+    }
+
+    /**
+     * Removes a CollisionListener from the list
+     * @param listener the CollisionListener to remove
+     */
+    public void removeCollisionListener(PhysicsCollisionListener listener) {
+        collisionListeners.remove(listener);
+    }
+
+    /**
+     * Adds a listener for a specific collision group, such a listener can disable collisions when they happen.<br>
+     * There can be only one listener per collision group.
+     * @param listener
+     * @param collisionGroup
+     */
+    public void addCollisionGroupListener(PhysicsCollisionGroupListener listener, int collisionGroup) {
+        collisionGroupListeners.put(collisionGroup, listener);
+    }
+
+    public void removeCollisionGroupListener(int collisionGroup) {
+        collisionGroupListeners.remove(collisionGroup);
+    }
+
+    /**
+     * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults
+     */
+    public List rayTest(Vector3f from, Vector3f to) {
+        List results = new LinkedList();
+        rayTest(from, to, results);
+        return (List<PhysicsRayTestResult>) results;
+    }
+
+    /**
+     * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults
+     */
+    public List<PhysicsRayTestResult> rayTest(Vector3f from, Vector3f to, List<PhysicsRayTestResult> results) {
+        results.clear();
+        rayTest_native(from, to, physicsSpaceId, results);
+        return results;
+    }
+
+    public native void rayTest_native(Vector3f from, Vector3f to, long physicsSpaceId, List<PhysicsRayTestResult> results);
+
+//    private class InternalRayListener extends CollisionWorld.RayResultCallback {
+//
+//        private List<PhysicsRayTestResult> results;
+//
+//        public InternalRayListener(List<PhysicsRayTestResult> results) {
+//            this.results = results;
+//        }
+//
+//        @Override
+//        public float addSingleResult(LocalRayResult lrr, boolean bln) {
+//            PhysicsCollisionObject obj = (PhysicsCollisionObject) lrr.collisionObject.getUserPointer();
+//            results.add(new PhysicsRayTestResult(obj, Converter.convert(lrr.hitNormalLocal), lrr.hitFraction, bln));
+//            return lrr.hitFraction;
+//        }
+//    }
+    /**
+     * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults<br/>
+     * You have to use different Transforms for start and end (at least distance > 0.4f).
+     * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center.
+     */
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end) {
+        List<PhysicsSweepTestResult> results = new LinkedList<PhysicsSweepTestResult>();
+//        if (!(shape.getCShape() instanceof ConvexShape)) {
+//            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
+//            return results;
+//        }
+//        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
+        return results;
+
+    }
+
+    /**
+     * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults<br/>
+     * You have to use different Transforms for start and end (at least distance > 0.4f).
+     * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center.
+     */
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end, List<PhysicsSweepTestResult> results) {
+        results.clear();
+//        if (!(shape.getCShape() instanceof ConvexShape)) {
+//            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
+//            return results;
+//        }
+//        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
+        return results;
+    }
+
+//    private class InternalSweepListener extends CollisionWorld.ConvexResultCallback {
+//
+//        private List<PhysicsSweepTestResult> results;
+//
+//        public InternalSweepListener(List<PhysicsSweepTestResult> results) {
+//            this.results = results;
+//        }
+//
+//        @Override
+//        public float addSingleResult(LocalConvexResult lcr, boolean bln) {
+//            PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer();
+//            results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln));
+//            return lcr.hitFraction;
+//        }
+//    }
+    /**
+     * destroys the current PhysicsSpace so that a new one can be created
+     */
+    public void destroy() {
+        physicsNodes.clear();
+        physicsJoints.clear();
+
+//        dynamicsWorld.destroy();
+//        dynamicsWorld = null;
+    }
+
+    /**
+    //     * used internally
+    //     * @return the dynamicsWorld
+    //     */
+    public long getSpaceId() {
+        return physicsSpaceId;
+    }
+
+    public BroadphaseType getBroadphaseType() {
+        return broadphaseType;
+    }
+
+    public void setBroadphaseType(BroadphaseType broadphaseType) {
+        this.broadphaseType = broadphaseType;
+    }
+
+    /**
+     * Sets the maximum amount of extra steps that will be used to step the physics
+     * when the fps is below the physics fps. Doing this maintains determinism in physics.
+     * For example a maximum number of 2 can compensate for framerates as low as 30fps
+     * when the physics has the default accuracy of 60 fps. Note that setting this
+     * value too high can make the physics drive down its own fps in case its overloaded.
+     * @param steps The maximum number of extra steps, default is 4.
+     */
+    public void setMaxSubSteps(int steps) {
+        maxSubSteps = steps;
+    }
+
+    /**
+     * get the current accuracy of the physics computation
+     * @return the current accuracy
+     */
+    public float getAccuracy() {
+        return accuracy;
+    }
+
+    /**
+     * sets the accuracy of the physics computation, default=1/60s<br>
+     * @param accuracy
+     */
+    public void setAccuracy(float accuracy) {
+        this.accuracy = accuracy;
+    }
+
+    public Vector3f getWorldMin() {
+        return worldMin;
+    }
+
+    /**
+     * only applies for AXIS_SWEEP broadphase
+     * @param worldMin
+     */
+    public void setWorldMin(Vector3f worldMin) {
+        this.worldMin.set(worldMin);
+    }
+
+    public Vector3f getWorldMax() {
+        return worldMax;
+    }
+
+    /**
+     * only applies for AXIS_SWEEP broadphase
+     * @param worldMax
+     */
+    public void setWorldMax(Vector3f worldMax) {
+        this.worldMax.set(worldMax);
+    }
+
+    /**
+     * Enable debug display for physics
+     * @param manager AssetManager to use to create debug materials
+     */
+    public void enableDebug(AssetManager manager) {
+        debugManager = manager;
+    }
+
+    /**
+     * Disable debug display
+     */
+    public void disableDebug() {
+        debugManager = null;
+    }
+
+    public AssetManager getDebugManager() {
+        return debugManager;
+    }
+
+    public static native void initNativePhysics();
+
+    /**
+     * interface with Broadphase types
+     */
+    public enum BroadphaseType {
+
+        /**
+         * basic Broadphase
+         */
+        SIMPLE,
+        /**
+         * better Broadphase, needs worldBounds , max Object number = 16384
+         */
+        AXIS_SWEEP_3,
+        /**
+         * better Broadphase, needs worldBounds , max Object number = 65536
+         */
+        AXIS_SWEEP_3_32,
+        /**
+         * Broadphase allowing quicker adding/removing of physics objects
+         */
+        DBVT;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing PhysicsSpace {0}", Long.toHexString(physicsSpaceId));
+        finalizeNative(physicsSpaceId);
+    }
+
+    private native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java
new file mode 100644
index 0000000..32c506c
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.util.EventObject;
+
+/**
+ * A CollisionEvent stores all information about a collision in the PhysicsWorld.
+ * Do not store this Object, as it will be reused after the collision() method has been called.
+ * Get/reference all data you need in the collide method.
+ * @author normenhansen
+ */
+public class PhysicsCollisionEvent extends EventObject {
+
+    public static final int TYPE_ADDED = 0;
+    public static final int TYPE_PROCESSED = 1;
+    public static final int TYPE_DESTROYED = 2;
+    private int type;
+    private PhysicsCollisionObject nodeA;
+    private PhysicsCollisionObject nodeB;
+    private long manifoldPointObjectId = 0;
+
+    public PhysicsCollisionEvent(int type, PhysicsCollisionObject nodeA, PhysicsCollisionObject nodeB, long manifoldPointObjectId) {
+        super(nodeA);
+        this.manifoldPointObjectId = manifoldPointObjectId;
+    }
+    
+    /**
+     * used by event factory, called when event is destroyed
+     */
+    public void clean() {
+        source = null;
+        this.type = 0;
+        this.nodeA = null;
+        this.nodeB = null;
+        this.manifoldPointObjectId = 0;
+    }
+
+    /**
+     * used by event factory, called when event reused
+     */
+    public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) {
+        this.source = source;
+        this.type = type;
+        this.nodeA = source;
+        this.nodeB = nodeB;
+        this.manifoldPointObjectId = manifoldPointObjectId;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial
+     */
+    public Spatial getNodeA() {
+        if (nodeA.getUserObject() instanceof Spatial) {
+            return (Spatial) nodeA.getUserObject();
+        }
+        return null;
+    }
+
+    /**
+     * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial
+     */
+    public Spatial getNodeB() {
+        if (nodeB.getUserObject() instanceof Spatial) {
+            return (Spatial) nodeB.getUserObject();
+        }
+        return null;
+    }
+
+    public PhysicsCollisionObject getObjectA() {
+        return nodeA;
+    }
+
+    public PhysicsCollisionObject getObjectB() {
+        return nodeB;
+    }
+
+    public float getAppliedImpulse() {
+        return getAppliedImpulse(manifoldPointObjectId);
+    }
+    private native float getAppliedImpulse(long manifoldPointObjectId);
+
+    public float getAppliedImpulseLateral1() {
+        return getAppliedImpulseLateral1(manifoldPointObjectId);
+    }
+    private native float getAppliedImpulseLateral1(long manifoldPointObjectId);
+
+    public float getAppliedImpulseLateral2() {
+        return getAppliedImpulseLateral2(manifoldPointObjectId);
+    }
+    private native float getAppliedImpulseLateral2(long manifoldPointObjectId);
+
+    public float getCombinedFriction() {
+        return getCombinedFriction(manifoldPointObjectId);
+    }
+    private native float getCombinedFriction(long manifoldPointObjectId);
+
+    public float getCombinedRestitution() {
+        return getCombinedRestitution(manifoldPointObjectId);
+    }
+    private native float getCombinedRestitution(long manifoldPointObjectId);
+
+    public float getDistance1() {
+        return getDistance1(manifoldPointObjectId);
+    }
+    private native float getDistance1(long manifoldPointObjectId);
+
+    public int getIndex0() {
+        return getIndex0(manifoldPointObjectId);
+    }
+    private native int getIndex0(long manifoldPointObjectId);
+
+    public int getIndex1() {
+        return getIndex1(manifoldPointObjectId);
+    }
+    private native int getIndex1(long manifoldPointObjectId);
+
+    public Vector3f getLateralFrictionDir1() {
+        return getLateralFrictionDir1(new Vector3f());
+    }
+
+    public Vector3f getLateralFrictionDir1(Vector3f lateralFrictionDir1) {
+        getLateralFrictionDir1(manifoldPointObjectId, lateralFrictionDir1);
+        return lateralFrictionDir1;
+    }
+    private native void getLateralFrictionDir1(long manifoldPointObjectId, Vector3f lateralFrictionDir1);
+
+    public Vector3f getLateralFrictionDir2() {
+        return getLateralFrictionDir2(new Vector3f());
+    }
+
+    public Vector3f getLateralFrictionDir2(Vector3f lateralFrictionDir2) {
+        getLateralFrictionDir2(manifoldPointObjectId, lateralFrictionDir2);
+        return lateralFrictionDir2;
+    }
+    private native void getLateralFrictionDir2(long manifoldPointObjectId, Vector3f lateralFrictionDir2);
+
+    public boolean isLateralFrictionInitialized() {
+        return isLateralFrictionInitialized(manifoldPointObjectId);
+    }
+    private native boolean isLateralFrictionInitialized(long manifoldPointObjectId);
+
+    public int getLifeTime() {
+        return getLifeTime(manifoldPointObjectId);
+    }
+    private native int getLifeTime(long manifoldPointObjectId);
+
+    public Vector3f getLocalPointA() {
+        return getLocalPointA(new Vector3f());
+    }
+    
+    public Vector3f getLocalPointA(Vector3f localPointA) {
+        getLocalPointA(manifoldPointObjectId, localPointA);
+        return localPointA;
+    }
+    private native void getLocalPointA(long manifoldPointObjectId, Vector3f localPointA);
+
+    public Vector3f getLocalPointB() {
+        return getLocalPointB(new Vector3f());
+    }
+    
+    public Vector3f getLocalPointB(Vector3f localPointB) {
+        getLocalPointB(manifoldPointObjectId, localPointB);
+        return localPointB;
+    }
+    private native void getLocalPointB(long manifoldPointObjectId, Vector3f localPointB);
+
+    public Vector3f getNormalWorldOnB() {
+        return getNormalWorldOnB(new Vector3f());
+    }
+
+    public Vector3f getNormalWorldOnB(Vector3f normalWorldOnB) {
+        getNormalWorldOnB(manifoldPointObjectId, normalWorldOnB);
+        return normalWorldOnB;
+    }
+    private native void getNormalWorldOnB(long manifoldPointObjectId, Vector3f normalWorldOnB);
+
+    public int getPartId0() {
+        return getPartId0(manifoldPointObjectId);
+    }
+    private native int getPartId0(long manifoldPointObjectId);
+
+    public int getPartId1() {
+        return getPartId1(manifoldPointObjectId);
+    }
+
+    private native int getPartId1(long manifoldPointObjectId);
+
+    public Vector3f getPositionWorldOnA() {
+        return getPositionWorldOnA(new Vector3f());
+    }
+
+    public Vector3f getPositionWorldOnA(Vector3f positionWorldOnA) {
+        getPositionWorldOnA(positionWorldOnA);
+        return positionWorldOnA;
+    }
+    private native void getPositionWorldOnA(long manifoldPointObjectId, Vector3f positionWorldOnA);
+
+    public Vector3f getPositionWorldOnB() {
+        return getPositionWorldOnB(new Vector3f());
+    }
+
+    public Vector3f getPositionWorldOnB(Vector3f positionWorldOnB) {
+        getPositionWorldOnB(manifoldPointObjectId, positionWorldOnB);
+        return positionWorldOnB;
+    }
+    private native void getPositionWorldOnB(long manifoldPointObjectId, Vector3f positionWorldOnB);
+
+//    public Object getUserPersistentData() {
+//        return userPersistentData;
+//    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java
new file mode 100644
index 0000000..4d88fc0
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class PhysicsCollisionEventFactory {
+
+    private ConcurrentLinkedQueue<PhysicsCollisionEvent> eventBuffer = new ConcurrentLinkedQueue<PhysicsCollisionEvent>();
+
+    public PhysicsCollisionEvent getEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) {
+        PhysicsCollisionEvent event = eventBuffer.poll();
+        if (event == null) {
+            event = new PhysicsCollisionEvent(type, source, nodeB, manifoldPointObjectId);
+        }else{
+            event.refactor(type, source, nodeB, manifoldPointObjectId);
+        }
+        return event;
+    }
+
+    public void recycle(PhysicsCollisionEvent event) {
+        event.clean();
+        eventBuffer.add(event);
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionObject.java b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionObject.java
new file mode 100644
index 0000000..5e1d65e
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/PhysicsCollisionObject.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.util.DebugShapeFactory;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Base class for collision objects (PhysicsRigidBody, PhysicsGhostObject)
+ * @author normenhansen
+ */
+public abstract class PhysicsCollisionObject implements Savable {
+
+    protected long objectId = 0;
+    protected Spatial debugShape;
+    protected Arrow debugArrow;
+    protected Geometry debugArrowGeom;
+    protected Material debugMaterialBlue;
+    protected Material debugMaterialRed;
+    protected Material debugMaterialGreen;
+    protected Material debugMaterialYellow;
+    protected CollisionShape collisionShape;
+    public static final int COLLISION_GROUP_NONE = 0x00000000;
+    public static final int COLLISION_GROUP_01 = 0x00000001;
+    public static final int COLLISION_GROUP_02 = 0x00000002;
+    public static final int COLLISION_GROUP_03 = 0x00000004;
+    public static final int COLLISION_GROUP_04 = 0x00000008;
+    public static final int COLLISION_GROUP_05 = 0x00000010;
+    public static final int COLLISION_GROUP_06 = 0x00000020;
+    public static final int COLLISION_GROUP_07 = 0x00000040;
+    public static final int COLLISION_GROUP_08 = 0x00000080;
+    public static final int COLLISION_GROUP_09 = 0x00000100;
+    public static final int COLLISION_GROUP_10 = 0x00000200;
+    public static final int COLLISION_GROUP_11 = 0x00000400;
+    public static final int COLLISION_GROUP_12 = 0x00000800;
+    public static final int COLLISION_GROUP_13 = 0x00001000;
+    public static final int COLLISION_GROUP_14 = 0x00002000;
+    public static final int COLLISION_GROUP_15 = 0x00004000;
+    public static final int COLLISION_GROUP_16 = 0x00008000;
+    protected int collisionGroup = 0x00000001;
+    protected int collisionGroupsMask = 0x00000001;
+    private Object userObject;
+
+    /**
+     * Sets a CollisionShape to this physics object, note that the object should
+     * not be in the physics space when adding a new collision shape as it is rebuilt
+     * on the physics side.
+     * @param collisionShape the CollisionShape to set
+     */
+    public void setCollisionShape(CollisionShape collisionShape) {
+        this.collisionShape = collisionShape;
+        updateDebugShape();
+    }
+
+    /**
+     * @return the CollisionShape of this PhysicsNode, to be able to reuse it with
+     * other physics nodes (increases performance)
+     */
+    public CollisionShape getCollisionShape() {
+        return collisionShape;
+    }
+
+    /**
+     * Returns the collision group for this collision shape
+     * @return
+     */
+    public int getCollisionGroup() {
+        return collisionGroup;
+    }
+
+    /**
+     * Sets the collision group number for this physics object. <br>
+     * The groups are integer bit masks and some pre-made variables are available in CollisionObject.
+     * All physics objects are by default in COLLISION_GROUP_01.<br>
+     * Two object will collide when <b>one</b> of the partys has the
+     * collisionGroup of the other in its collideWithGroups set.
+     * @param collisionGroup the collisionGroup to set
+     */
+    public void setCollisionGroup(int collisionGroup) {
+        this.collisionGroup = collisionGroup;
+        if (objectId != 0) {
+            setCollisionGroup(objectId, collisionGroup);
+        }
+    }
+
+    /**
+     * Add a group that this object will collide with.<br>
+     * Two object will collide when <b>one</b> of the partys has the
+     * collisionGroup of the other in its collideWithGroups set.<br>
+     * @param collisionGroup
+     */
+    public void addCollideWithGroup(int collisionGroup) {
+        this.collisionGroupsMask = this.collisionGroupsMask | collisionGroup;
+        if (objectId != 0) {
+            setCollideWithGroups(objectId, this.collisionGroupsMask);
+        }
+    }
+
+    /**
+     * Remove a group from the list this object collides with.
+     * @param collisionGroup
+     */
+    public void removeCollideWithGroup(int collisionGroup) {
+        this.collisionGroupsMask = this.collisionGroupsMask & ~collisionGroup;
+        if (objectId != 0) {
+            setCollideWithGroups(this.collisionGroupsMask);
+        }
+    }
+
+    /**
+     * Directly set the bitmask for collision groups that this object collides with.
+     * @param collisionGroup
+     */
+    public void setCollideWithGroups(int collisionGroups) {
+        this.collisionGroupsMask = collisionGroups;
+        if (objectId != 0) {
+            setCollideWithGroups(objectId, this.collisionGroupsMask);
+        }
+    }
+
+    /**
+     * Gets the bitmask of collision groups that this object collides with.
+     * @return
+     */
+    public int getCollideWithGroups() {
+        return collisionGroupsMask;
+    }
+
+    protected void initUserPointer() {
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "initUserPointer() objectId = {0}", Long.toHexString(objectId));
+        initUserPointer(objectId, collisionGroup, collisionGroupsMask);
+    }
+    native void initUserPointer(long objectId, int group, int groups);
+    /**
+     * Creates a visual debug shape of the current collision shape of this physics object<br/>
+     * <b>Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging</b>
+     * @param manager AssetManager to load the default wireframe material for the debug shape
+     */
+    protected Spatial attachDebugShape(AssetManager manager) {
+        debugMaterialBlue = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialBlue.getAdditionalRenderState().setWireframe(true);
+        debugMaterialBlue.setColor("Color", ColorRGBA.Blue);
+        debugMaterialGreen = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialGreen.getAdditionalRenderState().setWireframe(true);
+        debugMaterialGreen.setColor("Color", ColorRGBA.Green);
+        debugMaterialRed = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialRed.getAdditionalRenderState().setWireframe(true);
+        debugMaterialRed.setColor("Color", ColorRGBA.Red);
+        debugMaterialYellow = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialYellow.getAdditionalRenderState().setWireframe(true);
+        debugMaterialYellow.setColor("Color", ColorRGBA.Yellow);
+        debugArrow = new Arrow(Vector3f.UNIT_XYZ);
+        debugArrowGeom = new Geometry("DebugArrow", debugArrow);
+        debugArrowGeom.setMaterial(debugMaterialGreen);
+        return attachDebugShape();
+    }
+    
+    /**
+     * creates a debug shape for this CollisionObject
+     * @param manager
+     * @return 
+     */
+    public Spatial createDebugShape(AssetManager manager){
+        return attachDebugShape(manager);
+    }
+
+    protected Spatial attachDebugShape(Material material) {
+        debugMaterialBlue = material;
+        debugMaterialGreen = material;
+        debugMaterialRed = material;
+        debugMaterialYellow = material;
+        debugArrow = new Arrow(Vector3f.UNIT_XYZ);
+        debugArrowGeom = new Geometry("DebugArrow", debugArrow);
+        debugArrowGeom.setMaterial(debugMaterialGreen);
+        return attachDebugShape();
+    }
+
+    public Spatial debugShape() {
+        return debugShape;
+    }
+
+    /**
+     * Creates a visual debug shape of the current collision shape of this physics object<br/>
+     * <b>Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging</b>
+     * @param material Material to use for the debug shape
+     */
+    protected Spatial attachDebugShape() {
+        if (debugShape != null) {
+            detachDebugShape();
+        }
+        Spatial spatial = getDebugShape();
+        this.debugShape = spatial;
+        return debugShape;
+    }
+
+    protected void updateDebugShape() {
+        if (debugShape != null) {
+            detachDebugShape();
+            attachDebugShape();
+        }
+    }
+
+    protected Spatial getDebugShape() {
+        Spatial spatial = DebugShapeFactory.getDebugShape(collisionShape);
+        if (spatial == null) {
+            return new Node("nullnode");
+        }
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it1 = children.iterator(); it1.hasNext();) {
+                Spatial spatial1 = it1.next();
+                Geometry geom = ((Geometry) spatial1);
+                geom.setMaterial(debugMaterialBlue);
+                geom.setCullHint(Spatial.CullHint.Never);
+            }
+        } else {
+            Geometry geom = ((Geometry) spatial);
+            geom.setMaterial(debugMaterialBlue);
+            geom.setCullHint(Spatial.CullHint.Never);
+        }
+        spatial.setCullHint(Spatial.CullHint.Never);
+        return spatial;
+    }
+
+    /**
+     * Removes the debug shape
+     */
+    public void detachDebugShape() {
+        debugShape = null;
+    }
+
+    /**
+     * @return the userObject
+     */
+    public Object getUserObject() {
+        return userObject;
+    }
+
+    /**
+     * @param userObject the userObject to set
+     */
+    public void setUserObject(Object userObject) {
+        this.userObject = userObject;
+    }
+    
+    public long getObjectId(){
+        return objectId;
+    }
+    
+    protected native void attachCollisionShape(long objectId, long collisionShapeId);
+    native void setCollisionGroup(long objectId, int collisionGroup);
+    native void setCollideWithGroups(long objectId, int collisionGroups);
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(collisionGroup, "collisionGroup", 0x00000001);
+        capsule.write(collisionGroupsMask, "collisionGroupsMask", 0x00000001);
+        capsule.write(debugShape, "debugShape", null);
+        capsule.write(collisionShape, "collisionShape", null);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        collisionGroup = capsule.readInt("collisionGroup", 0x00000001);
+        collisionGroupsMask = capsule.readInt("collisionGroupsMask", 0x00000001);
+        debugShape = (Spatial) capsule.readSavable("debugShape", null);
+        CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null);
+        collisionShape = shape;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing CollisionObject {0}", Long.toHexString(objectId));
+        finalizeNative(objectId);
+    }
+
+    protected native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/PhysicsRayTestResult.java b/engine/src/bullet/com/jme3/bullet/collision/PhysicsRayTestResult.java
new file mode 100644
index 0000000..d4b092d
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/PhysicsRayTestResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.math.Vector3f;
+
+/**
+ * Contains the results of a PhysicsSpace rayTest
+ *  bulletAppState.getPhysicsSpace().rayTest(new Vector3f(0,1000,0),new Vector3f(0,-1000,0));
+    javap -s java.util.List
+ * @author Empire-Phoenix,normenhansen
+ */
+public class PhysicsRayTestResult {
+
+    private PhysicsCollisionObject collisionObject;
+    private Vector3f hitNormalLocal;
+    private float hitFraction;
+    private boolean normalInWorldSpace = true;
+
+    /**
+     * allocated by native code only
+     */
+    private PhysicsRayTestResult() {
+    }
+
+    /**
+     * @return the collisionObject
+     */
+    public PhysicsCollisionObject getCollisionObject() {
+        return collisionObject;
+    }
+
+    /**
+     * @return the hitNormalLocal
+     */
+    public Vector3f getHitNormalLocal() {
+        return hitNormalLocal;
+    }
+
+    /**
+     * @return the hitFraction
+     */
+    public float getHitFraction() {
+        return hitFraction;
+    }
+
+    /**
+     * @return the normalInWorldSpace
+     */
+    public boolean isNormalInWorldSpace() {
+        return normalInWorldSpace;
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java b/engine/src/bullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java
new file mode 100644
index 0000000..d513204
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.math.Vector3f;
+
+/**
+ * Contains the results of a PhysicsSpace rayTest
+ * @author normenhansen
+ */
+public class PhysicsSweepTestResult {
+
+    private PhysicsCollisionObject collisionObject;
+    private Vector3f hitNormalLocal;
+    private float hitFraction;
+    private boolean normalInWorldSpace;
+
+    public PhysicsSweepTestResult() {
+    }
+
+    public PhysicsSweepTestResult(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+
+    /**
+     * @return the collisionObject
+     */
+    public PhysicsCollisionObject getCollisionObject() {
+        return collisionObject;
+    }
+
+    /**
+     * @return the hitNormalLocal
+     */
+    public Vector3f getHitNormalLocal() {
+        return hitNormalLocal;
+    }
+
+    /**
+     * @return the hitFraction
+     */
+    public float getHitFraction() {
+        return hitFraction;
+    }
+
+    /**
+     * @return the normalInWorldSpace
+     */
+    public boolean isNormalInWorldSpace() {
+        return normalInWorldSpace;
+    }
+
+    public void fill(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java
new file mode 100644
index 0000000..bfcf287
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic box collision shape
+ * @author normenhansen
+ */
+public class BoxCollisionShape extends CollisionShape {
+
+    private Vector3f halfExtents;
+
+    public BoxCollisionShape() {
+    }
+
+    /**
+     * creates a collision box from the given halfExtents
+     * @param halfExtents the halfExtents of the CollisionBox
+     */
+    public BoxCollisionShape(Vector3f halfExtents) {
+        this.halfExtents = halfExtents;
+        createShape();
+    }
+
+    public final Vector3f getHalfExtents() {
+        return halfExtents;
+    }
+    
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1));
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        Vector3f halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(1, 1, 1));
+        this.halfExtents = halfExtents;
+        createShape();
+    }
+
+    protected void createShape() {
+        objectId = createShape(halfExtents);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        cShape = new BoxShape(Converter.convert(halfExtents));
+        setScale(scale);
+        setMargin(margin);
+    }
+    
+    private native long createShape(Vector3f halfExtents);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java
new file mode 100644
index 0000000..2f5ccc5
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic capsule collision shape
+ * @author normenhansen
+ */
+public class CapsuleCollisionShape extends CollisionShape{
+    protected float radius,height;
+    protected int axis;
+
+    public CapsuleCollisionShape() {
+    }
+
+    /**
+     * creates a new CapsuleCollisionShape with the given radius and height
+     * @param radius the radius of the capsule
+     * @param height the height of the capsule
+     */
+    public CapsuleCollisionShape(float radius, float height) {
+        this.radius=radius;
+        this.height=height;
+        this.axis=1;
+        createShape();
+    }
+
+    /**
+     * creates a capsule shape around the given axis (0=X,1=Y,2=Z)
+     * @param radius
+     * @param height
+     * @param axis
+     */
+    public CapsuleCollisionShape(float radius, float height, int axis) {
+        this.radius=radius;
+        this.height=height;
+        this.axis=axis;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public float getHeight() {
+        return height;
+    }
+
+    public int getAxis() {
+        return axis;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+        capsule.write(height, "height", 1);
+        capsule.write(axis, "axis", 1);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        height = capsule.readFloat("height", 0.5f);
+        axis = capsule.readInt("axis", 1);
+        createShape();
+    }
+
+    protected void createShape(){
+        objectId = createShape(axis, radius, height);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+        setScale(scale);
+        setMargin(margin);
+//        switch(axis){
+//            case 0:
+//                objectId=new CapsuleShapeX(radius,height);
+//            break;
+//            case 1:
+//                objectId=new CapsuleShape(radius,height);
+//            break;
+//            case 2:
+//                objectId=new CapsuleShapeZ(radius,height);
+//            break;
+//        }
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+    }
+    
+    private native long createShape(int axis, float radius, float height);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/CollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/CollisionShape.java
new file mode 100644
index 0000000..23a90d9
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/CollisionShape.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This Object holds information about a jbullet CollisionShape to be able to reuse
+ * CollisionShapes (as suggested in bullet manuals)
+ * TODO: add static methods to create shapes from nodes (like jbullet-jme constructor)
+ * @author normenhansen
+ */
+public abstract class CollisionShape implements Savable {
+
+    protected long objectId = 0;
+    protected Vector3f scale = new Vector3f(1, 1, 1);
+    protected float margin = 0.0f;
+
+    public CollisionShape() {
+    }
+
+//    /**
+//     * used internally, not safe
+//     */
+//    public void calculateLocalInertia(long objectId, float mass) {
+//        if (this.objectId == 0) {
+//            return;
+//        }
+////        if (this instanceof MeshCollisionShape) {
+////            vector.set(0, 0, 0);
+////        } else {
+//        calculateLocalInertia(objectId, this.objectId, mass);
+////            objectId.calculateLocalInertia(mass, vector);
+////        }
+//    }
+//    
+//    private native void calculateLocalInertia(long objectId, long shapeId, float mass);
+
+    /**
+     * used internally
+     */
+    public long getObjectId() {
+        return objectId;
+    }
+
+    /**
+     * used internally
+     */
+    public void setObjectId(long id) {
+        this.objectId = id;
+    }
+
+    public void setScale(Vector3f scale) {
+        this.scale.set(scale);
+        setLocalScaling(objectId, scale);
+    }
+    
+    public Vector3f getScale() {
+        return scale;
+    }
+
+    public float getMargin() {
+        return getMargin(objectId);
+    }
+    
+    private native float getMargin(long objectId);
+
+    public void setMargin(float margin) {
+        setMargin(objectId, margin);
+        this.margin = margin;
+    }
+    
+    private native void setLocalScaling(long obectId, Vector3f scale);
+    
+    private native void setMargin(long objectId, float margin);
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(scale, "scale", new Vector3f(1, 1, 1));
+        capsule.write(getMargin(), "margin", 0.0f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1));
+        this.margin = capsule.readFloat("margin", 0.0f);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing CollisionShape {0}", Long.toHexString(objectId));
+        finalizeNative(objectId);
+    }
+
+    private native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java
new file mode 100644
index 0000000..ee4d86d
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A CompoundCollisionShape allows combining multiple base shapes
+ * to generate a more sophisticated shape.
+ * @author normenhansen
+ */
+public class CompoundCollisionShape extends CollisionShape {
+
+    protected ArrayList<ChildCollisionShape> children = new ArrayList<ChildCollisionShape>();
+
+    public CompoundCollisionShape() {
+        objectId = createShape();//new CompoundShape();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+    }
+
+    /**
+     * adds a child shape at the given local translation
+     * @param shape the child shape to add
+     * @param location the local location of the child shape
+     */
+    public void addChildShape(CollisionShape shape, Vector3f location) {
+//        Transform transA = new Transform(Converter.convert(new Matrix3f()));
+//        Converter.convert(location, transA.origin);
+//        children.add(new ChildCollisionShape(location.clone(), new Matrix3f(), shape));
+//        ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId());
+        addChildShape(shape, location, new Matrix3f());
+    }
+
+    /**
+     * adds a child shape at the given local translation
+     * @param shape the child shape to add
+     * @param location the local location of the child shape
+     */
+    public void addChildShape(CollisionShape shape, Vector3f location, Matrix3f rotation) {
+        if(shape instanceof CompoundCollisionShape){
+            throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!");
+        }
+//        Transform transA = new Transform(Converter.convert(rotation));
+//        Converter.convert(location, transA.origin);
+//        Converter.convert(rotation, transA.basis);
+        children.add(new ChildCollisionShape(location.clone(), rotation.clone(), shape));
+        addChildShape(objectId, shape.getObjectId(), location, rotation);
+//        ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId());
+    }
+
+    private void addChildShapeDirect(CollisionShape shape, Vector3f location, Matrix3f rotation) {
+        if(shape instanceof CompoundCollisionShape){
+            throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!");
+        }
+//        Transform transA = new Transform(Converter.convert(rotation));
+//        Converter.convert(location, transA.origin);
+//        Converter.convert(rotation, transA.basis);
+        addChildShape(objectId, shape.getObjectId(), location, rotation);
+//        ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId());
+    }
+
+    /**
+     * removes a child shape
+     * @param shape the child shape to remove
+     */
+    public void removeChildShape(CollisionShape shape) {
+        removeChildShape(objectId, shape.getObjectId());
+//        ((CompoundShape) objectId).removeChildShape(shape.getObjectId());
+        for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+            ChildCollisionShape childCollisionShape = it.next();
+            if (childCollisionShape.shape == shape) {
+                it.remove();
+            }
+        }
+    }
+
+    public List<ChildCollisionShape> getChildren() {
+        return children;
+    }
+
+    /**
+     * WARNING - CompoundCollisionShape scaling has no effect.
+     */
+    @Override
+    public void setScale(Vector3f scale) {
+        Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CompoundCollisionShape cannot be scaled");
+    }
+
+    private native long createShape();
+    
+    private native long addChildShape(long objectId, long childId, Vector3f location, Matrix3f rotation);
+    
+    private native long removeChildShape(long objectId, long childId);
+    
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.writeSavableArrayList(children, "children", new ArrayList<ChildCollisionShape>());
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        children = capsule.readSavableArrayList("children", new ArrayList<ChildCollisionShape>());
+        setScale(scale);
+        setMargin(margin);
+        loadChildren();
+    }
+
+    private void loadChildren() {
+        for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+            ChildCollisionShape child = it.next();
+            addChildShapeDirect(child.shape, child.location, child.rotation);
+        }
+    }
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
new file mode 100644
index 0000000..f01b585
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
@@ -0,0 +1,81 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision.shapes;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class ConeCollisionShape extends CollisionShape {
+
+    protected float radius;
+    protected float height;
+    protected int axis;
+
+    public ConeCollisionShape() {
+    }
+
+    public ConeCollisionShape(float radius, float height, int axis) {
+        this.radius = radius;
+        this.height = radius;
+        this.axis = axis;
+        createShape();
+    }
+
+    public ConeCollisionShape(float radius, float height) {
+        this.radius = radius;
+        this.height = radius;
+        this.axis = PhysicsSpace.AXIS_Y;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+        capsule.write(height, "height", 0.5f);
+        capsule.write(axis, "axis", 0.5f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        radius = capsule.readFloat("height", 0.5f);
+        radius = capsule.readFloat("axis", 0.5f);
+        createShape();
+    }
+
+    protected void createShape() {
+        objectId = createShape(axis, radius, height);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        if (axis == PhysicsSpace.AXIS_X) {
+//            objectId = new ConeShapeX(radius, height);
+//        } else if (axis == PhysicsSpace.AXIS_Y) {
+//            objectId = new ConeShape(radius, height);
+//        } else if (axis == PhysicsSpace.AXIS_Z) {
+//            objectId = new ConeShapeZ(radius, height);
+//        }
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+        setScale(scale);
+        setMargin(margin);
+    }
+
+    private native long createShape(int axis, float radius, float height);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java
new file mode 100644
index 0000000..fa651df
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic cylinder collision shape
+ * @author normenhansen
+ */
+public class CylinderCollisionShape extends CollisionShape {
+
+    protected Vector3f halfExtents;
+    protected int axis;
+
+    public CylinderCollisionShape() {
+    }
+
+    /**
+     * creates a cylinder shape from the given halfextents
+     * @param halfExtents the halfextents to use
+     */
+    public CylinderCollisionShape(Vector3f halfExtents) {
+        this.halfExtents = halfExtents;
+        this.axis = 2;
+        createShape();
+    }
+
+    /**
+     * Creates a cylinder shape around the given axis from the given halfextents
+     * @param halfExtents the halfextents to use
+     * @param axis (0=X,1=Y,2=Z)
+     */
+    public CylinderCollisionShape(Vector3f halfExtents, int axis) {
+        this.halfExtents = halfExtents;
+        this.axis = axis;
+        createShape();
+    }
+
+    public final Vector3f getHalfExtents() {
+        return halfExtents;
+    }
+
+    public int getAxis() {
+        return axis;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(halfExtents, "halfExtents", new Vector3f(0.5f, 0.5f, 0.5f));
+        capsule.write(axis, "axis", 1);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(0.5f, 0.5f, 0.5f));
+        axis = capsule.readInt("axis", 1);
+        createShape();
+    }
+
+    protected void createShape() {
+        objectId = createShape(axis, halfExtents);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        switch (axis) {
+//            case 0:
+//                objectId = new CylinderShapeX(Converter.convert(halfExtents));
+//                break;
+//            case 1:
+//                objectId = new CylinderShape(Converter.convert(halfExtents));
+//                break;
+//            case 2:
+//                objectId = new CylinderShapeZ(Converter.convert(halfExtents));
+//                break;
+//        }
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+        setScale(scale);
+        setMargin(margin);
+    }
+    
+    private native long createShape(int axis, Vector3f halfExtents);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
new file mode 100644
index 0000000..49478b4
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
@@ -0,0 +1,168 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;

+

+import com.jme3.bullet.util.NativeMeshUtil;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.BufferUtils;

+import java.io.IOException;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * Basic mesh collision shape

+ * @author normenhansen

+ */

+public class GImpactCollisionShape extends CollisionShape {

+

+//    protected Vector3f worldScale;

+    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;

+    protected ByteBuffer triangleIndexBase, vertexBase;

+    protected long meshId = 0;

+//    protected IndexedMesh bulletMesh;

+

+    public GImpactCollisionShape() {

+    }

+

+    /**

+     * creates a collision shape from the given Mesh

+     * @param mesh the Mesh to use

+     */

+    public GImpactCollisionShape(Mesh mesh) {

+        createCollisionMesh(mesh);

+    }

+

+    private void createCollisionMesh(Mesh mesh) {

+        triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);

+        vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); 

+//        triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4);

+//        vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4);

+        numVertices = mesh.getVertexCount();

+        vertexStride = 12; //3 verts * 4 bytes per.

+        numTriangles = mesh.getTriangleCount();

+        triangleIndexStride = 12; //3 index entries * 4 bytes each.

+

+        IndexBuffer indices = mesh.getIndexBuffer();

+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);

+        vertices.rewind();

+

+        int verticesLength = mesh.getVertexCount() * 3;

+        for (int i = 0; i < verticesLength; i++) {

+            float tempFloat = vertices.get();

+            vertexBase.putFloat(tempFloat);

+        }

+

+        int indicesLength = mesh.getTriangleCount() * 3;

+        for (int i = 0; i < indicesLength; i++) {

+            triangleIndexBase.putInt(indices.get(i));

+        }

+        vertices.rewind();

+        vertices.clear();

+

+        createShape();

+    }

+

+//    /**

+//     * creates a jme mesh from the collision shape, only needed for debugging

+//     */

+//    public Mesh createJmeMesh() {

+//        return Converter.convert(bulletMesh);

+//    }

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule capsule = ex.getCapsule(this);

+//        capsule.write(worldScale, "worldScale", new Vector3f(1, 1, 1));

+        capsule.write(numVertices, "numVertices", 0);

+        capsule.write(numTriangles, "numTriangles", 0);

+        capsule.write(vertexStride, "vertexStride", 0);

+        capsule.write(triangleIndexStride, "triangleIndexStride", 0);

+

+        capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);

+        capsule.write(vertexBase.array(), "vertexBase", new byte[0]);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule capsule = im.getCapsule(this);

+//        worldScale = (Vector3f) capsule.readSavable("worldScale", new Vector3f(1, 1, 1));

+        numVertices = capsule.readInt("numVertices", 0);

+        numTriangles = capsule.readInt("numTriangles", 0);

+        vertexStride = capsule.readInt("vertexStride", 0);

+        triangleIndexStride = capsule.readInt("triangleIndexStride", 0);

+

+        triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));

+        vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0]));

+        createShape();

+    }

+

+    protected void createShape() {

+//        bulletMesh = new IndexedMesh();

+//        bulletMesh.numVertices = numVertices;

+//        bulletMesh.numTriangles = numTriangles;

+//        bulletMesh.vertexStride = vertexStride;

+//        bulletMesh.triangleIndexStride = triangleIndexStride;

+//        bulletMesh.triangleIndexBase = triangleIndexBase;

+//        bulletMesh.vertexBase = vertexBase;

+//        bulletMesh.triangleIndexBase = triangleIndexBase;

+//        TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);

+//        objectId = new GImpactMeshShape(tiv);

+//        objectId.setLocalScaling(Converter.convert(worldScale));

+//        ((GImpactMeshShape)objectId).updateBound();

+//        objectId.setLocalScaling(Converter.convert(getScale()));

+//        objectId.setMargin(margin);

+        meshId = NativeMeshUtil.createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Mesh {0}", Long.toHexString(meshId));

+        objectId = createShape(meshId);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));

+        setScale(scale);

+        setMargin(margin);

+    }

+

+    private native long createShape(long meshId);

+

+    @Override

+    protected void finalize() throws Throwable {

+        super.finalize();

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Mesh {0}", Long.toHexString(meshId));

+        finalizeNative(meshId);

+    }

+

+    private native void finalizeNative(long objectId);

+}

diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
new file mode 100644
index 0000000..5bef2e3
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
@@ -0,0 +1,145 @@
+/*

+ * To change this template, choose Tools | Templates

+ * and open the template in the editor.

+ */

+package com.jme3.bullet.collision.shapes;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Mesh;

+import com.jme3.util.BufferUtils;

+import java.io.IOException;

+import java.nio.ByteBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster

+ * than using a regular mesh.

+ * There are a couple tricks though:

+ *	-No rotation or translation is supported.

+ *	-The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being

+ *	equal on either side. If not, the whole collision box is shifted vertically and things don't collide

+ *	as they should.

+ * 

+ * @author Brent Owens

+ */

+public class HeightfieldCollisionShape extends CollisionShape {

+

+    protected int heightStickWidth;

+    protected int heightStickLength;

+    protected float[] heightfieldData;

+    protected float heightScale;

+    protected float minHeight;

+    protected float maxHeight;

+    protected int upAxis;

+    protected boolean flipQuadEdges;

+    protected ByteBuffer bbuf;

+//    protected FloatBuffer fbuf;

+

+    public HeightfieldCollisionShape() {

+    }

+

+    public HeightfieldCollisionShape(float[] heightmap) {

+        createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ);

+    }

+

+    public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) {

+        createCollisionHeightfield(heightmap, scale);

+    }

+

+    protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) {

+        this.scale = worldScale;

+        this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale

+

+        this.heightfieldData = heightmap;

+

+        float min = heightfieldData[0];

+        float max = heightfieldData[0];

+        // calculate min and max height

+        for (int i = 0; i < heightfieldData.length; i++) {

+            if (heightfieldData[i] < min) {

+                min = heightfieldData[i];

+            }

+            if (heightfieldData[i] > max) {

+                max = heightfieldData[i];

+            }

+        }

+        // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the

+        // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect.

+        if (max < 0) {

+            max = -min;

+        } else {

+            if (Math.abs(max) > Math.abs(min)) {

+                min = -max;

+            } else {

+                max = -min;

+            }

+        }

+        this.minHeight = min;

+        this.maxHeight = max;

+

+        this.upAxis = 1;

+        this.flipQuadEdges = false;

+

+        heightStickWidth = (int) FastMath.sqrt(heightfieldData.length);

+        heightStickLength = heightStickWidth;

+

+

+        createShape();

+    }

+

+    protected void createShape() {

+        bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4); 

+//        fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData);

+//        fbuf.rewind();

+//        fbuf.put(heightfieldData);

+        for (int i = 0; i < heightfieldData.length; i++) {

+            float f = heightfieldData[i];

+            bbuf.putFloat(f);

+        }

+//        fbuf.rewind();

+        objectId = createShape(heightStickWidth, heightStickLength, bbuf, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));

+        setScale(scale);

+        setMargin(margin);

+    }

+

+    private native long createShape(int heightStickWidth, int heightStickLength, ByteBuffer heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, boolean flipQuadEdges);

+

+    public Mesh createJmeMesh() {

+        //TODO return Converter.convert(bulletMesh);

+        return null;

+    }

+

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule capsule = ex.getCapsule(this);

+        capsule.write(heightStickWidth, "heightStickWidth", 0);

+        capsule.write(heightStickLength, "heightStickLength", 0);

+        capsule.write(heightScale, "heightScale", 0);

+        capsule.write(minHeight, "minHeight", 0);

+        capsule.write(maxHeight, "maxHeight", 0);

+        capsule.write(upAxis, "upAxis", 1);

+        capsule.write(heightfieldData, "heightfieldData", new float[0]);

+        capsule.write(flipQuadEdges, "flipQuadEdges", false);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule capsule = im.getCapsule(this);

+        heightStickWidth = capsule.readInt("heightStickWidth", 0);

+        heightStickLength = capsule.readInt("heightStickLength", 0);

+        heightScale = capsule.readFloat("heightScale", 0);

+        minHeight = capsule.readFloat("minHeight", 0);

+        maxHeight = capsule.readFloat("maxHeight", 0);

+        upAxis = capsule.readInt("upAxis", 1);

+        heightfieldData = capsule.readFloatArray("heightfieldData", new float[0]);

+        flipQuadEdges = capsule.readBoolean("flipQuadEdges", false);

+        createShape();

+    }

+}

diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
new file mode 100644
index 0000000..9be760f
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
@@ -0,0 +1,98 @@
+package com.jme3.bullet.collision.shapes;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.util.BufferUtils;

+import java.io.IOException;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class HullCollisionShape extends CollisionShape {

+

+    private float[] points;

+//    protected FloatBuffer fbuf;

+

+    public HullCollisionShape() {

+    }

+

+    public HullCollisionShape(Mesh mesh) {

+        this.points = getPoints(mesh);

+        createShape();

+    }

+

+    public HullCollisionShape(float[] points) {

+        this.points = points;

+        createShape();

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+

+        OutputCapsule capsule = ex.getCapsule(this);

+        capsule.write(points, "points", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule capsule = im.getCapsule(this);

+

+        // for backwards compatability

+        Mesh mesh = (Mesh) capsule.readSavable("hullMesh", null);

+        if (mesh != null) {

+            this.points = getPoints(mesh);

+        } else {

+            this.points = capsule.readFloatArray("points", null);

+

+        }

+//        fbuf = ByteBuffer.allocateDirect(points.length * 4).asFloatBuffer();

+//        fbuf.put(points);

+//        fbuf = FloatBuffer.wrap(points).order(ByteOrder.nativeOrder()).asFloatBuffer();

+        createShape();

+    }

+

+    protected void createShape() {

+//        ObjectArrayList<Vector3f> pointList = new ObjectArrayList<Vector3f>();

+//        for (int i = 0; i < points.length; i += 3) {

+//            pointList.add(new Vector3f(points[i], points[i + 1], points[i + 2]));

+//        }

+//        objectId = new ConvexHullShape(pointList);

+//        objectId.setLocalScaling(Converter.convert(getScale()));

+//        objectId.setMargin(margin);

+        ByteBuffer bbuf=BufferUtils.createByteBuffer(points.length * 4); 

+//        fbuf = bbuf.asFloatBuffer();

+//        fbuf.rewind();

+//        fbuf.put(points);

+        for (int i = 0; i < points.length; i++) {

+            float f = points[i];

+            bbuf.putFloat(f);

+        }

+        bbuf.rewind();

+        objectId = createShape(bbuf);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));

+        setScale(scale);

+        setMargin(margin);

+    }

+

+    private native long createShape(ByteBuffer points);

+

+    protected float[] getPoints(Mesh mesh) {

+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);

+        vertices.rewind();

+        int components = mesh.getVertexCount() * 3;

+        float[] pointsArray = new float[components];

+        for (int i = 0; i < components; i += 3) {

+            pointsArray[i] = vertices.get();

+            pointsArray[i + 1] = vertices.get();

+            pointsArray[i + 2] = vertices.get();

+        }

+        return pointsArray;

+    }

+}

diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
new file mode 100644
index 0000000..c13bbd1
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
@@ -0,0 +1,161 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;

+

+import com.jme3.bullet.util.NativeMeshUtil;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.BufferUtils;

+import java.io.IOException;

+import java.nio.ByteBuffer;

+import java.nio.ByteOrder;

+import java.nio.FloatBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * Basic mesh collision shape

+ * @author normenhansen

+ */

+public class MeshCollisionShape extends CollisionShape {

+

+    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;

+    protected ByteBuffer triangleIndexBase, vertexBase;

+    protected long meshId = 0;

+

+    public MeshCollisionShape() {

+    }

+

+    /**

+     * creates a collision shape from the given TriMesh

+     * @param mesh the TriMesh to use

+     */

+    public MeshCollisionShape(Mesh mesh) {

+        createCollisionMesh(mesh);

+    }

+

+    private void createCollisionMesh(Mesh mesh) {

+        triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);

+        vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);

+        numVertices = mesh.getVertexCount();

+        vertexStride = 12; //3 verts * 4 bytes per.

+        numTriangles = mesh.getTriangleCount();

+        triangleIndexStride = 12; //3 index entries * 4 bytes each.

+

+        IndexBuffer indices = mesh.getIndexBuffer();

+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);

+        vertices.rewind();

+

+        int verticesLength = mesh.getVertexCount() * 3;

+        for (int i = 0; i < verticesLength; i++) {

+            float tempFloat = vertices.get();

+            vertexBase.putFloat(tempFloat);

+        }

+

+        int indicesLength = mesh.getTriangleCount() * 3;

+        for (int i = 0; i < indicesLength; i++) {

+            triangleIndexBase.putInt(indices.get(i));

+        }

+        vertices.rewind();

+        vertices.clear();

+

+        createShape();

+    }

+

+    /**

+     * creates a jme mesh from the collision shape, only needed for debugging

+     */

+//    public Mesh createJmeMesh(){

+//        return Converter.convert(bulletMesh);

+//    }

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule capsule = ex.getCapsule(this);

+        capsule.write(numVertices, "numVertices", 0);

+        capsule.write(numTriangles, "numTriangles", 0);

+        capsule.write(vertexStride, "vertexStride", 0);

+        capsule.write(triangleIndexStride, "triangleIndexStride", 0);

+

+        capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);

+        capsule.write(vertexBase.array(), "vertexBase", new byte[0]);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule capsule = im.getCapsule(this);

+        numVertices = capsule.readInt("numVertices", 0);

+        numTriangles = capsule.readInt("numTriangles", 0);

+        vertexStride = capsule.readInt("vertexStride", 0);

+        triangleIndexStride = capsule.readInt("triangleIndexStride", 0);

+

+        triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));

+        vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])).order(ByteOrder.nativeOrder());

+        createShape();

+    }

+

+    protected void createShape() {

+//        bulletMesh = new IndexedMesh();

+//        bulletMesh.numVertices = numVertices;

+//        bulletMesh.numTriangles = numTriangles;

+//        bulletMesh.vertexStride = vertexStride;

+//        bulletMesh.triangleIndexStride = triangleIndexStride;

+//        bulletMesh.triangleIndexBase = triangleIndexBase;

+//        bulletMesh.vertexBase = vertexBase;

+//        bulletMesh.triangleIndexBase = triangleIndexBase;

+//        TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);

+//        objectId = new BvhTriangleMeshShape(tiv, true);

+//        objectId.setLocalScaling(Converter.convert(getScale()));

+//        objectId.setMargin(margin);

+        meshId = NativeMeshUtil.createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Mesh {0}", Long.toHexString(meshId));

+        objectId = createShape(meshId);

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));

+        setScale(scale);

+        setMargin(margin);

+    }

+

+    private native long createShape(long meshId);

+

+    @Override

+    protected void finalize() throws Throwable {

+        super.finalize();

+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Mesh {0}", Long.toHexString(meshId));

+        finalizeNative(meshId);

+    }

+

+    private native void finalizeNative(long objectId);

+}

diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java
new file mode 100644
index 0000000..3e949bd
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java
@@ -0,0 +1,66 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class PlaneCollisionShape extends CollisionShape{
+    private Plane plane;
+
+    public PlaneCollisionShape() {
+    }
+
+    /**
+     * Creates a plane Collision shape
+     * @param plane the plane that defines the shape
+     */
+    public PlaneCollisionShape(Plane plane) {
+        this.plane = plane;
+        createShape();
+    }
+
+    public final Plane getPlane() {
+        return plane;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(plane, "collisionPlane", new Plane());
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        plane = (Plane) capsule.readSavable("collisionPlane", new Plane());
+        createShape();
+    }
+
+    protected void createShape() {
+        objectId = createShape(plane.getNormal(), plane.getConstant());
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        objectId = new StaticPlaneShape(Converter.convert(plane.getNormal()),plane.getConstant());
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+        setScale(scale);
+        setMargin(margin);
+    }
+    
+    private native long createShape(Vector3f normal, float constant);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java
new file mode 100644
index 0000000..cef33d7
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java
@@ -0,0 +1,100 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple point, line, triangle or quad collisionShape based on one to four points-
+ * @author normenhansen
+ */
+public class SimplexCollisionShape extends CollisionShape {
+
+    private Vector3f vector1, vector2, vector3, vector4;
+
+    public SimplexCollisionShape() {
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3, Vector3f point4) {
+        vector1 = point1;
+        vector2 = point2;
+        vector3 = point3;
+        vector4 = point4;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3) {
+        vector1 = point1;
+        vector2 = point2;
+        vector3 = point3;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2) {
+        vector1 = point1;
+        vector2 = point2;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1) {
+        vector1 = point1;
+        createShape();
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(vector1, "simplexPoint1", null);
+        capsule.write(vector2, "simplexPoint2", null);
+        capsule.write(vector3, "simplexPoint3", null);
+        capsule.write(vector4, "simplexPoint4", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        vector1 = (Vector3f) capsule.readSavable("simplexPoint1", null);
+        vector2 = (Vector3f) capsule.readSavable("simplexPoint2", null);
+        vector3 = (Vector3f) capsule.readSavable("simplexPoint3", null);
+        vector4 = (Vector3f) capsule.readSavable("simplexPoint4", null);
+        createShape();
+    }
+
+    protected void createShape() {
+        if (vector4 != null) {
+            objectId = createShape(vector1, vector2, vector3, vector4);
+//            objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3), Converter.convert(vector4));
+        } else if (vector3 != null) {
+            objectId = createShape(vector1, vector2, vector3);
+//            objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3));
+        } else if (vector2 != null) {
+            objectId = createShape(vector1, vector2);
+//            objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2));
+        } else {
+            objectId = createShape(vector1);
+//            objectId = new BU_Simplex1to4(Converter.convert(vector1));
+        }
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+        setScale(scale);
+        setMargin(margin);
+    }
+    
+    private native long createShape(Vector3f vector1);
+    
+    private native long createShape(Vector3f vector1, Vector3f vector2);
+    
+    private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3);
+
+    private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3, Vector3f vector4);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/engine/src/bullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java
new file mode 100644
index 0000000..2ccd816
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic sphere collision shape
+ * @author normenhansen
+ */
+public class SphereCollisionShape extends CollisionShape {
+
+    protected float radius;
+
+    public SphereCollisionShape() {
+    }
+
+    /**
+     * creates a SphereCollisionShape with the given radius
+     * @param radius
+     */
+    public SphereCollisionShape(float radius) {
+        this.radius = radius;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        createShape();
+    }
+
+    protected void createShape() {
+        objectId = createShape(radius);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
+//        new SphereShape(radius);
+//        objectId.setLocalScaling(Converter.convert(getScale()));
+//        objectId.setMargin(margin);
+        setScale(scale);
+        setMargin(margin);
+    }
+    
+    private native long createShape(float radius);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/ConeJoint.java b/engine/src/bullet/com/jme3/bullet/joints/ConeJoint.java
new file mode 100644
index 0000000..fc803ff
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/ConeJoint.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * To create ragdolls, the conve twist constraint is very useful for limbs like the upper arm.
+ * It is a special point to point constraint that adds cone and twist axis limits.
+ * The x-axis serves as twist axis.
+ * @author normenhansen
+ */
+public class ConeJoint extends PhysicsJoint {
+
+    protected Matrix3f rotA, rotB;
+    protected float swingSpan1 = 1e30f;
+    protected float swingSpan2 = 1e30f;
+    protected float twistSpan = 1e30f;
+    protected boolean angularOnly = false;
+
+    public ConeJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = new Matrix3f();
+        this.rotB = new Matrix3f();
+        createJoint();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = rotA;
+        this.rotB = rotB;
+        createJoint();
+    }
+
+    public void setLimit(float swingSpan1, float swingSpan2, float twistSpan) {
+        this.swingSpan1 = swingSpan1;
+        this.swingSpan2 = swingSpan2;
+        this.twistSpan = twistSpan;
+        setLimit(objectId, swingSpan1, swingSpan2, twistSpan);
+    }
+
+    private native void setLimit(long objectId, float swingSpan1, float swingSpan2, float twistSpan);
+
+    public void setAngularOnly(boolean value) {
+        angularOnly = value;
+        setAngularOnly(objectId, value);
+    }
+
+    private native void setAngularOnly(long objectId, boolean value);
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(rotA, "rotA", new Matrix3f());
+        capsule.write(rotB, "rotB", new Matrix3f());
+
+        capsule.write(angularOnly, "angularOnly", false);
+        capsule.write(swingSpan1, "swingSpan1", 1e30f);
+        capsule.write(swingSpan2, "swingSpan2", 1e30f);
+        capsule.write(twistSpan, "twistSpan", 1e30f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        this.rotA = (Matrix3f) capsule.readSavable("rotA", new Matrix3f());
+        this.rotB = (Matrix3f) capsule.readSavable("rotB", new Matrix3f());
+
+        this.angularOnly = capsule.readBoolean("angularOnly", false);
+        this.swingSpan1 = capsule.readFloat("swingSpan1", 1e30f);
+        this.swingSpan2 = capsule.readFloat("swingSpan2", 1e30f);
+        this.twistSpan = capsule.readFloat("twistSpan", 1e30f);
+        createJoint();
+    }
+
+    protected void createJoint() {
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+        setLimit(objectId, swingSpan1, swingSpan2, twistSpan);
+        setAngularOnly(objectId, angularOnly);
+    }
+
+    private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/HingeJoint.java b/engine/src/bullet/com/jme3/bullet/joints/HingeJoint.java
new file mode 100644
index 0000000..93769d9
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/HingeJoint.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * Hinge constraint, or revolute joint restricts two additional angular degrees of freedom,
+ * so the body can only rotate around one axis, the hinge axis.
+ * This can be useful to represent doors or wheels rotating around one axis.
+ * The user can specify limits and motor for the hinge.
+ * @author normenhansen
+ */
+public class HingeJoint extends PhysicsJoint {
+
+    protected Vector3f axisA;
+    protected Vector3f axisB;
+    protected boolean angularOnly = false;
+    protected float biasFactor = 0.3f;
+    protected float relaxationFactor = 1.0f;
+    protected float limitSoftness = 0.9f;
+
+    public HingeJoint() {
+    }
+
+    /**
+     * Creates a new HingeJoint
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public HingeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Vector3f axisA, Vector3f axisB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.axisA = axisA;
+        this.axisB = axisB;
+        createJoint();
+    }
+
+    public void enableMotor(boolean enable, float targetVelocity, float maxMotorImpulse) {
+        enableMotor(objectId, enable, targetVelocity, maxMotorImpulse);
+    }
+
+    private native void enableMotor(long objectId, boolean enable, float targetVelocity, float maxMotorImpulse);
+
+    public boolean getEnableMotor() {
+        return getEnableAngularMotor(objectId);
+    }
+
+    private native boolean getEnableAngularMotor(long objectId);
+
+    public float getMotorTargetVelocity() {
+        return getMotorTargetVelocity(objectId);
+    }
+
+    private native float getMotorTargetVelocity(long objectId);
+
+    public float getMaxMotorImpulse() {
+        return getMaxMotorImpulse(objectId);
+    }
+
+    private native float getMaxMotorImpulse(long objectId);
+
+    public void setLimit(float low, float high) {
+        setLimit(objectId, low, high);
+    }
+
+    private native void setLimit(long objectId, float low, float high);
+
+    public void setLimit(float low, float high, float _softness, float _biasFactor, float _relaxationFactor) {
+        biasFactor = _biasFactor;
+        relaxationFactor = _relaxationFactor;
+        limitSoftness = _softness;
+        setLimit(objectId, low, high, _softness, _biasFactor, _relaxationFactor);
+    }
+
+    private native void setLimit(long objectId, float low, float high, float _softness, float _biasFactor, float _relaxationFactor);
+
+    public float getUpperLimit() {
+        return getUpperLimit(objectId);
+    }
+
+    private native float getUpperLimit(long objectId);
+
+    public float getLowerLimit() {
+        return getLowerLimit(objectId);
+    }
+
+    private native float getLowerLimit(long objectId);
+
+    public void setAngularOnly(boolean angularOnly) {
+        this.angularOnly = angularOnly;
+        setAngularOnly(objectId, angularOnly);
+    }
+
+    private native void setAngularOnly(long objectId, boolean angularOnly);
+
+    public float getHingeAngle() {
+        return getHingeAngle(objectId);
+    }
+
+    private native float getHingeAngle(long objectId);
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(axisA, "axisA", new Vector3f());
+        capsule.write(axisB, "axisB", new Vector3f());
+
+        capsule.write(angularOnly, "angularOnly", false);
+
+        capsule.write(getLowerLimit(), "lowerLimit", 1e30f);
+        capsule.write(getUpperLimit(), "upperLimit", -1e30f);
+
+        capsule.write(biasFactor, "biasFactor", 0.3f);
+        capsule.write(relaxationFactor, "relaxationFactor", 1f);
+        capsule.write(limitSoftness, "limitSoftness", 0.9f);
+
+        capsule.write(getEnableMotor(), "enableAngularMotor", false);
+        capsule.write(getMotorTargetVelocity(), "targetVelocity", 0.0f);
+        capsule.write(getMaxMotorImpulse(), "maxMotorImpulse", 0.0f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        this.axisA = (Vector3f) capsule.readSavable("axisA", new Vector3f());
+        this.axisB = (Vector3f) capsule.readSavable("axisB", new Vector3f());
+
+        this.angularOnly = capsule.readBoolean("angularOnly", false);
+        float lowerLimit = capsule.readFloat("lowerLimit", 1e30f);
+        float upperLimit = capsule.readFloat("upperLimit", -1e30f);
+
+        this.biasFactor = capsule.readFloat("biasFactor", 0.3f);
+        this.relaxationFactor = capsule.readFloat("relaxationFactor", 1f);
+        this.limitSoftness = capsule.readFloat("limitSoftness", 0.9f);
+
+        boolean enableAngularMotor = capsule.readBoolean("enableAngularMotor", false);
+        float targetVelocity = capsule.readFloat("targetVelocity", 0.0f);
+        float maxMotorImpulse = capsule.readFloat("maxMotorImpulse", 0.0f);
+
+        createJoint();
+        enableMotor(enableAngularMotor, targetVelocity, maxMotorImpulse);
+        setLimit(lowerLimit, upperLimit, limitSoftness, biasFactor, relaxationFactor);
+    }
+
+    protected void createJoint() {
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, axisA, pivotB, axisB);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+    }
+
+    private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f axisA, Vector3f pivotB, Vector3f axisB);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/PhysicsJoint.java b/engine/src/bullet/com/jme3/bullet/joints/PhysicsJoint.java
new file mode 100644
index 0000000..ecf517c
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/PhysicsJoint.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsJoint - Basic Phyiscs Joint</p>
+ * @author normenhansen
+ */
+public abstract class PhysicsJoint implements Savable {
+
+    protected long objectId = 0;
+    protected PhysicsRigidBody nodeA;
+    protected PhysicsRigidBody nodeB;
+    protected Vector3f pivotA;
+    protected Vector3f pivotB;
+    protected boolean collisionBetweenLinkedBodys = true;
+
+    public PhysicsJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public PhysicsJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        this.nodeA = nodeA;
+        this.nodeB = nodeB;
+        this.pivotA = pivotA;
+        this.pivotB = pivotB;
+        nodeA.addJoint(this);
+        nodeB.addJoint(this);
+    }
+
+    public float getAppliedImpulse() {
+        return getAppliedImpulse(objectId);
+    }
+
+    private native float getAppliedImpulse(long objectId);
+
+    /**
+     * @return the constraint
+     */
+    public long getObjectId() {
+        return objectId;
+    }
+
+    /**
+     * @return the collisionBetweenLinkedBodys
+     */
+    public boolean isCollisionBetweenLinkedBodys() {
+        return collisionBetweenLinkedBodys;
+    }
+
+    /**
+     * toggles collisions between linked bodys<br>
+     * joint has to be removed from and added to PhyiscsSpace to apply this.
+     * @param collisionBetweenLinkedBodys set to false to have no collisions between linked bodys
+     */
+    public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodys) {
+        this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodys;
+    }
+
+    public PhysicsRigidBody getBodyA() {
+        return nodeA;
+    }
+
+    public PhysicsRigidBody getBodyB() {
+        return nodeB;
+    }
+
+    public Vector3f getPivotA() {
+        return pivotA;
+    }
+
+    public Vector3f getPivotB() {
+        return pivotB;
+    }
+
+    /**
+     * destroys this joint and removes it from its connected PhysicsRigidBodys joint lists
+     */
+    public void destroy() {
+        getBodyA().removeJoint(this);
+        getBodyB().removeJoint(this);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(nodeA, "nodeA", null);
+        capsule.write(nodeB, "nodeB", null);
+        capsule.write(pivotA, "pivotA", null);
+        capsule.write(pivotB, "pivotB", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        this.nodeA = ((PhysicsRigidBody) capsule.readSavable("nodeA", new PhysicsRigidBody()));
+        this.nodeB = (PhysicsRigidBody) capsule.readSavable("nodeB", new PhysicsRigidBody());
+        this.pivotA = (Vector3f) capsule.readSavable("pivotA", new Vector3f());
+        this.pivotB = (Vector3f) capsule.readSavable("pivotB", new Vector3f());
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Joint {0}", Long.toHexString(objectId));
+        finalizeNative(objectId);
+    }
+
+    private native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/Point2PointJoint.java b/engine/src/bullet/com/jme3/bullet/joints/Point2PointJoint.java
new file mode 100644
index 0000000..15f6e3a
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/Point2PointJoint.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * Point to point constraint, also known as ball socket joint limits the translation
+ * so that the local pivot points of 2 rigidbodies match in worldspace.
+ * A chain of rigidbodies can be connected using this constraint.
+ * @author normenhansen
+ */
+public class Point2PointJoint extends PhysicsJoint {
+
+    public Point2PointJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public Point2PointJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        createJoint();
+    }
+
+    public void setDamping(float value) {
+        setDamping(objectId, value);
+    }
+
+    private native void setDamping(long objectId, float value);
+
+    public void setImpulseClamp(float value) {
+        setImpulseClamp(objectId, value);
+    }
+
+    private native void setImpulseClamp(long objectId, float value);
+
+    public void setTau(float value) {
+        setTau(objectId, value);
+    }
+
+    private native void setTau(long objectId, float value);
+
+    public float getDamping() {
+        return getDamping(objectId);
+    }
+
+    private native float getDamping(long objectId);
+
+    public float getImpulseClamp() {
+        return getImpulseClamp(objectId);
+    }
+
+    private native float getImpulseClamp(long objectId);
+
+    public float getTau() {
+        return getTau(objectId);
+    }
+
+    private native float getTau(long objectId);
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule cap = ex.getCapsule(this);
+        cap.write(getDamping(), "damping", 1.0f);
+        cap.write(getTau(), "tau", 0.3f);
+        cap.write(getImpulseClamp(), "impulseClamp", 0f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        createJoint();
+        InputCapsule cap = im.getCapsule(this);
+        setDamping(cap.readFloat("damping", 1.0f));
+        setDamping(cap.readFloat("tau", 0.3f));
+        setDamping(cap.readFloat("impulseClamp", 0f));
+    }
+
+    protected void createJoint() {
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, pivotB);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+    }
+
+    private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f pivotB);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/SixDofJoint.java b/engine/src/bullet/com/jme3/bullet/joints/SixDofJoint.java
new file mode 100644
index 0000000..2760322
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/SixDofJoint.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.joints.motors.RotationalLimitMotor;
+import com.jme3.bullet.joints.motors.TranslationalLimitMotor;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * This generic constraint can emulate a variety of standard constraints,
+ * by configuring each of the 6 degrees of freedom (dof).
+ * The first 3 dof axis are linear axis, which represent translation of rigidbodies,
+ * and the latter 3 dof axis represent the angular motion. Each axis can be either locked,
+ * free or limited. On construction of a new btGeneric6DofConstraint, all axis are locked.
+ * Afterwards the axis can be reconfigured. Note that several combinations that
+ * include free and/or limited angular degrees of freedom are undefined.
+ * @author normenhansen
+ */
+public class SixDofJoint extends PhysicsJoint {
+
+    Matrix3f rotA, rotB;
+    boolean useLinearReferenceFrameA;
+    LinkedList<RotationalLimitMotor> rotationalMotors = new LinkedList<RotationalLimitMotor>();
+    TranslationalLimitMotor translationalMotor;
+    Vector3f angularUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY);
+    Vector3f angularLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+    Vector3f linearUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY);
+    Vector3f linearLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+
+    public SixDofJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+        this.rotA = rotA;
+        this.rotB = rotB;
+
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+        gatherMotors();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+        rotA = new Matrix3f();
+        rotB = new Matrix3f();
+
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+        gatherMotors();
+    }
+
+    private void gatherMotors() {
+        for (int i = 0; i < 3; i++) {
+            RotationalLimitMotor rmot = new RotationalLimitMotor(getRotationalLimitMotor(objectId, i));
+            rotationalMotors.add(rmot);
+        }
+        translationalMotor = new TranslationalLimitMotor(getTranslationalLimitMotor(objectId));
+    }
+
+    private native long getRotationalLimitMotor(long objectId, int index);
+
+    private native long getTranslationalLimitMotor(long objectId);
+
+    /**
+     * returns the TranslationalLimitMotor of this 6DofJoint which allows
+     * manipulating the translational axis
+     * @return the TranslationalLimitMotor
+     */
+    public TranslationalLimitMotor getTranslationalLimitMotor() {
+        return translationalMotor;
+    }
+
+    /**
+     * returns one of the three RotationalLimitMotors of this 6DofJoint which
+     * allow manipulating the rotational axes
+     * @param index the index of the RotationalLimitMotor
+     * @return the RotationalLimitMotor at the given index
+     */
+    public RotationalLimitMotor getRotationalLimitMotor(int index) {
+        return rotationalMotors.get(index);
+    }
+
+    public void setLinearUpperLimit(Vector3f vector) {
+        linearUpperLimit.set(vector);
+        setLinearUpperLimit(objectId, vector);
+    }
+
+    private native void setLinearUpperLimit(long objctId, Vector3f vector);
+
+    public void setLinearLowerLimit(Vector3f vector) {
+        linearLowerLimit.set(vector);
+        setLinearLowerLimit(objectId, vector);
+    }
+
+    private native void setLinearLowerLimit(long objctId, Vector3f vector);
+
+    public void setAngularUpperLimit(Vector3f vector) {
+        angularUpperLimit.set(vector);
+        setAngularUpperLimit(objectId, vector);
+    }
+
+    private native void setAngularUpperLimit(long objctId, Vector3f vector);
+
+    public void setAngularLowerLimit(Vector3f vector) {
+        angularLowerLimit.set(vector);
+        setAngularLowerLimit(objectId, vector);
+    }
+
+    private native void setAngularLowerLimit(long objctId, Vector3f vector);
+
+    native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA);
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+        gatherMotors();
+
+        setAngularUpperLimit((Vector3f) capsule.readSavable("angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)));
+        setAngularLowerLimit((Vector3f) capsule.readSavable("angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)));
+        setLinearUpperLimit((Vector3f) capsule.readSavable("linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)));
+        setLinearLowerLimit((Vector3f) capsule.readSavable("linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)));
+
+        for (int i = 0; i < 3; i++) {
+            RotationalLimitMotor rotationalLimitMotor = getRotationalLimitMotor(i);
+            rotationalLimitMotor.setBounce(capsule.readFloat("rotMotor" + i + "_Bounce", 0.0f));
+            rotationalLimitMotor.setDamping(capsule.readFloat("rotMotor" + i + "_Damping", 1.0f));
+            rotationalLimitMotor.setERP(capsule.readFloat("rotMotor" + i + "_ERP", 0.5f));
+            rotationalLimitMotor.setHiLimit(capsule.readFloat("rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY));
+            rotationalLimitMotor.setLimitSoftness(capsule.readFloat("rotMotor" + i + "_LimitSoftness", 0.5f));
+            rotationalLimitMotor.setLoLimit(capsule.readFloat("rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY));
+            rotationalLimitMotor.setMaxLimitForce(capsule.readFloat("rotMotor" + i + "_MaxLimitForce", 300.0f));
+            rotationalLimitMotor.setMaxMotorForce(capsule.readFloat("rotMotor" + i + "_MaxMotorForce", 0.1f));
+            rotationalLimitMotor.setTargetVelocity(capsule.readFloat("rotMotor" + i + "_TargetVelocity", 0));
+            rotationalLimitMotor.setEnableMotor(capsule.readBoolean("rotMotor" + i + "_EnableMotor", false));
+        }
+        getTranslationalLimitMotor().setAccumulatedImpulse((Vector3f) capsule.readSavable("transMotor_AccumulatedImpulse", Vector3f.ZERO));
+        getTranslationalLimitMotor().setDamping(capsule.readFloat("transMotor_Damping", 1.0f));
+        getTranslationalLimitMotor().setLimitSoftness(capsule.readFloat("transMotor_LimitSoftness", 0.7f));
+        getTranslationalLimitMotor().setLowerLimit((Vector3f) capsule.readSavable("transMotor_LowerLimit", Vector3f.ZERO));
+        getTranslationalLimitMotor().setRestitution(capsule.readFloat("transMotor_Restitution", 0.5f));
+        getTranslationalLimitMotor().setUpperLimit((Vector3f) capsule.readSavable("transMotor_UpperLimit", Vector3f.ZERO));
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(angularUpperLimit, "angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY));
+        capsule.write(angularLowerLimit, "angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY));
+        capsule.write(linearUpperLimit, "linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY));
+        capsule.write(linearLowerLimit, "linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY));
+        int i = 0;
+        for (Iterator<RotationalLimitMotor> it = rotationalMotors.iterator(); it.hasNext();) {
+            RotationalLimitMotor rotationalLimitMotor = it.next();
+            capsule.write(rotationalLimitMotor.getBounce(), "rotMotor" + i + "_Bounce", 0.0f);
+            capsule.write(rotationalLimitMotor.getDamping(), "rotMotor" + i + "_Damping", 1.0f);
+            capsule.write(rotationalLimitMotor.getERP(), "rotMotor" + i + "_ERP", 0.5f);
+            capsule.write(rotationalLimitMotor.getHiLimit(), "rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY);
+            capsule.write(rotationalLimitMotor.getLimitSoftness(), "rotMotor" + i + "_LimitSoftness", 0.5f);
+            capsule.write(rotationalLimitMotor.getLoLimit(), "rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY);
+            capsule.write(rotationalLimitMotor.getMaxLimitForce(), "rotMotor" + i + "_MaxLimitForce", 300.0f);
+            capsule.write(rotationalLimitMotor.getMaxMotorForce(), "rotMotor" + i + "_MaxMotorForce", 0.1f);
+            capsule.write(rotationalLimitMotor.getTargetVelocity(), "rotMotor" + i + "_TargetVelocity", 0);
+            capsule.write(rotationalLimitMotor.isEnableMotor(), "rotMotor" + i + "_EnableMotor", false);
+            i++;
+        }
+        capsule.write(getTranslationalLimitMotor().getAccumulatedImpulse(), "transMotor_AccumulatedImpulse", Vector3f.ZERO);
+        capsule.write(getTranslationalLimitMotor().getDamping(), "transMotor_Damping", 1.0f);
+        capsule.write(getTranslationalLimitMotor().getLimitSoftness(), "transMotor_LimitSoftness", 0.7f);
+        capsule.write(getTranslationalLimitMotor().getLowerLimit(), "transMotor_LowerLimit", Vector3f.ZERO);
+        capsule.write(getTranslationalLimitMotor().getRestitution(), "transMotor_Restitution", 0.5f);
+        capsule.write(getTranslationalLimitMotor().getUpperLimit(), "transMotor_UpperLimit", Vector3f.ZERO);
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/SixDofSpringJoint.java b/engine/src/bullet/com/jme3/bullet/joints/SixDofSpringJoint.java
new file mode 100644
index 0000000..df77f49
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/SixDofSpringJoint.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * This generic constraint can emulate a variety of standard constraints,
+ * by configuring each of the 6 degrees of freedom (dof).
+ * The first 3 dof axis are linear axis, which represent translation of rigidbodies,
+ * and the latter 3 dof axis represent the angular motion. Each axis can be either locked,
+ * free or limited. On construction of a new btGeneric6DofConstraint, all axis are locked.
+ * Afterwards the axis can be reconfigured. Note that several combinations that
+ * include free and/or limited angular degrees of freedom are undefined.
+ * @author normenhansen
+ */
+public class SixDofSpringJoint extends SixDofJoint {
+
+   final boolean       springEnabled[] = new boolean[6];
+   final float equilibriumPoint[] = new float[6];
+   final float springStiffness[] = new float[6];
+   final float springDamping[] = new float[6]; // between 0 and 1 (1 == no damping)
+
+    public SixDofSpringJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SixDofSpringJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB, rotA, rotB, useLinearReferenceFrameA);
+    }
+    public void enableSpring(int index, boolean onOff) {
+        enableSpring(objectId, index, onOff);
+    }
+    native void enableSpring(long objctId, int index, boolean onOff);
+
+    public void setStiffness(int index, float stiffness) {
+        setStiffness(objectId, index, stiffness);
+    }
+    native void setStiffness(long objctId, int index, float stiffness);
+
+    public void setDamping(int index, float damping) {
+        setDamping(objectId, index, damping);
+
+    }
+    native void setDamping(long objctId, int index, float damping);
+    public void setEquilibriumPoint() { // set the current constraint position/orientation as an equilibrium point for all DOF
+        setEquilibriumPoint(objectId);
+    }
+    native void setEquilibriumPoint(long objctId);
+    public void setEquilibriumPoint(int index){ // set the current constraint position/orientation as an equilibrium point for given DOF
+        setEquilibriumPoint(objectId, index);
+    }
+    native void setEquilibriumPoint(long objctId, int index);
+    @Override
+    native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA);
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/SliderJoint.java b/engine/src/bullet/com/jme3/bullet/joints/SliderJoint.java
new file mode 100644
index 0000000..400cd28
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/SliderJoint.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * The slider constraint allows the body to rotate around one axis and translate along this axis.
+ * @author normenhansen
+ */
+public class SliderJoint extends PhysicsJoint {
+
+    protected Matrix3f rotA, rotB;
+    protected boolean useLinearReferenceFrameA;
+
+    public SliderJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = rotA;
+        this.rotB = rotB;
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+        createJoint();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = new Matrix3f();
+        this.rotB = new Matrix3f();
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+        createJoint();
+    }
+
+    public float getLowerLinLimit() {
+        return getLowerLinLimit(objectId);
+    }
+
+    private native float getLowerLinLimit(long objectId);
+
+    public void setLowerLinLimit(float lowerLinLimit) {
+        setLowerLinLimit(objectId, lowerLinLimit);
+    }
+
+    private native void setLowerLinLimit(long objectId, float value);
+
+    public float getUpperLinLimit() {
+        return getUpperLinLimit(objectId);
+    }
+
+    private native float getUpperLinLimit(long objectId);
+
+    public void setUpperLinLimit(float upperLinLimit) {
+        setUpperLinLimit(objectId, upperLinLimit);
+    }
+
+    private native void setUpperLinLimit(long objectId, float value);
+
+    public float getLowerAngLimit() {
+        return getLowerAngLimit(objectId);
+    }
+
+    private native float getLowerAngLimit(long objectId);
+
+    public void setLowerAngLimit(float lowerAngLimit) {
+        setLowerAngLimit(objectId, lowerAngLimit);
+    }
+
+    private native void setLowerAngLimit(long objectId, float value);
+
+    public float getUpperAngLimit() {
+        return getUpperAngLimit(objectId);
+    }
+
+    private native float getUpperAngLimit(long objectId);
+
+    public void setUpperAngLimit(float upperAngLimit) {
+        setUpperAngLimit(objectId, upperAngLimit);
+    }
+
+    private native void setUpperAngLimit(long objectId, float value);
+
+    public float getSoftnessDirLin() {
+        return getSoftnessDirLin(objectId);
+    }
+
+    private native float getSoftnessDirLin(long objectId);
+
+    public void setSoftnessDirLin(float softnessDirLin) {
+        setSoftnessDirLin(objectId, softnessDirLin);
+    }
+
+    private native void setSoftnessDirLin(long objectId, float value);
+
+    public float getRestitutionDirLin() {
+        return getRestitutionDirLin(objectId);
+    }
+
+    private native float getRestitutionDirLin(long objectId);
+
+    public void setRestitutionDirLin(float restitutionDirLin) {
+        setRestitutionDirLin(objectId, restitutionDirLin);
+    }
+
+    private native void setRestitutionDirLin(long objectId, float value);
+
+    public float getDampingDirLin() {
+        return getDampingDirLin(objectId);
+    }
+
+    private native float getDampingDirLin(long objectId);
+
+    public void setDampingDirLin(float dampingDirLin) {
+        setDampingDirLin(objectId, dampingDirLin);
+    }
+
+    private native void setDampingDirLin(long objectId, float value);
+
+    public float getSoftnessDirAng() {
+        return getSoftnessDirAng(objectId);
+    }
+
+    private native float getSoftnessDirAng(long objectId);
+
+    public void setSoftnessDirAng(float softnessDirAng) {
+        setSoftnessDirAng(objectId, softnessDirAng);
+    }
+
+    private native void setSoftnessDirAng(long objectId, float value);
+
+    public float getRestitutionDirAng() {
+        return getRestitutionDirAng(objectId);
+    }
+
+    private native float getRestitutionDirAng(long objectId);
+
+    public void setRestitutionDirAng(float restitutionDirAng) {
+        setRestitutionDirAng(objectId, restitutionDirAng);
+    }
+
+    private native void setRestitutionDirAng(long objectId, float value);
+
+    public float getDampingDirAng() {
+        return getDampingDirAng(objectId);
+    }
+
+    private native float getDampingDirAng(long objectId);
+
+    public void setDampingDirAng(float dampingDirAng) {
+        setDampingDirAng(objectId, dampingDirAng);
+    }
+
+    private native void setDampingDirAng(long objectId, float value);
+
+    public float getSoftnessLimLin() {
+        return getSoftnessLimLin(objectId);
+    }
+
+    private native float getSoftnessLimLin(long objectId);
+
+    public void setSoftnessLimLin(float softnessLimLin) {
+        setSoftnessLimLin(objectId, softnessLimLin);
+    }
+
+    private native void setSoftnessLimLin(long objectId, float value);
+
+    public float getRestitutionLimLin() {
+        return getRestitutionLimLin(objectId);
+    }
+
+    private native float getRestitutionLimLin(long objectId);
+
+    public void setRestitutionLimLin(float restitutionLimLin) {
+        setRestitutionLimLin(objectId, restitutionLimLin);
+    }
+
+    private native void setRestitutionLimLin(long objectId, float value);
+
+    public float getDampingLimLin() {
+        return getDampingLimLin(objectId);
+    }
+
+    private native float getDampingLimLin(long objectId);
+
+    public void setDampingLimLin(float dampingLimLin) {
+        setDampingLimLin(objectId, dampingLimLin);
+    }
+
+    private native void setDampingLimLin(long objectId, float value);
+
+    public float getSoftnessLimAng() {
+        return getSoftnessLimAng(objectId);
+    }
+
+    private native float getSoftnessLimAng(long objectId);
+
+    public void setSoftnessLimAng(float softnessLimAng) {
+        setSoftnessLimAng(objectId, softnessLimAng);
+    }
+
+    private native void setSoftnessLimAng(long objectId, float value);
+
+    public float getRestitutionLimAng() {
+        return getRestitutionLimAng(objectId);
+    }
+
+    private native float getRestitutionLimAng(long objectId);
+
+    public void setRestitutionLimAng(float restitutionLimAng) {
+        setRestitutionLimAng(objectId, restitutionLimAng);
+    }
+
+    private native void setRestitutionLimAng(long objectId, float value);
+
+    public float getDampingLimAng() {
+        return getDampingLimAng(objectId);
+    }
+
+    private native float getDampingLimAng(long objectId);
+
+    public void setDampingLimAng(float dampingLimAng) {
+        setDampingLimAng(objectId, dampingLimAng);
+    }
+
+    private native void setDampingLimAng(long objectId, float value);
+
+    public float getSoftnessOrthoLin() {
+        return getSoftnessOrthoLin(objectId);
+    }
+
+    private native float getSoftnessOrthoLin(long objectId);
+
+    public void setSoftnessOrthoLin(float softnessOrthoLin) {
+        setSoftnessOrthoLin(objectId, softnessOrthoLin);
+    }
+
+    private native void setSoftnessOrthoLin(long objectId, float value);
+
+    public float getRestitutionOrthoLin() {
+        return getRestitutionOrthoLin(objectId);
+    }
+
+    private native float getRestitutionOrthoLin(long objectId);
+
+    public void setRestitutionOrthoLin(float restitutionOrthoLin) {
+        setDampingOrthoLin(objectId, restitutionOrthoLin);
+    }
+
+    private native void setRestitutionOrthoLin(long objectId, float value);
+
+    public float getDampingOrthoLin() {
+        return getDampingOrthoLin(objectId);
+    }
+
+    private native float getDampingOrthoLin(long objectId);
+
+    public void setDampingOrthoLin(float dampingOrthoLin) {
+        setDampingOrthoLin(objectId, dampingOrthoLin);
+    }
+
+    private native void setDampingOrthoLin(long objectId, float value);
+
+    public float getSoftnessOrthoAng() {
+        return getSoftnessOrthoAng(objectId);
+    }
+
+    private native float getSoftnessOrthoAng(long objectId);
+
+    public void setSoftnessOrthoAng(float softnessOrthoAng) {
+        setSoftnessOrthoAng(objectId, softnessOrthoAng);
+    }
+
+    private native void setSoftnessOrthoAng(long objectId, float value);
+
+    public float getRestitutionOrthoAng() {
+        return getRestitutionOrthoAng(objectId);
+    }
+
+    private native float getRestitutionOrthoAng(long objectId);
+
+    public void setRestitutionOrthoAng(float restitutionOrthoAng) {
+        setRestitutionOrthoAng(objectId, restitutionOrthoAng);
+    }
+
+    private native void setRestitutionOrthoAng(long objectId, float value);
+
+    public float getDampingOrthoAng() {
+        return getDampingOrthoAng(objectId);
+    }
+
+    private native float getDampingOrthoAng(long objectId);
+
+    public void setDampingOrthoAng(float dampingOrthoAng) {
+        setDampingOrthoAng(objectId, dampingOrthoAng);
+    }
+
+    private native void setDampingOrthoAng(long objectId, float value);
+
+    public boolean isPoweredLinMotor() {
+        return isPoweredLinMotor(objectId);
+    }
+
+    private native boolean isPoweredLinMotor(long objectId);
+
+    public void setPoweredLinMotor(boolean poweredLinMotor) {
+        setPoweredLinMotor(objectId, poweredLinMotor);
+    }
+
+    private native void setPoweredLinMotor(long objectId, boolean value);
+
+    public float getTargetLinMotorVelocity() {
+        return getTargetLinMotorVelocity(objectId);
+    }
+
+    private native float getTargetLinMotorVelocity(long objectId);
+
+    public void setTargetLinMotorVelocity(float targetLinMotorVelocity) {
+        setTargetLinMotorVelocity(objectId, targetLinMotorVelocity);
+    }
+
+    private native void setTargetLinMotorVelocity(long objectId, float value);
+
+    public float getMaxLinMotorForce() {
+        return getMaxLinMotorForce(objectId);
+    }
+
+    private native float getMaxLinMotorForce(long objectId);
+
+    public void setMaxLinMotorForce(float maxLinMotorForce) {
+        setMaxLinMotorForce(objectId, maxLinMotorForce);
+    }
+
+    private native void setMaxLinMotorForce(long objectId, float value);
+
+    public boolean isPoweredAngMotor() {
+        return isPoweredAngMotor(objectId);
+    }
+
+    private native boolean isPoweredAngMotor(long objectId);
+
+    public void setPoweredAngMotor(boolean poweredAngMotor) {
+        setPoweredAngMotor(objectId, poweredAngMotor);
+    }
+
+    private native void setPoweredAngMotor(long objectId, boolean value);
+
+    public float getTargetAngMotorVelocity() {
+        return getTargetAngMotorVelocity(objectId);
+    }
+
+    private native float getTargetAngMotorVelocity(long objectId);
+
+    public void setTargetAngMotorVelocity(float targetAngMotorVelocity) {
+        setTargetAngMotorVelocity(objectId, targetAngMotorVelocity);
+    }
+
+    private native void setTargetAngMotorVelocity(long objectId, float value);
+
+    public float getMaxAngMotorForce() {
+        return getMaxAngMotorForce(objectId);
+    }
+
+    private native float getMaxAngMotorForce(long objectId);
+
+    public void setMaxAngMotorForce(float maxAngMotorForce) {
+        setMaxAngMotorForce(objectId, maxAngMotorForce);
+    }
+
+    private native void setMaxAngMotorForce(long objectId, float value);
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        //TODO: standard values..
+        capsule.write(getDampingDirAng(), "dampingDirAng", 0f);
+        capsule.write(getDampingDirLin(), "dampingDirLin", 0f);
+        capsule.write(getDampingLimAng(), "dampingLimAng", 0f);
+        capsule.write(getDampingLimLin(), "dampingLimLin", 0f);
+        capsule.write(getDampingOrthoAng(), "dampingOrthoAng", 0f);
+        capsule.write(getDampingOrthoLin(), "dampingOrthoLin", 0f);
+        capsule.write(getLowerAngLimit(), "lowerAngLimit", 0f);
+        capsule.write(getLowerLinLimit(), "lowerLinLimit", 0f);
+        capsule.write(getMaxAngMotorForce(), "maxAngMotorForce", 0f);
+        capsule.write(getMaxLinMotorForce(), "maxLinMotorForce", 0f);
+        capsule.write(isPoweredAngMotor(), "poweredAngMotor", false);
+        capsule.write(isPoweredLinMotor(), "poweredLinMotor", false);
+        capsule.write(getRestitutionDirAng(), "restitutionDirAng", 0f);
+        capsule.write(getRestitutionDirLin(), "restitutionDirLin", 0f);
+        capsule.write(getRestitutionLimAng(), "restitutionLimAng", 0f);
+        capsule.write(getRestitutionLimLin(), "restitutionLimLin", 0f);
+        capsule.write(getRestitutionOrthoAng(), "restitutionOrthoAng", 0f);
+        capsule.write(getRestitutionOrthoLin(), "restitutionOrthoLin", 0f);
+
+        capsule.write(getSoftnessDirAng(), "softnessDirAng", 0f);
+        capsule.write(getSoftnessDirLin(), "softnessDirLin", 0f);
+        capsule.write(getSoftnessLimAng(), "softnessLimAng", 0f);
+        capsule.write(getSoftnessLimLin(), "softnessLimLin", 0f);
+        capsule.write(getSoftnessOrthoAng(), "softnessOrthoAng", 0f);
+        capsule.write(getSoftnessOrthoLin(), "softnessOrthoLin", 0f);
+
+        capsule.write(getTargetAngMotorVelocity(), "targetAngMotorVelicoty", 0f);
+        capsule.write(getTargetLinMotorVelocity(), "targetLinMotorVelicoty", 0f);
+
+        capsule.write(getUpperAngLimit(), "upperAngLimit", 0f);
+        capsule.write(getUpperLinLimit(), "upperLinLimit", 0f);
+
+        capsule.write(useLinearReferenceFrameA, "useLinearReferenceFrameA", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        float dampingDirAng = capsule.readFloat("dampingDirAng", 0f);
+        float dampingDirLin = capsule.readFloat("dampingDirLin", 0f);
+        float dampingLimAng = capsule.readFloat("dampingLimAng", 0f);
+        float dampingLimLin = capsule.readFloat("dampingLimLin", 0f);
+        float dampingOrthoAng = capsule.readFloat("dampingOrthoAng", 0f);
+        float dampingOrthoLin = capsule.readFloat("dampingOrthoLin", 0f);
+        float lowerAngLimit = capsule.readFloat("lowerAngLimit", 0f);
+        float lowerLinLimit = capsule.readFloat("lowerLinLimit", 0f);
+        float maxAngMotorForce = capsule.readFloat("maxAngMotorForce", 0f);
+        float maxLinMotorForce = capsule.readFloat("maxLinMotorForce", 0f);
+        boolean poweredAngMotor = capsule.readBoolean("poweredAngMotor", false);
+        boolean poweredLinMotor = capsule.readBoolean("poweredLinMotor", false);
+        float restitutionDirAng = capsule.readFloat("restitutionDirAng", 0f);
+        float restitutionDirLin = capsule.readFloat("restitutionDirLin", 0f);
+        float restitutionLimAng = capsule.readFloat("restitutionLimAng", 0f);
+        float restitutionLimLin = capsule.readFloat("restitutionLimLin", 0f);
+        float restitutionOrthoAng = capsule.readFloat("restitutionOrthoAng", 0f);
+        float restitutionOrthoLin = capsule.readFloat("restitutionOrthoLin", 0f);
+
+        float softnessDirAng = capsule.readFloat("softnessDirAng", 0f);
+        float softnessDirLin = capsule.readFloat("softnessDirLin", 0f);
+        float softnessLimAng = capsule.readFloat("softnessLimAng", 0f);
+        float softnessLimLin = capsule.readFloat("softnessLimLin", 0f);
+        float softnessOrthoAng = capsule.readFloat("softnessOrthoAng", 0f);
+        float softnessOrthoLin = capsule.readFloat("softnessOrthoLin", 0f);
+
+        float targetAngMotorVelicoty = capsule.readFloat("targetAngMotorVelicoty", 0f);
+        float targetLinMotorVelicoty = capsule.readFloat("targetLinMotorVelicoty", 0f);
+
+        float upperAngLimit = capsule.readFloat("upperAngLimit", 0f);
+        float upperLinLimit = capsule.readFloat("upperLinLimit", 0f);
+
+        useLinearReferenceFrameA = capsule.readBoolean("useLinearReferenceFrameA", false);
+
+        createJoint();
+
+        setDampingDirAng(dampingDirAng);
+        setDampingDirLin(dampingDirLin);
+        setDampingLimAng(dampingLimAng);
+        setDampingLimLin(dampingLimLin);
+        setDampingOrthoAng(dampingOrthoAng);
+        setDampingOrthoLin(dampingOrthoLin);
+        setLowerAngLimit(lowerAngLimit);
+        setLowerLinLimit(lowerLinLimit);
+        setMaxAngMotorForce(maxAngMotorForce);
+        setMaxLinMotorForce(maxLinMotorForce);
+        setPoweredAngMotor(poweredAngMotor);
+        setPoweredLinMotor(poweredLinMotor);
+        setRestitutionDirAng(restitutionDirAng);
+        setRestitutionDirLin(restitutionDirLin);
+        setRestitutionLimAng(restitutionLimAng);
+        setRestitutionLimLin(restitutionLimLin);
+        setRestitutionOrthoAng(restitutionOrthoAng);
+        setRestitutionOrthoLin(restitutionOrthoLin);
+
+        setSoftnessDirAng(softnessDirAng);
+        setSoftnessDirLin(softnessDirLin);
+        setSoftnessLimAng(softnessLimAng);
+        setSoftnessLimLin(softnessLimLin);
+        setSoftnessOrthoAng(softnessOrthoAng);
+        setSoftnessOrthoLin(softnessOrthoLin);
+
+        setTargetAngMotorVelocity(targetAngMotorVelicoty);
+        setTargetLinMotorVelocity(targetLinMotorVelicoty);
+
+        setUpperAngLimit(upperAngLimit);
+        setUpperLinLimit(upperLinLimit);
+    }
+
+    protected void createJoint() {
+        objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Joint {0}", Long.toHexString(objectId));
+        // = new SliderConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA);
+    }
+
+    private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java b/engine/src/bullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java
new file mode 100644
index 0000000..83b397b
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints.motors;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class RotationalLimitMotor {
+
+    private long motorId = 0;
+
+    public RotationalLimitMotor(long motor) {
+        this.motorId = motor;
+    }
+
+    public long getMotor() {
+        return motorId;
+    }
+
+    public float getLoLimit() {
+        return getLoLimit(motorId);
+    }
+
+    private native float getLoLimit(long motorId);
+
+    public void setLoLimit(float loLimit) {
+        setLoLimit(motorId, loLimit);
+    }
+
+    private native void setLoLimit(long motorId, float loLimit);
+
+    public float getHiLimit() {
+        return getHiLimit(motorId);
+    }
+
+    private native float getHiLimit(long motorId);
+
+    public void setHiLimit(float hiLimit) {
+        setHiLimit(motorId, hiLimit);
+    }
+
+    private native void setHiLimit(long motorId, float hiLimit);
+
+    public float getTargetVelocity() {
+        return getTargetVelocity(motorId);
+    }
+
+    private native float getTargetVelocity(long motorId);
+
+    public void setTargetVelocity(float targetVelocity) {
+        setTargetVelocity(motorId, targetVelocity);
+    }
+
+    private native void setTargetVelocity(long motorId, float targetVelocity);
+
+    public float getMaxMotorForce() {
+        return getMaxMotorForce(motorId);
+    }
+
+    private native float getMaxMotorForce(long motorId);
+
+    public void setMaxMotorForce(float maxMotorForce) {
+        setMaxMotorForce(motorId, maxMotorForce);
+    }
+
+    private native void setMaxMotorForce(long motorId, float maxMotorForce);
+
+    public float getMaxLimitForce() {
+        return getMaxLimitForce(motorId);
+    }
+
+    private native float getMaxLimitForce(long motorId);
+
+    public void setMaxLimitForce(float maxLimitForce) {
+        setMaxLimitForce(motorId, maxLimitForce);
+    }
+
+    private native void setMaxLimitForce(long motorId, float maxLimitForce);
+
+    public float getDamping() {
+        return getDamping(motorId);
+    }
+
+    private native float getDamping(long motorId);
+
+    public void setDamping(float damping) {
+        setDamping(motorId, damping);
+    }
+
+    private native void setDamping(long motorId, float damping);
+
+    public float getLimitSoftness() {
+        return getLimitSoftness(motorId);
+    }
+
+    private native float getLimitSoftness(long motorId);
+
+    public void setLimitSoftness(float limitSoftness) {
+        setLimitSoftness(motorId, limitSoftness);
+    }
+
+    private native void setLimitSoftness(long motorId, float limitSoftness);
+
+    public float getERP() {
+        return getERP(motorId);
+    }
+
+    private native float getERP(long motorId);
+
+    public void setERP(float ERP) {
+        setERP(motorId, ERP);
+    }
+
+    private native void setERP(long motorId, float ERP);
+
+    public float getBounce() {
+        return getBounce(motorId);
+    }
+
+    private native float getBounce(long motorId);
+
+    public void setBounce(float bounce) {
+        setBounce(motorId, bounce);
+    }
+
+    private native void setBounce(long motorId, float limitSoftness);
+
+    public boolean isEnableMotor() {
+        return isEnableMotor(motorId);
+    }
+
+    private native boolean isEnableMotor(long motorId);
+
+    public void setEnableMotor(boolean enableMotor) {
+        setEnableMotor(motorId, enableMotor);
+    }
+
+    private native void setEnableMotor(long motorId, boolean enableMotor);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java b/engine/src/bullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java
new file mode 100644
index 0000000..2e3910c
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints.motors;
+
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class TranslationalLimitMotor {
+
+    private long motorId = 0;
+
+    public TranslationalLimitMotor(long motor) {
+        this.motorId = motor;
+    }
+
+    public long getMotor() {
+        return motorId;
+    }
+
+    public Vector3f getLowerLimit() {
+        Vector3f vec = new Vector3f();
+        getLowerLimit(motorId, vec);
+        return vec;
+    }
+
+    private native void getLowerLimit(long motorId, Vector3f vector);
+
+    public void setLowerLimit(Vector3f lowerLimit) {
+        setLowerLimit(motorId, lowerLimit);
+    }
+
+    private native void setLowerLimit(long motorId, Vector3f vector);
+    
+    public Vector3f getUpperLimit() {
+        Vector3f vec = new Vector3f();
+        getUpperLimit(motorId, vec);
+        return vec;
+    }
+
+    private native void getUpperLimit(long motorId, Vector3f vector);
+
+    public void setUpperLimit(Vector3f upperLimit) {
+        setUpperLimit(motorId, upperLimit);
+    }
+
+    private native void setUpperLimit(long motorId, Vector3f vector);
+
+    public Vector3f getAccumulatedImpulse() {
+        Vector3f vec = new Vector3f();
+        getAccumulatedImpulse(motorId, vec);
+        return vec;
+    }
+
+    private native void getAccumulatedImpulse(long motorId, Vector3f vector);
+    
+    public void setAccumulatedImpulse(Vector3f accumulatedImpulse) {
+        setAccumulatedImpulse(motorId, accumulatedImpulse);
+    }
+
+    private native void setAccumulatedImpulse(long motorId, Vector3f vector);
+
+    public float getLimitSoftness() {
+        return getLimitSoftness(motorId);
+    }
+    
+    private native float getLimitSoftness(long motorId);
+
+    public void setLimitSoftness(float limitSoftness) {
+        setLimitSoftness(motorId, limitSoftness);
+    }
+    
+    private native void setLimitSoftness(long motorId, float limitSoftness);
+
+    public float getDamping() {
+        return getDamping(motorId);
+    }
+
+    private native float getDamping(long motorId);
+    
+    public void setDamping(float damping) {
+        setDamping(motorId, damping);
+    }
+
+    private native void setDamping(long motorId, float damping);
+    
+    public float getRestitution() {
+        return getRestitution(motorId);
+    }
+    
+    private native float getRestitution(long motorId);
+
+    public void setRestitution(float restitution) {
+        setRestitution(motorId, restitution);
+    }
+
+    private native void setRestitution(long motorId, float restitution);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java
new file mode 100644
index 0000000..fb98fec
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic Bullet Character
+ * @author normenhansen
+ */
+public class PhysicsCharacter extends PhysicsCollisionObject {
+
+    protected long characterId = 0;
+    protected float stepHeight;
+    protected Vector3f walkDirection = new Vector3f();
+    protected float fallSpeed = 55.0f;
+    protected float jumpSpeed = 10.0f;
+    protected int upAxis = 1;
+    protected boolean locationDirty = false;
+    //TEMP VARIABLES
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+
+    public PhysicsCharacter() {
+    }
+
+    /**
+     * @param shape The CollisionShape (no Mesh or CompoundCollisionShapes)
+     * @param stepHeight The quantization size for vertical movement
+     */
+    public PhysicsCharacter(CollisionShape shape, float stepHeight) {
+        this.collisionShape = shape;
+//        if (shape instanceof MeshCollisionShape || shape instanceof CompoundCollisionShape) {
+//            throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh or compound collision shapes"));
+//        }
+        this.stepHeight = stepHeight;
+        buildObject();
+    }
+
+    protected void buildObject() {
+        if (objectId == 0) {
+            objectId = createGhostObject();
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Creating GhostObject {0}", Long.toHexString(objectId));
+            initUserPointer();
+        }
+        setCharacterFlags(objectId);
+        attachCollisionShape(objectId, collisionShape.getObjectId());
+        if (characterId != 0) {
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing Character {0}", Long.toHexString(objectId));
+            finalizeNativeCharacter(characterId);
+        }
+        characterId = createCharacterObject(objectId, collisionShape.getObjectId(), stepHeight);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Creating Character {0}", Long.toHexString(characterId));
+    }
+
+    private native long createGhostObject();
+
+    private native void setCharacterFlags(long objectId);
+
+    private native long createCharacterObject(long objectId, long shapeId, float stepHeight);
+
+    /**
+     * Sets the location of this physics character
+     * @param location
+     */
+    public void warp(Vector3f location) {
+        warp(characterId, location);
+    }
+
+    private native void warp(long characterId, Vector3f location);
+
+    /**
+     * Set the walk direction, works continuously.
+     * This should probably be called setPositionIncrementPerSimulatorStep.
+     * This is neither a direction nor a velocity, but the amount to
+     * increment the position each physics tick. So vector length = accuracy*speed in m/s
+     * @param vec the walk direction to set
+     */
+    public void setWalkDirection(Vector3f vec) {
+        walkDirection.set(vec);
+        setWalkDirection(characterId, vec);
+    }
+
+    private native void setWalkDirection(long characterId, Vector3f vec);
+
+    /**
+     * @return the currently set walkDirection
+     */
+    public Vector3f getWalkDirection() {
+        return walkDirection;
+    }
+
+    public void setUpAxis(int axis) {
+        upAxis = axis;
+        setUpAxis(characterId, axis);
+    }
+
+    private native void setUpAxis(long characterId, int axis);
+
+    public int getUpAxis() {
+        return upAxis;
+    }
+
+    public void setFallSpeed(float fallSpeed) {
+        this.fallSpeed = fallSpeed;
+        setFallSpeed(characterId, fallSpeed);
+    }
+
+    private native void setFallSpeed(long characterId, float fallSpeed);
+
+    public float getFallSpeed() {
+        return fallSpeed;
+    }
+
+    public void setJumpSpeed(float jumpSpeed) {
+        this.jumpSpeed = jumpSpeed;
+        setJumpSpeed(characterId, jumpSpeed);
+    }
+
+    private native void setJumpSpeed(long characterId, float jumpSpeed);
+
+    public float getJumpSpeed() {
+        return jumpSpeed;
+    }
+
+    public void setGravity(float value) {
+        setGravity(characterId, value);
+    }
+
+    private native void setGravity(long characterId, float gravity);
+
+    public float getGravity() {
+        return getGravity(characterId);
+    }
+
+    private native float getGravity(long characterId);
+
+    public void setMaxSlope(float slopeRadians) {
+        setMaxSlope(characterId, slopeRadians);
+    }
+
+    private native void setMaxSlope(long characterId, float slopeRadians);
+
+    public float getMaxSlope() {
+        return getMaxSlope(characterId);
+    }
+
+    private native float getMaxSlope(long characterId);
+
+    public boolean onGround() {
+        return onGround(characterId);
+    }
+
+    private native boolean onGround(long characterId);
+
+    public void jump() {
+        jump(characterId);
+    }
+
+    private native void jump(long characterId);
+
+    @Override
+    public void setCollisionShape(CollisionShape collisionShape) {
+//        if (!(collisionShape.getObjectId() instanceof ConvexShape)) {
+//            throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes"));
+//        }
+        super.setCollisionShape(collisionShape);
+        if (objectId == 0) {
+            buildObject();
+        } else {
+            attachCollisionShape(objectId, collisionShape.getObjectId());
+        }
+    }
+
+    /**
+     * Set the physics location (same as warp())
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        warp(location);
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation(Vector3f trans) {
+        if (trans == null) {
+            trans = new Vector3f();
+        }
+        getPhysicsLocation(objectId, trans);
+        return trans;
+    }
+
+    private native void getPhysicsLocation(long objectId, Vector3f vec);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation() {
+        return getPhysicsLocation(null);
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        setCcdSweptSphereRadius(objectId, radius);
+    }
+
+    private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+    public void setCcdMotionThreshold(float threshold) {
+        setCcdMotionThreshold(objectId, threshold);
+    }
+
+    private native void setCcdMotionThreshold(long objectId, float threshold);
+
+    public float getCcdSweptSphereRadius() {
+        return getCcdSweptSphereRadius(objectId);
+    }
+
+    private native float getCcdSweptSphereRadius(long objectId);
+
+    public float getCcdMotionThreshold() {
+        return getCcdMotionThreshold(objectId);
+    }
+
+    private native float getCcdMotionThreshold(long objectId);
+
+    public float getCcdSquareMotionThreshold() {
+        return getCcdSquareMotionThreshold(objectId);
+    }
+
+    private native float getCcdSquareMotionThreshold(long objectId);
+
+    /**
+     * used internally
+     */
+    public long getControllerId() {
+        return characterId;
+    }
+
+    public void destroy() {
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(stepHeight, "stepHeight", 1.0f);
+        capsule.write(getGravity(), "gravity", 9.8f * 3);
+        capsule.write(getMaxSlope(), "maxSlope", 1.0f);
+        capsule.write(fallSpeed, "fallSpeed", 55.0f);
+        capsule.write(jumpSpeed, "jumpSpeed", 10.0f);
+        capsule.write(upAxis, "upAxis", 1);
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        stepHeight = capsule.readFloat("stepHeight", 1.0f);
+        buildObject();
+        setGravity(capsule.readFloat("gravity", 9.8f * 3));
+        setMaxSlope(capsule.readFloat("maxSlope", 1.0f));
+        setFallSpeed(capsule.readFloat("fallSpeed", 55.0f));
+        setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f));
+        setUpAxis(capsule.readInt("upAxis", 1));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        finalizeNativeCharacter(characterId);
+    }
+
+    private native void finalizeNativeCharacter(long characterId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java
new file mode 100644
index 0000000..2f87164
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From Bullet manual:</i><br>
+ * GhostObject can keep track of all objects that are overlapping.
+ * By default, this overlap is based on the AABB.
+ * This is useful for creating a character controller,
+ * collision sensors/triggers, explosions etc.<br>
+ * @author normenhansen
+ */
+public class PhysicsGhostObject extends PhysicsCollisionObject {
+
+    protected boolean locationDirty = false;
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private List<PhysicsCollisionObject> overlappingObjects = new LinkedList<PhysicsCollisionObject>();
+
+    public PhysicsGhostObject() {
+    }
+
+    public PhysicsGhostObject(CollisionShape shape) {
+        collisionShape = shape;
+        buildObject();
+    }
+
+    public PhysicsGhostObject(Spatial child, CollisionShape shape) {
+        collisionShape = shape;
+        buildObject();
+    }
+
+    protected void buildObject() {
+        if (objectId == 0) {
+//            gObject = new PairCachingGhostObject();
+            objectId = createGhostObject();
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Ghost Object {0}", Long.toHexString(objectId));
+            setGhostFlags(objectId);
+            initUserPointer();
+        }
+//        if (gObject == null) {
+//            gObject = new PairCachingGhostObject();
+//            gObject.setCollisionFlags(gObject.getCollisionFlags() | CollisionFlags.NO_CONTACT_RESPONSE);
+//        }
+        attachCollisionShape(objectId, collisionShape.getObjectId());
+    }
+
+    private native long createGhostObject();
+
+    private native void setGhostFlags(long objectId);
+
+    @Override
+    public void setCollisionShape(CollisionShape collisionShape) {
+        super.setCollisionShape(collisionShape);
+        if (objectId == 0) {
+            buildObject();
+        } else {
+            attachCollisionShape(objectId, collisionShape.getObjectId());
+        }
+    }
+
+    /**
+     * Sets the physics object location
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        setPhysicsLocation(objectId, location);
+    }
+
+    private native void setPhysicsLocation(long objectId, Vector3f location);
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Matrix3f rotation) {
+        setPhysicsRotation(objectId, rotation);
+    }
+
+    private native void setPhysicsRotation(long objectId, Matrix3f rotation);
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Quaternion rotation) {
+        setPhysicsRotation(objectId, rotation);
+    }
+
+    private native void setPhysicsRotation(long objectId, Quaternion rotation);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation(Vector3f trans) {
+        if (trans == null) {
+            trans = new Vector3f();
+        }
+        getPhysicsLocation(objectId, trans);
+        return trans;
+    }
+
+    private native void getPhysicsLocation(long objectId, Vector3f vector);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation(Quaternion rot) {
+        if (rot == null) {
+            rot = new Quaternion();
+        }
+        getPhysicsRotation(objectId, rot);
+        return rot;
+    }
+
+    private native void getPhysicsRotation(long objectId, Quaternion rot);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) {
+        if (rot == null) {
+            rot = new Matrix3f();
+        }
+        getPhysicsRotationMatrix(objectId, rot);
+        return rot;
+    }
+
+    private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation() {
+        Vector3f vec = new Vector3f();
+        getPhysicsLocation(objectId, vec);
+        return vec;
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation() {
+        Quaternion quat = new Quaternion();
+        getPhysicsRotation(objectId, quat);
+        return quat;
+    }
+
+    public Matrix3f getPhysicsRotationMatrix() {
+        Matrix3f mtx = new Matrix3f();
+        getPhysicsRotationMatrix(objectId, mtx);
+        return mtx;
+    }
+
+    /**
+     * used internally
+     */
+//    public PairCachingGhostObject getObjectId() {
+//        return gObject;
+//    }
+    /**
+     * destroys this PhysicsGhostNode and removes it from memory
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Another Object is overlapping with this GhostNode,
+     * if and if only there CollisionShapes overlaps.
+     * They could be both regular PhysicsRigidBodys or PhysicsGhostObjects.
+     * @return All CollisionObjects overlapping with this GhostNode.
+     */
+    public List<PhysicsCollisionObject> getOverlappingObjects() {
+        overlappingObjects.clear();
+        getOverlappingObjects(objectId);
+//        for (com.bulletphysics.collision.dispatch.CollisionObject collObj : gObject.getOverlappingPairs()) {
+//            overlappingObjects.add((PhysicsCollisionObject) collObj.getUserPointer());
+//        }
+        return overlappingObjects;
+    }
+
+    protected native void getOverlappingObjects(long objectId);
+
+    private void addOverlappingObject_native(PhysicsCollisionObject co) {
+        overlappingObjects.add(co);
+    }
+
+    /**
+     *
+     * @return With how many other CollisionObjects this GhostNode is currently overlapping.
+     */
+    public int getOverlappingCount() {
+        return getOverlappingCount(objectId);
+    }
+
+    private native int getOverlappingCount(long objectId);
+
+    /**
+     *
+     * @param index The index of the overlapping Node to retrieve.
+     * @return The Overlapping CollisionObject at the given index.
+     */
+    public PhysicsCollisionObject getOverlapping(int index) {
+        return overlappingObjects.get(index);
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        setCcdSweptSphereRadius(objectId, radius);
+    }
+
+    private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+    public void setCcdMotionThreshold(float threshold) {
+        setCcdMotionThreshold(objectId, threshold);
+    }
+
+    private native void setCcdMotionThreshold(long objectId, float threshold);
+
+    public float getCcdSweptSphereRadius() {
+        return getCcdSweptSphereRadius(objectId);
+    }
+
+    private native float getCcdSweptSphereRadius(long objectId);
+
+    public float getCcdMotionThreshold() {
+        return getCcdMotionThreshold(objectId);
+    }
+
+    private native float getCcdMotionThreshold(long objectId);
+
+    public float getCcdSquareMotionThreshold() {
+        return getCcdSquareMotionThreshold(objectId);
+    }
+
+    private native float getCcdSquareMotionThreshold(long objectId);
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+        capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        buildObject();
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+        setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f())));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java
new file mode 100644
index 0000000..0d41798
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.infos.RigidBodyMotionState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+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.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsRigidBody - Basic physics object</p>
+ * @author normenhansen
+ */
+public class PhysicsRigidBody extends PhysicsCollisionObject {
+
+    protected RigidBodyMotionState motionState = new RigidBodyMotionState();
+    protected float mass = 1.0f;
+    protected boolean kinematic = false;
+    protected ArrayList<PhysicsJoint> joints = new ArrayList<PhysicsJoint>();
+
+    public PhysicsRigidBody() {
+    }
+
+    /**
+     * Creates a new PhysicsRigidBody with the supplied collision shape
+     * @param child
+     * @param shape
+     */
+    public PhysicsRigidBody(CollisionShape shape) {
+        collisionShape = shape;
+        rebuildRigidBody();
+    }
+
+    public PhysicsRigidBody(CollisionShape shape, float mass) {
+        collisionShape = shape;
+        this.mass = mass;
+        rebuildRigidBody();
+    }
+
+    /**
+     * Builds/rebuilds the phyiscs body when parameters have changed
+     */
+    protected void rebuildRigidBody() {
+        boolean removed = false;
+        if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (objectId != 0) {
+            if (isInWorld(objectId)) {
+                PhysicsSpace.getPhysicsSpace().remove(this);
+                removed = true;
+            }
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing RigidBody {0}", Long.toHexString(objectId));
+            finalizeNative(objectId);
+        }
+        preRebuild();
+        objectId = createRigidBody(mass, motionState.getObjectId(), collisionShape.getObjectId());
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created RigidBody {0}", Long.toHexString(objectId));
+        postRebuild();
+        if (removed) {
+            PhysicsSpace.getPhysicsSpace().add(this);
+        }
+    }
+
+    protected void preRebuild() {
+    }
+
+    private native long createRigidBody(float mass, long motionStateId, long collisionShapeId);
+
+    protected void postRebuild() {
+        if (mass == 0.0f) {
+            setStatic(objectId, true);
+        } else {
+            setStatic(objectId, false);
+        }
+        initUserPointer();
+    }
+
+    /**
+     * @return the motionState
+     */
+    public RigidBodyMotionState getMotionState() {
+        return motionState;
+    }
+
+    public boolean isInWorld() {
+        return isInWorld(objectId);
+    }
+
+    private native boolean isInWorld(long objectId);
+
+    /**
+     * Sets the physics object location
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        setPhysicsLocation(objectId, location);
+    }
+
+    private native void setPhysicsLocation(long objectId, Vector3f location);
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Matrix3f rotation) {
+        setPhysicsRotation(objectId, rotation);
+    }
+
+    private native void setPhysicsRotation(long objectId, Matrix3f rotation);
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Quaternion rotation) {
+        setPhysicsRotation(objectId, rotation);
+    }
+
+    private native void setPhysicsRotation(long objectId, Quaternion rotation);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation(Vector3f trans) {
+        if (trans == null) {
+            trans = new Vector3f();
+        }
+        getPhysicsLocation(objectId, trans);
+        return trans;
+    }
+
+    private native void getPhysicsLocation(long objectId, Vector3f vector);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation(Quaternion rot) {
+        if (rot == null) {
+            rot = new Quaternion();
+        }
+        getPhysicsRotation(objectId, rot);
+        return rot;
+    }
+
+    private native void getPhysicsRotation(long objectId, Quaternion rot);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) {
+        if (rot == null) {
+            rot = new Matrix3f();
+        }
+        getPhysicsRotationMatrix(objectId, rot);
+        return rot;
+    }
+
+    private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot);
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation() {
+        Vector3f vec = new Vector3f();
+        getPhysicsLocation(objectId, vec);
+        return vec;
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation() {
+        Quaternion quat = new Quaternion();
+        getPhysicsRotation(objectId, quat);
+        return quat;
+    }
+
+    public Matrix3f getPhysicsRotationMatrix() {
+        Matrix3f mtx = new Matrix3f();
+        getPhysicsRotationMatrix(objectId, mtx);
+        return mtx;
+    }
+
+//    /**
+//     * Gets the physics object location
+//     * @param location the location of the actual physics object is stored in this Vector3f
+//     */
+//    public Vector3f getInterpolatedPhysicsLocation(Vector3f location) {
+//        if (location == null) {
+//            location = new Vector3f();
+//        }
+//        rBody.getInterpolationWorldTransform(tempTrans);
+//        return Converter.convert(tempTrans.origin, location);
+//    }
+//
+//    /**
+//     * Gets the physics object rotation
+//     * @param rotation the rotation of the actual physics object is stored in this Matrix3f
+//     */
+//    public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) {
+//        if (rotation == null) {
+//            rotation = new Matrix3f();
+//        }
+//        rBody.getInterpolationWorldTransform(tempTrans);
+//        return Converter.convert(tempTrans.basis, rotation);
+//    }
+    /**
+     * Sets the node to kinematic mode. in this mode the node is not affected by physics
+     * but affects other physics objects. Iits kinetic force is calculated by the amount
+     * of movement it is exposed to and its weight.
+     * @param kinematic
+     */
+    public void setKinematic(boolean kinematic) {
+        this.kinematic = kinematic;
+        setKinematic(objectId, kinematic);
+    }
+
+    private native void setKinematic(long objectId, boolean kinematic);
+
+    public boolean isKinematic() {
+        return kinematic;
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        setCcdSweptSphereRadius(objectId, radius);
+    }
+
+    private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+    /**
+     * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection<br/>
+     * This avoids the problem of fast objects moving through other objects, set to zero to disable (default)
+     * @param threshold
+     */
+    public void setCcdMotionThreshold(float threshold) {
+        setCcdMotionThreshold(objectId, threshold);
+    }
+
+    private native void setCcdMotionThreshold(long objectId, float threshold);
+
+    public float getCcdSweptSphereRadius() {
+        return getCcdSweptSphereRadius(objectId);
+    }
+
+    private native float getCcdSweptSphereRadius(long objectId);
+
+    public float getCcdMotionThreshold() {
+        return getCcdMotionThreshold(objectId);
+    }
+
+    private native float getCcdMotionThreshold(long objectId);
+
+    public float getCcdSquareMotionThreshold() {
+        return getCcdSquareMotionThreshold(objectId);
+    }
+
+    private native float getCcdSquareMotionThreshold(long objectId);
+
+    public float getMass() {
+        return mass;
+    }
+
+    /**
+     * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static.
+     * @param mass
+     */
+    public void setMass(float mass) {
+        this.mass = mass;
+        if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (objectId != 0) {
+            if (collisionShape != null) {
+                updateMassProps(objectId, collisionShape.getObjectId(), mass);
+            }
+            if (mass == 0.0f) {
+                setStatic(objectId, true);
+            } else {
+                setStatic(objectId, false);
+            }
+        }
+    }
+
+    private native void setStatic(long objectId, boolean state);
+
+    private native long updateMassProps(long objectId, long collisionShapeId, float mass);
+
+    public Vector3f getGravity() {
+        return getGravity(null);
+    }
+
+    public Vector3f getGravity(Vector3f gravity) {
+        if (gravity == null) {
+            gravity = new Vector3f();
+        }
+        getGravity(objectId, gravity);
+        return gravity;
+    }
+
+    private native void getGravity(long objectId, Vector3f gravity);
+
+    /**
+     * Set the local gravity of this PhysicsRigidBody<br/>
+     * Set this after adding the node to the PhysicsSpace,
+     * the PhysicsSpace assigns its current gravity to the physics node when its added.
+     * @param gravity the gravity vector to set
+     */
+    public void setGravity(Vector3f gravity) {
+        setGravity(objectId, gravity);
+    }
+
+    private native void setGravity(long objectId, Vector3f gravity);
+
+    public float getFriction() {
+        return getFriction(objectId);
+    }
+
+    private native float getFriction(long objectId);
+
+    /**
+     * Sets the friction of this physics object
+     * @param friction the friction of this physics object
+     */
+    public void setFriction(float friction) {
+        setFriction(objectId, friction);
+    }
+
+    private native void setFriction(long objectId, float friction);
+
+    public void setDamping(float linearDamping, float angularDamping) {
+        setDamping(objectId, linearDamping, angularDamping);
+    }
+
+    private native void setDamping(long objectId, float linearDamping, float angularDamping);
+
+//    private native void setRestitution(long objectId, float factor);
+//
+//    public void setLinearDamping(float linearDamping) {
+//        constructionInfo.linearDamping = linearDamping;
+//        rBody.setDamping(linearDamping, constructionInfo.angularDamping);
+//    }
+//
+//    private native void setRestitution(long objectId, float factor);
+//
+    public void setLinearDamping(float linearDamping) {
+        setDamping(objectId, linearDamping, getAngularDamping());
+    }
+    
+    public void setAngularDamping(float angularDamping) {
+        setAngularDamping(objectId, angularDamping);
+    }
+    private native void setAngularDamping(long objectId, float factor);
+
+    public float getLinearDamping() {
+        return getLinearDamping(objectId);
+    }
+
+    private native float getLinearDamping(long objectId);
+
+    public float getAngularDamping() {
+        return getAngularDamping(objectId);
+    }
+
+    private native float getAngularDamping(long objectId);
+
+    public float getRestitution() {
+        return getRestitution(objectId);
+    }
+
+    private native float getRestitution(long objectId);
+
+    /**
+     * The "bouncyness" of the PhysicsRigidBody, best performance if restitution=0
+     * @param restitution
+     */
+    public void setRestitution(float restitution) {
+        setRestitution(objectId, restitution);
+    }
+
+    private native void setRestitution(long objectId, float factor);
+
+    /**
+     * Get the current angular velocity of this PhysicsRigidBody
+     * @return the current linear velocity
+     */
+    public Vector3f getAngularVelocity() {
+        Vector3f vec = new Vector3f();
+        getAngularVelocity(objectId, vec);
+        return vec;
+    }
+
+    private native void getAngularVelocity(long objectId, Vector3f vec);
+
+    /**
+     * Get the current angular velocity of this PhysicsRigidBody
+     * @param vec the vector to store the velocity in
+     */
+    public void getAngularVelocity(Vector3f vec) {
+        getAngularVelocity(objectId, vec);
+    }
+
+    /**
+     * Sets the angular velocity of this PhysicsRigidBody
+     * @param vec the angular velocity of this PhysicsRigidBody
+     */
+    public void setAngularVelocity(Vector3f vec) {
+        setAngularVelocity(objectId, vec);
+        activate();
+    }
+
+    private native void setAngularVelocity(long objectId, Vector3f vec);
+
+    /**
+     * Get the current linear velocity of this PhysicsRigidBody
+     * @return the current linear velocity
+     */
+    public Vector3f getLinearVelocity() {
+        Vector3f vec = new Vector3f();
+        getLinearVelocity(objectId, vec);
+        return vec;
+    }
+
+    private native void getLinearVelocity(long objectId, Vector3f vec);
+
+    /**
+     * Get the current linear velocity of this PhysicsRigidBody
+     * @param vec the vector to store the velocity in
+     */
+    public void getLinearVelocity(Vector3f vec) {
+        getLinearVelocity(objectId, vec);
+    }
+
+    /**
+     * Sets the linear velocity of this PhysicsRigidBody
+     * @param vec the linear velocity of this PhysicsRigidBody
+     */
+    public void setLinearVelocity(Vector3f vec) {
+        setLinearVelocity(objectId, vec);
+        activate();
+    }
+
+    private native void setLinearVelocity(long objectId, Vector3f vec);
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force.
+     * @param force the force
+     * @param location the location of the force
+     */
+    public void applyForce(Vector3f force, Vector3f location) {
+        applyForce(objectId, force, location);
+        activate();
+    }
+
+    private native void applyForce(long objectId, Vector3f force, Vector3f location);
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse.
+     * 
+     * @param force the force
+     */
+    public void applyCentralForce(Vector3f force) {
+        applyCentralForce(objectId, force);
+        activate();
+    }
+
+    private native void applyCentralForce(long objectId, Vector3f force);
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse.
+     * 
+     * @param torque the torque
+     */
+    public void applyTorque(Vector3f torque) {
+        applyTorque(objectId, torque);
+        activate();
+    }
+
+    private native void applyTorque(long objectId, Vector3f vec);
+
+    /**
+     * Apply an impulse to the PhysicsRigidBody in the next physics update.
+     * @param impulse applied impulse
+     * @param rel_pos location relative to object
+     */
+    public void applyImpulse(Vector3f impulse, Vector3f rel_pos) {
+        applyImpulse(objectId, impulse, rel_pos);
+        activate();
+    }
+
+    private native void applyImpulse(long objectId, Vector3f impulse, Vector3f rel_pos);
+
+    /**
+     * Apply a torque impulse to the PhysicsRigidBody in the next physics update.
+     * @param vec
+     */
+    public void applyTorqueImpulse(Vector3f vec) {
+        applyTorqueImpulse(objectId, vec);
+        activate();
+    }
+
+    private native void applyTorqueImpulse(long objectId, Vector3f vec);
+
+    /**
+     * Clear all forces from the PhysicsRigidBody
+     * 
+     */
+    public void clearForces() {
+        clearForces(objectId);
+    }
+
+    private native void clearForces(long objectId);
+
+    public void setCollisionShape(CollisionShape collisionShape) {
+        super.setCollisionShape(collisionShape);
+        if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (objectId == 0) {
+            rebuildRigidBody();
+        } else {
+            setCollisionShape(objectId, collisionShape.getObjectId());
+            updateMassProps(objectId, collisionShape.getObjectId(), mass);
+        }
+    }
+
+    private native void setCollisionShape(long objectId, long collisionShapeId);
+
+    /**
+     * reactivates this PhysicsRigidBody when it has been deactivated because it was not moving
+     */
+    public void activate() {
+        activate(objectId);
+    }
+
+    private native void activate(long objectId);
+
+    public boolean isActive() {
+        return isActive(objectId);
+    }
+
+    private native boolean isActive(long objectId);
+
+    /**
+     * sets the sleeping thresholds, these define when the object gets deactivated
+     * to save ressources. Low values keep the object active when it barely moves
+     * @param linear the linear sleeping threshold
+     * @param angular the angular sleeping threshold
+     */
+    public void setSleepingThresholds(float linear, float angular) {
+        setSleepingThresholds(objectId, linear, angular);
+    }
+
+    private native void setSleepingThresholds(long objectId, float linear, float angular);
+
+    public void setLinearSleepingThreshold(float linearSleepingThreshold) {
+        setLinearSleepingThreshold(objectId, linearSleepingThreshold);
+    }
+
+    private native void setLinearSleepingThreshold(long objectId, float linearSleepingThreshold);
+
+    public void setAngularSleepingThreshold(float angularSleepingThreshold) {
+        setAngularSleepingThreshold(objectId, angularSleepingThreshold);
+    }
+
+    private native void setAngularSleepingThreshold(long objectId, float angularSleepingThreshold);
+
+    public float getLinearSleepingThreshold() {
+        return getLinearSleepingThreshold(objectId);
+    }
+
+    private native float getLinearSleepingThreshold(long objectId);
+
+    public float getAngularSleepingThreshold() {
+        return getAngularSleepingThreshold(objectId);
+    }
+
+    private native float getAngularSleepingThreshold(long objectId);
+
+    public float getAngularFactor() {
+        return getAngularFactor(objectId);
+    }
+
+    private native float getAngularFactor(long objectId);
+
+    public void setAngularFactor(float factor) {
+        setAngularFactor(objectId, factor);
+    }
+
+    private native void setAngularFactor(long objectId, float factor);
+
+    /**
+     * do not use manually, joints are added automatically
+     */
+    public void addJoint(PhysicsJoint joint) {
+        if (!joints.contains(joint)) {
+            joints.add(joint);
+        }
+        updateDebugShape();
+    }
+
+    /**
+     * 
+     */
+    public void removeJoint(PhysicsJoint joint) {
+        joints.remove(joint);
+    }
+
+    /**
+     * Returns a list of connected joints. This list is only filled when
+     * the PhysicsRigidBody is actually added to the physics space or loaded from disk.
+     * @return list of active joints connected to this PhysicsRigidBody
+     */
+    public List<PhysicsJoint> getJoints() {
+        return joints;
+    }
+
+    @Override
+    protected Spatial getDebugShape() {
+        //add joints
+        Spatial shape = super.getDebugShape();
+        Node node = null;
+        if (shape instanceof Node) {
+            node = (Node) shape;
+        } else {
+            node = new Node("DebugShapeNode");
+            node.attachChild(shape);
+        }
+        int i = 0;
+        for (Iterator<PhysicsJoint> it = joints.iterator(); it.hasNext();) {
+            PhysicsJoint physicsJoint = it.next();
+            Vector3f pivot = null;
+            if (physicsJoint.getBodyA() == this) {
+                pivot = physicsJoint.getPivotA();
+            } else {
+                pivot = physicsJoint.getPivotB();
+            }
+            Arrow arrow = new Arrow(pivot);
+            Geometry geom = new Geometry("DebugBone" + i, arrow);
+            geom.setMaterial(debugMaterialGreen);
+            node.attachChild(geom);
+            i++;
+        }
+        return node;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+
+        capsule.write(getMass(), "mass", 1.0f);
+
+        capsule.write(getGravity(), "gravity", Vector3f.ZERO);
+        capsule.write(getFriction(), "friction", 0.5f);
+        capsule.write(getRestitution(), "restitution", 0);
+        capsule.write(getAngularFactor(), "angularFactor", 1);
+        capsule.write(kinematic, "kinematic", false);
+
+        capsule.write(getLinearDamping(), "linearDamping", 0);
+        capsule.write(getAngularDamping(), "angularDamping", 0);
+        capsule.write(getLinearSleepingThreshold(), "linearSleepingThreshold", 0.8f);
+        capsule.write(getAngularSleepingThreshold(), "angularSleepingThreshold", 1.0f);
+
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+        capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+
+        capsule.writeSavableArrayList(joints, "joints", null);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+
+        InputCapsule capsule = e.getCapsule(this);
+        float mass = capsule.readFloat("mass", 1.0f);
+        this.mass = mass;
+        rebuildRigidBody();
+        setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone()));
+        setFriction(capsule.readFloat("friction", 0.5f));
+        setKinematic(capsule.readBoolean("kinematic", false));
+
+        setRestitution(capsule.readFloat("restitution", 0));
+        setAngularFactor(capsule.readFloat("angularFactor", 1));
+        setDamping(capsule.readFloat("linearDamping", 0), capsule.readFloat("angularDamping", 0));
+        setSleepingThresholds(capsule.readFloat("linearSleepingThreshold", 0.8f), capsule.readFloat("angularSleepingThreshold", 1.0f));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+        setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()));
+
+        joints = capsule.readSavableArrayList("joints", null);
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java
new file mode 100644
index 0000000..fba1e83
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.objects.infos.VehicleTuning;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions</p>
+ * <p>
+ * <i>From bullet manual:</i><br>
+ * For most vehicle simulations, it is recommended to use the simplified Bullet
+ * vehicle model as provided in btRaycastVehicle. Instead of simulation each wheel
+ * and chassis as separate rigid bodies, connected by constraints, it uses a simplified model.
+ * This simplified model has many benefits, and is widely used in commercial driving games.<br>
+ * The entire vehicle is represented as a single rigidbody, the chassis.
+ * The collision detection of the wheels is approximated by ray casts,
+ * and the tire friction is a basic anisotropic friction model.
+ * </p>
+ * @author normenhansen
+ */
+public class PhysicsVehicle extends PhysicsRigidBody {
+
+    protected long vehicleId = 0;
+    protected long rayCasterId = 0;
+    protected VehicleTuning tuning = new VehicleTuning();
+    protected ArrayList<VehicleWheel> wheels = new ArrayList<VehicleWheel>();
+    protected PhysicsSpace physicsSpace;
+
+    public PhysicsVehicle() {
+    }
+
+    public PhysicsVehicle(CollisionShape shape) {
+        super(shape);
+    }
+
+    public PhysicsVehicle(CollisionShape shape, float mass) {
+        super(shape, mass);
+    }
+
+    /**
+     * used internally
+     */
+    public void updateWheels() {
+        if (vehicleId != 0) {
+            for (int i = 0; i < wheels.size(); i++) {
+                updateWheelTransform(vehicleId, i, true);
+                wheels.get(i).updatePhysicsState();
+            }
+        }
+    }
+
+    private native void updateWheelTransform(long vehicleId, int wheel, boolean interpolated);
+
+    /**
+     * used internally
+     */
+    public void applyWheelTransforms() {
+        if (wheels != null) {
+            for (int i = 0; i < wheels.size(); i++) {
+                wheels.get(i).applyWheelTransform();
+            }
+        }
+    }
+
+    @Override
+    protected void postRebuild() {
+        super.postRebuild();
+        motionState.setVehicle(this);
+        createVehicle(physicsSpace);
+    }
+
+    /**
+     * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace
+     */
+    public void createVehicle(PhysicsSpace space) {
+        physicsSpace = space;
+        if (space == null) {
+            return;
+        }
+        if (space.getSpaceId() == 0) {
+            throw new IllegalStateException("Physics space is not initialized!");
+        }
+        if (rayCasterId != 0) {
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing RayCaster {0}", Long.toHexString(rayCasterId));
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing Vehicle {0}", Long.toHexString(vehicleId));
+            finalizeNative(rayCasterId, vehicleId);
+        }
+        rayCasterId = createVehicleRaycaster(objectId, space.getSpaceId());
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created RayCaster {0}", Long.toHexString(rayCasterId));
+        vehicleId = createRaycastVehicle(objectId, rayCasterId);
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Vehicle {0}", Long.toHexString(vehicleId));
+        setCoordinateSystem(vehicleId, 0, 1, 2);
+        for (VehicleWheel wheel : wheels) {
+            wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
+        }
+    }
+
+    private native long createVehicleRaycaster(long objectId, long physicsSpaceId);
+
+    private native long createRaycastVehicle(long objectId, long rayCasterId);
+
+    private native void setCoordinateSystem(long objectId, int a, int b, int c);
+
+    private native int addWheel(long objectId, Vector3f location, Vector3f direction, Vector3f axle, float restLength, float radius, VehicleTuning tuning, boolean frontWheel);
+
+    /**
+     * Add a wheel to this vehicle
+     * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+     * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+     * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+     * @param suspensionRestLength The current length of the suspension (metres)
+     * @param wheelRadius the wheel radius
+     * @param isFrontWheel sets if this wheel is a front wheel (steering)
+     * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+     */
+    public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+        return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+    }
+
+    /**
+     * Add a wheel to this vehicle
+     * @param spat the wheel Geometry
+     * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+     * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+     * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+     * @param suspensionRestLength The current length of the suspension (metres)
+     * @param wheelRadius the wheel radius
+     * @param isFrontWheel sets if this wheel is a front wheel (steering)
+     * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+     */
+    public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+        VehicleWheel wheel = null;
+        if (spat == null) {
+            wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+        } else {
+            wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+        }
+        wheel.setFrictionSlip(tuning.frictionSlip);
+        wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
+        wheel.setSuspensionStiffness(tuning.suspensionStiffness);
+        wheel.setWheelsDampingCompression(tuning.suspensionCompression);
+        wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
+        wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
+        wheels.add(wheel);
+        if (vehicleId != 0) {
+            wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
+        }
+        if (debugShape != null) {
+            updateDebugShape();
+        }
+        return wheel;
+    }
+
+    /**
+     * This rebuilds the vehicle as there is no way in bullet to remove a wheel.
+     * @param wheel
+     */
+    public void removeWheel(int wheel) {
+        wheels.remove(wheel);
+        rebuildRigidBody();
+//        updateDebugShape();
+    }
+
+    /**
+     * You can get access to the single wheels via this method.
+     * @param wheel the wheel index
+     * @return the WheelInfo of the selected wheel
+     */
+    public VehicleWheel getWheel(int wheel) {
+        return wheels.get(wheel);
+    }
+
+    public int getNumWheels() {
+        return wheels.size();
+    }
+
+    /**
+     * @return the frictionSlip
+     */
+    public float getFrictionSlip() {
+        return tuning.frictionSlip;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param frictionSlip the frictionSlip to set
+     */
+    public void setFrictionSlip(float frictionSlip) {
+        tuning.frictionSlip = frictionSlip;
+    }
+
+    /**
+     * The coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param wheel
+     * @param frictionSlip
+     */
+    public void setFrictionSlip(int wheel, float frictionSlip) {
+        wheels.get(wheel).setFrictionSlip(frictionSlip);
+    }
+
+    /**
+     * Reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+     * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+     * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+     * You should also try lowering the vehicle's centre of mass
+     */
+    public void setRollInfluence(int wheel, float rollInfluence) {
+        wheels.get(wheel).setRollInfluence(rollInfluence);
+    }
+
+    /**
+     * @return the maxSuspensionTravelCm
+     */
+    public float getMaxSuspensionTravelCm() {
+        return tuning.maxSuspensionTravelCm;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The maximum distance the suspension can be compressed (centimetres)
+     * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set
+     */
+    public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+        tuning.maxSuspensionTravelCm = maxSuspensionTravelCm;
+    }
+
+    /**
+     * The maximum distance the suspension can be compressed (centimetres)
+     * @param wheel
+     * @param maxSuspensionTravelCm
+     */
+    public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
+        wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
+    }
+
+    public float getMaxSuspensionForce() {
+        return tuning.maxSuspensionForce;
+    }
+
+    /**
+     * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param maxSuspensionForce
+     */
+    public void setMaxSuspensionForce(float maxSuspensionForce) {
+        tuning.maxSuspensionForce = maxSuspensionForce;
+    }
+
+    /**
+     * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param wheel
+     * @param maxSuspensionForce
+     */
+    public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
+        wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
+    }
+
+    /**
+     * @return the suspensionCompression
+     */
+    public float getSuspensionCompression() {
+        return tuning.suspensionCompression;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param suspensionCompression the suspensionCompression to set
+     */
+    public void setSuspensionCompression(float suspensionCompression) {
+        tuning.suspensionCompression = suspensionCompression;
+    }
+
+    /**
+     * The damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param wheel
+     * @param suspensionCompression
+     */
+    public void setSuspensionCompression(int wheel, float suspensionCompression) {
+        wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
+    }
+
+    /**
+     * @return the suspensionDamping
+     */
+    public float getSuspensionDamping() {
+        return tuning.suspensionDamping;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The damping coefficient for when the suspension is expanding.
+     * See the comments for setSuspensionCompression for how to set k.
+     * @param suspensionDamping the suspensionDamping to set
+     */
+    public void setSuspensionDamping(float suspensionDamping) {
+        tuning.suspensionDamping = suspensionDamping;
+    }
+
+    /**
+     * The damping coefficient for when the suspension is expanding.
+     * See the comments for setSuspensionCompression for how to set k.
+     * @param wheel
+     * @param suspensionDamping
+     */
+    public void setSuspensionDamping(int wheel, float suspensionDamping) {
+        wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
+    }
+
+    /**
+     * @return the suspensionStiffness
+     */
+    public float getSuspensionStiffness() {
+        return tuning.suspensionStiffness;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param suspensionStiffness 
+     */
+    public void setSuspensionStiffness(float suspensionStiffness) {
+        tuning.suspensionStiffness = suspensionStiffness;
+    }
+
+    /**
+     * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param wheel
+     * @param suspensionStiffness
+     */
+    public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
+        wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
+    }
+
+    /**
+     * Reset the suspension
+     */
+    public void resetSuspension() {
+        resetSuspension(vehicleId);
+    }
+
+    private native void resetSuspension(long vehicleId);
+
+    /**
+     * Apply the given engine force to all wheels, works continuously
+     * @param force the force
+     */
+    public void accelerate(float force) {
+        for (int i = 0; i < wheels.size(); i++) {
+            accelerate(i, force);
+        }
+    }
+
+    /**
+     * Apply the given engine force, works continuously
+     * @param wheel the wheel to apply the force on
+     * @param force the force
+     */
+    public void accelerate(int wheel, float force) {
+        applyEngineForce(vehicleId, wheel, force);
+
+    }
+
+    private native void applyEngineForce(long vehicleId, int wheel, float force);
+
+    /**
+     * Set the given steering value to all front wheels (0 = forward)
+     * @param value the steering angle of the front wheels (Pi = 360deg)
+     */
+    public void steer(float value) {
+        for (int i = 0; i < wheels.size(); i++) {
+            if (getWheel(i).isFrontWheel()) {
+                steer(i, value);
+            }
+        }
+    }
+
+    /**
+     * Set the given steering value to the given wheel (0 = forward)
+     * @param wheel the wheel to set the steering on
+     * @param value the steering angle of the front wheels (Pi = 360deg)
+     */
+    public void steer(int wheel, float value) {
+        steer(vehicleId, wheel, value);
+    }
+
+    private native void steer(long vehicleId, int wheel, float value);
+
+    /**
+     * Apply the given brake force to all wheels, works continuously
+     * @param force the force
+     */
+    public void brake(float force) {
+        for (int i = 0; i < wheels.size(); i++) {
+            brake(i, force);
+        }
+    }
+
+    /**
+     * Apply the given brake force, works continuously
+     * @param wheel the wheel to apply the force on
+     * @param force the force
+     */
+    public void brake(int wheel, float force) {
+        brake(vehicleId, wheel, force);
+    }
+
+    private native void brake(long vehicleId, int wheel, float force);
+
+    /**
+     * Get the current speed of the vehicle in km/h
+     * @return
+     */
+    public float getCurrentVehicleSpeedKmHour() {
+        return getCurrentVehicleSpeedKmHour(vehicleId);
+    }
+
+    private native float getCurrentVehicleSpeedKmHour(long vehicleId);
+
+    /**
+     * Get the current forward vector of the vehicle in world coordinates
+     * @param vector
+     * @return
+     */
+    public Vector3f getForwardVector(Vector3f vector) {
+        if (vector == null) {
+            vector = new Vector3f();
+        }
+        getForwardVector(vehicleId, vector);
+        return vector;
+    }
+
+    private native void getForwardVector(long objectId, Vector3f vector);
+
+    /**
+     * used internally
+     */
+    public long getVehicleId() {
+        return vehicleId;
+    }
+
+    @Override
+    protected Spatial getDebugShape() {
+        Spatial shape = super.getDebugShape();
+        Node node = null;
+        if (shape instanceof Node) {
+            node = (Node) shape;
+        } else {
+            node = new Node("DebugShapeNode");
+            node.attachChild(shape);
+        }
+        int i = 0;
+        for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+            VehicleWheel physicsVehicleWheel = it.next();
+            Vector3f location = physicsVehicleWheel.getLocation().clone();
+            Vector3f direction = physicsVehicleWheel.getDirection().clone();
+            Vector3f axle = physicsVehicleWheel.getAxle().clone();
+            float restLength = physicsVehicleWheel.getRestLength();
+            float radius = physicsVehicleWheel.getRadius();
+
+            Arrow locArrow = new Arrow(location);
+            Arrow axleArrow = new Arrow(axle.normalizeLocal().multLocal(0.3f));
+            Arrow wheelArrow = new Arrow(direction.normalizeLocal().multLocal(radius));
+            Arrow dirArrow = new Arrow(direction.normalizeLocal().multLocal(restLength));
+            Geometry locGeom = new Geometry("WheelLocationDebugShape" + i, locArrow);
+            Geometry dirGeom = new Geometry("WheelDirectionDebugShape" + i, dirArrow);
+            Geometry axleGeom = new Geometry("WheelAxleDebugShape" + i, axleArrow);
+            Geometry wheelGeom = new Geometry("WheelRadiusDebugShape" + i, wheelArrow);
+            dirGeom.setLocalTranslation(location);
+            axleGeom.setLocalTranslation(location.add(direction));
+            wheelGeom.setLocalTranslation(location.add(direction));
+            locGeom.setMaterial(debugMaterialGreen);
+            dirGeom.setMaterial(debugMaterialGreen);
+            axleGeom.setMaterial(debugMaterialGreen);
+            wheelGeom.setMaterial(debugMaterialGreen);
+            node.attachChild(locGeom);
+            node.attachChild(dirGeom);
+            node.attachChild(axleGeom);
+            node.attachChild(wheelGeom);
+            i++;
+        }
+        return node;
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        tuning = new VehicleTuning();
+        tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+        tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+        tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+        tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f);
+        tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f);
+        tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f);
+        wheels = capsule.readSavableArrayList("wheelsList", new ArrayList<VehicleWheel>());
+        motionState.setVehicle(this);
+        super.read(im);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f);
+        capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+        capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f);
+        capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f);
+        capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f);
+        capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f);
+        capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList<VehicleWheel>());
+        super.write(ex);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing RayCaster {0}", Long.toHexString(rayCasterId));
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Vehicle {0}", Long.toHexString(vehicleId));
+        finalizeNative(rayCasterId, vehicleId);
+    }
+
+    private native void finalizeNative(long rayCaster, long vehicle);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java b/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java
new file mode 100644
index 0000000..11eab60
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.export.*;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Stores info about one wheel of a PhysicsVehicle
+ * @author normenhansen
+ */
+public class VehicleWheel implements Savable {
+
+    protected long wheelId = 0;
+    protected int wheelIndex = 0;
+    protected boolean frontWheel;
+    protected Vector3f location = new Vector3f();
+    protected Vector3f direction = new Vector3f();
+    protected Vector3f axle = new Vector3f();
+    protected float suspensionStiffness = 20.0f;
+    protected float wheelsDampingRelaxation = 2.3f;
+    protected float wheelsDampingCompression = 4.4f;
+    protected float frictionSlip = 10.5f;
+    protected float rollInfluence = 1.0f;
+    protected float maxSuspensionTravelCm = 500f;
+    protected float maxSuspensionForce = 6000f;
+    protected float radius = 0.5f;
+    protected float restLength = 1f;
+    protected Vector3f wheelWorldLocation = new Vector3f();
+    protected Quaternion wheelWorldRotation = new Quaternion();
+    protected Spatial wheelSpatial;
+    protected Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f();
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private boolean applyLocal = false;
+
+    public VehicleWheel() {
+    }
+
+    public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle,
+            float restLength, float radius, boolean frontWheel) {
+        this(location, direction, axle, restLength, radius, frontWheel);
+        wheelSpatial = spat;
+    }
+
+    public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle,
+            float restLength, float radius, boolean frontWheel) {
+        this.location.set(location);
+        this.direction.set(direction);
+        this.axle.set(axle);
+        this.frontWheel = frontWheel;
+        this.restLength = restLength;
+        this.radius = radius;
+    }
+
+    public synchronized void updatePhysicsState() {
+        getWheelLocation(wheelId, wheelIndex, wheelWorldLocation);
+        getWheelRotation(wheelId, wheelIndex, tmp_Matrix);
+        wheelWorldRotation.fromRotationMatrix(tmp_Matrix);
+    }
+
+    private native void getWheelLocation(long vehicleId, int wheelId, Vector3f location);
+
+    private native void getWheelRotation(long vehicleId, int wheelId, Matrix3f location);
+
+    public synchronized void applyWheelTransform() {
+        if (wheelSpatial == null) {
+            return;
+        }
+        Quaternion localRotationQuat = wheelSpatial.getLocalRotation();
+        Vector3f localLocation = wheelSpatial.getLocalTranslation();
+        if (!applyLocal && wheelSpatial.getParent() != null) {
+            localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation());
+            localLocation.divideLocal(wheelSpatial.getParent().getWorldScale());
+            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+            localRotationQuat.set(wheelWorldRotation);
+            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
+
+            wheelSpatial.setLocalTranslation(localLocation);
+            wheelSpatial.setLocalRotation(localRotationQuat);
+        } else {
+            wheelSpatial.setLocalTranslation(wheelWorldLocation);
+            wheelSpatial.setLocalRotation(wheelWorldRotation);
+        }
+    }
+
+    public long getWheelId() {
+        return wheelId;
+    }
+
+    public void setVehicleId(long vehicleId, int wheelIndex) {
+        this.wheelId = vehicleId;
+        this.wheelIndex = wheelIndex;
+        applyInfo();
+    }
+
+    public boolean isFrontWheel() {
+        return frontWheel;
+    }
+
+    public void setFrontWheel(boolean frontWheel) {
+        this.frontWheel = frontWheel;
+        applyInfo();
+    }
+
+    public Vector3f getLocation() {
+        return location;
+    }
+
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    public Vector3f getAxle() {
+        return axle;
+    }
+
+    public float getSuspensionStiffness() {
+        return suspensionStiffness;
+    }
+
+    /**
+     * the stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param suspensionStiffness
+     */
+    public void setSuspensionStiffness(float suspensionStiffness) {
+        this.suspensionStiffness = suspensionStiffness;
+        applyInfo();
+    }
+
+    public float getWheelsDampingRelaxation() {
+        return wheelsDampingRelaxation;
+    }
+
+    /**
+     * the damping coefficient for when the suspension is expanding.
+     * See the comments for setWheelsDampingCompression for how to set k.
+     * @param wheelsDampingRelaxation
+     */
+    public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) {
+        this.wheelsDampingRelaxation = wheelsDampingRelaxation;
+        applyInfo();
+    }
+
+    public float getWheelsDampingCompression() {
+        return wheelsDampingCompression;
+    }
+
+    /**
+     * the damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param wheelsDampingCompression
+     */
+    public void setWheelsDampingCompression(float wheelsDampingCompression) {
+        this.wheelsDampingCompression = wheelsDampingCompression;
+        applyInfo();
+    }
+
+    public float getFrictionSlip() {
+        return frictionSlip;
+    }
+
+    /**
+     * the coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param frictionSlip
+     */
+    public void setFrictionSlip(float frictionSlip) {
+        this.frictionSlip = frictionSlip;
+        applyInfo();
+    }
+
+    public float getRollInfluence() {
+        return rollInfluence;
+    }
+
+    /**
+     * reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+     * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+     * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+     * You should also try lowering the vehicle's centre of mass
+     * @param rollInfluence the rollInfluence to set
+     */
+    public void setRollInfluence(float rollInfluence) {
+        this.rollInfluence = rollInfluence;
+        applyInfo();
+    }
+
+    public float getMaxSuspensionTravelCm() {
+        return maxSuspensionTravelCm;
+    }
+
+    /**
+     * the maximum distance the suspension can be compressed (centimetres)
+     * @param maxSuspensionTravelCm
+     */
+    public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+        this.maxSuspensionTravelCm = maxSuspensionTravelCm;
+        applyInfo();
+    }
+
+    public float getMaxSuspensionForce() {
+        return maxSuspensionForce;
+    }
+
+    /**
+     * The maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param maxSuspensionTravelCm
+     */
+    public void setMaxSuspensionForce(float maxSuspensionForce) {
+        this.maxSuspensionForce = maxSuspensionForce;
+        applyInfo();
+    }
+
+    private void applyInfo() {
+        if (wheelId == 0) {
+            return;
+        }
+        applyInfo(wheelId, wheelIndex, suspensionStiffness, wheelsDampingRelaxation, wheelsDampingCompression, frictionSlip, rollInfluence, maxSuspensionTravelCm, maxSuspensionForce, radius, frontWheel, restLength);
+    }
+
+    private native void applyInfo(long wheelId, int wheelIndex,
+            float suspensionStiffness,
+            float wheelsDampingRelaxation,
+            float wheelsDampingCompression,
+            float frictionSlip,
+            float rollInfluence,
+            float maxSuspensionTravelCm,
+            float maxSuspensionForce,
+            float wheelsRadius,
+            boolean frontWheel,
+            float suspensionRestLength);
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void setRadius(float radius) {
+        this.radius = radius;
+        applyInfo();
+    }
+
+    public float getRestLength() {
+        return restLength;
+    }
+
+    public void setRestLength(float restLength) {
+        this.restLength = restLength;
+        applyInfo();
+    }
+
+    /**
+     * returns the object this wheel is in contact with or null if no contact
+     * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject)
+     */
+    public PhysicsCollisionObject getGroundObject() {
+//        if (wheelInfo.raycastInfo.groundObject == null) {
+//            return null;
+//        } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) {
+//            System.out.println("RigidBody");
+//            return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer();
+//        } else {
+        return null;
+//        }
+    }
+
+    /**
+     * returns the location where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionLocation(Vector3f vec) {
+        getCollisionLocation(wheelId, wheelIndex, vec);
+        return vec;
+    }
+
+    private native void getCollisionLocation(long wheelId, int wheelIndex, Vector3f vec);
+
+    /**
+     * returns the location where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionLocation() {
+        Vector3f vec = new Vector3f();
+        getCollisionLocation(wheelId, wheelIndex, vec);
+        return vec;
+    }
+
+    /**
+     * returns the normal where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionNormal(Vector3f vec) {
+        getCollisionNormal(wheelId, wheelIndex, vec);
+        return vec;
+    }
+
+    private native void getCollisionNormal(long wheelId, int wheelIndex, Vector3f vec);
+
+    /**
+     * returns the normal where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionNormal() {
+        Vector3f vec = new Vector3f();
+        getCollisionNormal(wheelId, wheelIndex, vec);
+        return vec;
+    }
+
+    /**
+     * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)<br>
+     * 0.0 = wheels are sliding, 1.0 = wheels have traction.
+     */
+    public float getSkidInfo() {
+        return getSkidInfo(wheelId, wheelIndex);
+    }
+
+    public native float getSkidInfo(long wheelId, int wheelIndex);
+    
+    /**
+     * returns how many degrees the wheel has turned since the last physics
+     * step.
+     */
+    public float getDeltaRotation() {
+        return getDeltaRotation(wheelId, wheelIndex);
+    }
+    
+    public native float getDeltaRotation(long wheelId, int wheelIndex);
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null);
+        frontWheel = capsule.readBoolean("frontWheel", false);
+        location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f());
+        direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f());
+        axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f());
+        suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f);
+        wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f);
+        wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f);
+        frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+        rollInfluence = capsule.readFloat("rollInfluence", 1.0f);
+        maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+        maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+        radius = capsule.readFloat("wheelRadius", 0.5f);
+        restLength = capsule.readFloat("restLength", 1f);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(wheelSpatial, "wheelSpatial", null);
+        capsule.write(frontWheel, "frontWheel", false);
+        capsule.write(location, "wheelLocation", new Vector3f());
+        capsule.write(direction, "wheelDirection", new Vector3f());
+        capsule.write(axle, "wheelAxle", new Vector3f());
+        capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f);
+        capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f);
+        capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f);
+        capsule.write(frictionSlip, "frictionSlip", 10.5f);
+        capsule.write(rollInfluence, "rollInfluence", 1.0f);
+        capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+        capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f);
+        capsule.write(radius, "wheelRadius", 0.5f);
+        capsule.write(restLength, "restLength", 1f);
+    }
+
+    /**
+     * @return the wheelSpatial
+     */
+    public Spatial getWheelSpatial() {
+        return wheelSpatial;
+    }
+
+    /**
+     * @param wheelSpatial the wheelSpatial to set
+     */
+    public void setWheelSpatial(Spatial wheelSpatial) {
+        this.wheelSpatial = wheelSpatial;
+    }
+
+    public boolean isApplyLocal() {
+        return applyLocal;
+    }
+
+    public void setApplyLocal(boolean applyLocal) {
+        this.applyLocal = applyLocal;
+    }
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
new file mode 100644
index 0000000..c60d6c5
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects.infos;
+
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * stores transform info of a PhysicsNode in a threadsafe manner to
+ * allow multithreaded access from the jme scenegraph and the bullet physicsspace
+ * @author normenhansen
+ */
+public class RigidBodyMotionState {
+    long motionStateId = 0;
+    private Vector3f worldLocation = new Vector3f();
+    private Matrix3f worldRotation = new Matrix3f();
+    private Quaternion worldRotationQuat = new Quaternion();
+    private Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private PhysicsVehicle vehicle;
+    private boolean applyPhysicsLocal = false;
+//    protected LinkedList<PhysicsMotionStateListener> listeners = new LinkedList<PhysicsMotionStateListener>();
+
+    public RigidBodyMotionState() {
+        this.motionStateId = createMotionState();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created MotionState {0}", Long.toHexString(motionStateId));
+    }
+
+    private native long createMotionState();
+
+    /**
+     * applies the current transform to the given jme Node if the location has been updated on the physics side
+     * @param spatial
+     */
+    public synchronized boolean applyTransform(Spatial spatial) {
+        Vector3f localLocation = spatial.getLocalTranslation();
+        Quaternion localRotationQuat = spatial.getLocalRotation();
+        boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat);
+        if (!physicsLocationDirty) {
+            return false;
+        }
+        if (!applyPhysicsLocal && spatial.getParent() != null) {
+            localLocation.subtractLocal(spatial.getParent().getWorldTranslation());
+            localLocation.divideLocal(spatial.getParent().getWorldScale());
+            tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+//            localRotationQuat.set(worldRotationQuat);
+            tmp_inverseWorldRotation.mult(localRotationQuat, localRotationQuat);
+
+            spatial.setLocalTranslation(localLocation);
+            spatial.setLocalRotation(localRotationQuat);
+        } else {
+            spatial.setLocalTranslation(localLocation);
+            spatial.setLocalRotation(localRotationQuat);
+//            spatial.setLocalTranslation(worldLocation);
+//            spatial.setLocalRotation(worldRotationQuat);
+        }
+        if (vehicle != null) {
+            vehicle.updateWheels();
+        }
+        return true;
+    }
+
+    private synchronized native boolean applyTransform(long stateId, Vector3f location, Quaternion rotation);
+
+    /**
+     * @return the worldLocation
+     */
+    public Vector3f getWorldLocation() {
+        getWorldLocation(motionStateId, worldLocation);
+        return worldLocation;
+    }
+
+    private native void getWorldLocation(long stateId, Vector3f vec);
+
+    /**
+     * @return the worldRotation
+     */
+    public Matrix3f getWorldRotation() {
+        getWorldRotation(motionStateId, worldRotation);
+        return worldRotation;
+    }
+
+    private native void getWorldRotation(long stateId, Matrix3f vec);
+
+    /**
+     * @return the worldRotationQuat
+     */
+    public Quaternion getWorldRotationQuat() {
+        getWorldRotationQuat(motionStateId, worldRotationQuat);
+        return worldRotationQuat;
+    }
+
+    private native void getWorldRotationQuat(long stateId, Quaternion vec);
+
+    /**
+     * @param vehicle the vehicle to set
+     */
+    public void setVehicle(PhysicsVehicle vehicle) {
+        this.vehicle = vehicle;
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return applyPhysicsLocal;
+    }
+
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        this.applyPhysicsLocal = applyPhysicsLocal;
+    }
+    
+    public long getObjectId(){
+        return motionStateId;
+    }
+//    public void addMotionStateListener(PhysicsMotionStateListener listener){
+//        listeners.add(listener);
+//    }
+//
+//    public void removeMotionStateListener(PhysicsMotionStateListener listener){
+//        listeners.remove(listener);
+//    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing MotionState {0}", Long.toHexString(motionStateId));
+        finalizeNative(motionStateId);
+    }
+
+    private native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java b/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java
new file mode 100644
index 0000000..892c9bd
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects.infos;
+
+/**
+ * 
+ * @author normenhansen
+ */
+public class VehicleTuning {
+    public float suspensionStiffness = 5.88f;
+    public float suspensionCompression = 0.83f;
+    public float suspensionDamping = 0.88f;
+    public float maxSuspensionTravelCm = 500f;
+    public float maxSuspensionForce = 6000f;
+    public float frictionSlip = 10.5f;
+}
diff --git a/engine/src/bullet/com/jme3/bullet/util/DebugMeshCallback.java b/engine/src/bullet/com/jme3/bullet/util/DebugMeshCallback.java
new file mode 100644
index 0000000..9960011
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/util/DebugMeshCallback.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.util;
+
+import com.jme3.math.Vector3f;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class DebugMeshCallback {
+
+    private ArrayList<Vector3f> list = new ArrayList<Vector3f>();
+
+    public void addVector(float x, float y, float z, int part, int index) {
+        list.add(new Vector3f(x, y, z));
+    }
+
+    public FloatBuffer getVertices() {
+        FloatBuffer buf = BufferUtils.createFloatBuffer(list.size() * 3);
+        for (int i = 0; i < list.size(); i++) {
+            Vector3f vector3f = list.get(i);
+            buf.put(vector3f.x);
+            buf.put(vector3f.y);
+            buf.put(vector3f.z);
+        }
+        return buf;
+    }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/util/DebugShapeFactory.java b/engine/src/bullet/com/jme3/bullet/util/DebugShapeFactory.java
new file mode 100644
index 0000000..b03e185
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/util/DebugShapeFactory.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.util;
+
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
+import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
+import com.jme3.math.Matrix3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author CJ Hare, normenhansen
+ */
+public class DebugShapeFactory {
+
+    /** The maximum corner for the aabb used for triangles to include in ConcaveShape processing.*/
+//    private static final Vector3f aabbMax = new Vector3f(1e30f, 1e30f, 1e30f);
+    /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/
+//    private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f);
+
+    /**
+     * Creates a debug shape from the given collision shape. This is mostly used internally.<br>
+     * To attach a debug shape to a physics object, call <code>attachDebugShape(AssetManager manager);</code> on it.
+     * @param collisionShape
+     * @return
+     */
+    public static Spatial getDebugShape(CollisionShape collisionShape) {
+        if (collisionShape == null) {
+            return null;
+        }
+        Spatial debugShape;
+        if (collisionShape instanceof CompoundCollisionShape) {
+            CompoundCollisionShape shape = (CompoundCollisionShape) collisionShape;
+            List<ChildCollisionShape> children = shape.getChildren();
+            Node node = new Node("DebugShapeNode");
+            for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+                ChildCollisionShape childCollisionShape = it.next();
+                CollisionShape ccollisionShape = childCollisionShape.shape;
+                Geometry geometry = createDebugShape(ccollisionShape);
+
+                // apply translation
+                geometry.setLocalTranslation(childCollisionShape.location);
+
+                // apply rotation
+                TempVars vars = TempVars.get();                
+                Matrix3f tempRot = vars.tempMat3;
+
+                tempRot.set(geometry.getLocalRotation());
+                childCollisionShape.rotation.mult(tempRot, tempRot);
+                geometry.setLocalRotation(tempRot);
+
+                vars.release();
+
+                node.attachChild(geometry);
+            }
+            debugShape = node;
+        } else {
+            debugShape = createDebugShape(collisionShape);
+        }
+        if (debugShape == null) {
+            return null;
+        }
+        debugShape.updateGeometricState();
+        return debugShape;
+    }
+
+    private static Geometry createDebugShape(CollisionShape shape) {
+        Geometry geom = new Geometry();
+        geom.setMesh(DebugShapeFactory.getDebugMesh(shape));
+//        geom.setLocalScale(shape.getScale());
+        geom.updateModelBound();
+        return geom;
+    }
+
+    public static Mesh getDebugMesh(CollisionShape shape) {
+        Mesh mesh = new Mesh();
+        mesh = new Mesh();
+        DebugMeshCallback callback = new DebugMeshCallback();
+        getVertices(shape.getObjectId(), callback);
+        mesh.setBuffer(Type.Position, 3, callback.getVertices());
+        mesh.getFloatBuffer(Type.Position).clear();
+        return mesh;
+    }
+
+    private static native void getVertices(long shapeId, DebugMeshCallback buffer);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/util/NativeMeshUtil.java b/engine/src/bullet/com/jme3/bullet/util/NativeMeshUtil.java
new file mode 100644
index 0000000..4e14b8e
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/util/NativeMeshUtil.java
@@ -0,0 +1,77 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet.util;

+

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.BufferUtils;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+

+/**

+ *

+ * @author normenhansen

+ */

+public class NativeMeshUtil {

+    

+    public static long getTriangleIndexVertexArray(Mesh mesh){

+        ByteBuffer triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4);

+        ByteBuffer vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4);

+        int numVertices = mesh.getVertexCount();

+        int vertexStride = 12; //3 verts * 4 bytes per.

+        int numTriangles = mesh.getTriangleCount();

+        int triangleIndexStride = 12; //3 index entries * 4 bytes each.

+

+        IndexBuffer indices = mesh.getIndicesAsList();

+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);

+        vertices.rewind();

+

+        int verticesLength = mesh.getVertexCount() * 3;

+        for (int i = 0; i < verticesLength; i++) {

+            float tempFloat = vertices.get();

+            vertexBase.putFloat(tempFloat);

+        }

+

+        int indicesLength = mesh.getTriangleCount() * 3;

+        for (int i = 0; i < indicesLength; i++) {

+            triangleIndexBase.putInt(indices.get(i));

+        }

+        vertices.rewind();

+        vertices.clear();

+

+        return createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride);

+    }

+    

+    public static native long createTriangleIndexVertexArray(ByteBuffer triangleIndexBase, ByteBuffer vertexBase, int numTraingles, int numVertices, int vertextStride, int triangleIndexStride);

+    

+}

diff --git a/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.frag b/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.frag
new file mode 100644
index 0000000..35ba78b
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.frag
@@ -0,0 +1,24 @@
+uniform sampler2D m_Texture; // this should hold the texture rendered by the horizontal blur pass

+uniform float m_Size;

+uniform float m_Scale;

+

+varying vec2 texCoord;

+

+void main(){ 

+   float blurSize = m_Scale/m_Size;

+   vec4 sum = vec4(0.0);

+

+   // blur in x (vertical)

+   // take nine samples, with the distance blurSize between them

+   sum += texture2D(m_Texture, vec2(texCoord.x- 4.0*blurSize, texCoord.y )) * 0.05;

+   sum += texture2D(m_Texture, vec2(texCoord.x- 3.0*blurSize, texCoord.y )) * 0.09;

+   sum += texture2D(m_Texture, vec2(texCoord.x - 2.0*blurSize, texCoord.y)) * 0.12;

+   sum += texture2D(m_Texture, vec2(texCoord.x- blurSize, texCoord.y )) * 0.15;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y)) * 0.16;

+   sum += texture2D(m_Texture, vec2(texCoord.x+ blurSize, texCoord.y )) * 0.15;

+   sum += texture2D(m_Texture, vec2(texCoord.x+ 2.0*blurSize, texCoord.y )) * 0.12;

+   sum += texture2D(m_Texture, vec2(texCoord.x+ 3.0*blurSize, texCoord.y )) * 0.09;

+   sum += texture2D(m_Texture, vec2(texCoord.x+ 4.0*blurSize, texCoord.y )) * 0.05;

+

+   gl_FragColor = sum;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.j3md b/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.j3md
new file mode 100644
index 0000000..ddb262e
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/HGaussianBlur.j3md
@@ -0,0 +1,21 @@
+MaterialDef Bloom {

+

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Float Size

+        Float Scale

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Blur/HGaussianBlur.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.frag b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.frag
new file mode 100644
index 0000000..b3adfd4
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.frag
@@ -0,0 +1,47 @@
+uniform sampler2D m_Texture;

+uniform float m_SampleDist;

+uniform float m_SampleStrength;

+uniform float m_Samples[10];

+varying vec2 texCoord;

+

+void main(void)

+{

+   // some sample positions

+   //float samples[10] =   float[](-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08);

+

+    // 0.5,0.5 is the center of the screen

+    // so substracting texCoord from it will result in

+    // a vector pointing to the middle of the screen

+    vec2 dir = 0.5 - texCoord;

+

+    // calculate the distance to the center of the screen

+    float dist = sqrt(dir.x*dir.x + dir.y*dir.y);

+

+    // normalize the direction (reuse the distance)

+    dir = dir/dist;

+

+    // this is the original colour of this fragment

+    // using only this would result in a nonblurred version

+    vec4 colorRes = texture2D(m_Texture,texCoord);

+

+    vec4 sum = colorRes;

+

+    // take 10 additional blur samples in the direction towards

+    // the center of the screen

+    for (int i = 0; i < 10; i++)

+    {

+      sum += texture2D( m_Texture, texCoord + dir * m_Samples[i] * m_SampleDist );

+    }

+

+    // we have taken eleven samples

+    sum *= 1.0/11.0;

+

+    // weighten the blur effect with the distance to the

+    // center of the screen ( further out is blurred more)

+    float t = dist * m_SampleStrength;

+    t = clamp( t ,0.0,1.0); //0 &lt;= t &lt;= 1

+

+    //Blend the original color with the averaged pixels

+    gl_FragColor =mix( colorRes, sum, t );

+     

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.j3md b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.j3md
new file mode 100644
index 0000000..1e397fa
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur.j3md
@@ -0,0 +1,36 @@
+MaterialDef Radial Blur {

+

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Color Color

+        Float SampleDist

+        Float SampleStrength

+        FloatArray Samples

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Blur/RadialBlur15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+        }

+    }

+

+    Technique {

+        VertexShader GLSL120:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL120: Common/MatDefs/Blur/RadialBlur.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/RadialBlur15.frag b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur15.frag
new file mode 100644
index 0000000..cc40350
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/RadialBlur15.frag
@@ -0,0 +1,50 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform float m_SampleDist;

+uniform float m_SampleStrength;

+uniform float m_Samples[10];

+

+in vec2 texCoord;

+out vec4 outFragColor;

+

+void main(void)

+{

+   // some sample positions

+   //float samples[10] =   float[](-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08);

+

+    // 0.5,0.5 is the center of the screen

+    // so substracting texCoord from it will result in

+    // a vector pointing to the middle of the screen

+    vec2 dir = 0.5 - texCoord;

+

+    // calculate the distance to the center of the screen

+    float dist = sqrt(dir.x*dir.x + dir.y*dir.y);

+

+    // normalize the direction (reuse the distance)

+    dir = dir/dist;

+

+    // this is the original colour of this fragment

+    // using only this would result in a nonblurred version

+    vec4 colorRes = getColor(m_Texture,texCoord);

+

+    vec4 sum = colorRes;

+

+    // take 10 additional blur samples in the direction towards

+    // the center of the screen

+    for (int i = 0; i < 10; i++){

+      sum += getColor( m_Texture, texCoord + dir * m_Samples[i] * m_SampleDist );

+    }

+

+    // we have taken eleven samples

+    sum *= 1.0/11.0;

+

+    // weighten the blur effect with the distance to the

+    // center of the screen ( further out is blurred more)

+    float t = dist * m_SampleStrength;

+    t = clamp( t ,0.0,1.0); //0 &lt;= t &lt;= 1

+

+    //Blend the original color with the averaged pixels

+    outFragColor =mix( colorRes, sum, t );

+     

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.frag b/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.frag
new file mode 100644
index 0000000..3e20fe5
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.frag
@@ -0,0 +1,25 @@
+uniform sampler2D m_Texture; // this should hold the texture rendered by the horizontal blur pass

+uniform float m_Size;

+uniform float m_Scale;

+varying vec2 texCoord;

+

+

+

+void main(void)

+{  float blurSize = m_Scale/m_Size;

+   vec4 sum = vec4(0.0);

+

+   // blur in y (vertical)

+   // take nine samples, with the distance blurSize between them

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y - 4.0*blurSize)) * 0.05;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y - 3.0*blurSize)) * 0.09;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y - 2.0*blurSize)) * 0.12;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y - blurSize)) * 0.15;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y)) * 0.16;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y + blurSize)) * 0.15;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y + 2.0*blurSize)) * 0.12;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y + 3.0*blurSize)) * 0.09;

+   sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y + 4.0*blurSize)) * 0.05;

+

+   gl_FragColor = sum;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.j3md b/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.j3md
new file mode 100644
index 0000000..163f9f3
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Blur/VGaussianBlur.j3md
@@ -0,0 +1,21 @@
+MaterialDef Bloom {

+

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Float Size

+        Float Scale

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Blur/VGaussianBlur.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Gui/Gui.frag b/engine/src/core-data/Common/MatDefs/Gui/Gui.frag
new file mode 100644
index 0000000..caa666c
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Gui/Gui.frag
@@ -0,0 +1,16 @@
+#ifdef TEXTURE

+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+#endif

+

+varying vec4 color;

+

+void main() {

+    #ifdef TEXTURE

+      vec4 texVal = texture2D(m_Texture, texCoord);

+      gl_FragColor = texVal * color;

+    #else

+      gl_FragColor = color;

+    #endif

+}

+

diff --git a/engine/src/core-data/Common/MatDefs/Gui/Gui.j3md b/engine/src/core-data/Common/MatDefs/Gui/Gui.j3md
new file mode 100644
index 0000000..ea871b5
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Gui/Gui.j3md
@@ -0,0 +1,26 @@
+MaterialDef Default GUI {

+

+    MaterialParameters {

+        Texture2D Texture

+        Color Color ( Color )

+        Boolean VertexColor

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Gui/Gui.vert

+        FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            TEXTURE : Texture

+            VERTEX_COLOR : VertexColor

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Gui/Gui.vert b/engine/src/core-data/Common/MatDefs/Gui/Gui.vert
new file mode 100644
index 0000000..0591c5e
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Gui/Gui.vert
@@ -0,0 +1,29 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform vec4 m_Color;

+

+attribute vec3 inPosition;

+

+#ifdef VERTEX_COLOR

+attribute vec4 inColor;

+#endif

+

+#ifdef TEXTURE

+attribute vec2 inTexCoord;

+varying vec2 texCoord;

+#endif

+

+varying vec4 color;

+

+void main() {

+    //vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy;

+    //gl_Position = vec4(pos, 0.0, 1.0);

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+    #ifdef TEXTURE

+        texCoord = inTexCoord;

+    #endif

+    #ifdef VERTEX_COLOR

+        color = m_Color * inColor;

+    #else

+        color = m_Color;

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Hdr/LogLum.frag b/engine/src/core-data/Common/MatDefs/Hdr/LogLum.frag
new file mode 100644
index 0000000..68f08e9
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Hdr/LogLum.frag
@@ -0,0 +1,65 @@
+#import "Common/ShaderLib/Hdr.glsllib"

+

+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+

+#ifdef BLOCKS

+ uniform vec2 m_PixelSize;

+ uniform vec2 m_BlockSize;

+ uniform float m_NumPixels;

+#endif

+

+vec4 blocks(vec2 halfBlockSize, vec2 pixelSize, float numPixels){

+    vec2 startUV = texCoord - halfBlockSize;

+    vec2 endUV = texCoord + halfBlockSize;

+

+    vec4 sum = vec4(0.0);

+    float numPix = 0.0;

+    //float maxLum = 0.0;

+

+    for (float x = startUV.x; x < endUV.x; x += pixelSize.x){

+        for (float y = startUV.y; y < endUV.y; y += pixelSize.y){

+            numPix += 1.0;

+            vec4 color = texture2D(m_Texture, vec2(x,y));

+

+            #ifdef ENCODE_LUM

+            color = HDR_EncodeLum(HDR_GetLum(color.rgb));

+            #endif

+            //#ifdef COMPUTE_MAX

+            //maxLum = max(color.r, maxLum);

+            //#endif

+            sum += color;

+        }

+    }

+    sum /= numPix;

+

+    #ifdef DECODE_LUM

+    sum = vec4(HDR_DecodeLum(sum));

+       //#ifdef COMPUTE_MAX

+       //maxLum = HDR_GetExpLum(maxLum);

+       //#endif

+    #endif

+

+    return sum;

+}

+

+vec4 fetch(){

+    vec4 color = texture2D(m_Texture, texCoord);

+    #ifdef ENCODE_LUM

+       return HDR_EncodeLum(HDR_GetLum(color.rgb));

+    #elif defined DECODE_LUM

+       return vec4(HDR_DecodeLum(color));

+    #else

+       return color;

+    #endif

+}

+

+void main() {

+    #ifdef BLOCKS

+    gl_FragColor = blocks(m_BlockSize * vec2(0.5), m_PixelSize, m_NumPixels);

+    #else

+    gl_FragColor = vec4(fetch());

+    #endif

+}

+

+

diff --git a/engine/src/core-data/Common/MatDefs/Hdr/LogLum.j3md b/engine/src/core-data/Common/MatDefs/Hdr/LogLum.j3md
new file mode 100644
index 0000000..0c4c6c8
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Hdr/LogLum.j3md
@@ -0,0 +1,31 @@
+MaterialDef Log Lum 2D {

+

+    MaterialParameters {

+        Texture2D Texture

+        Vector2 BlockSize

+        Vector2 PixelSize

+        Float NumPixels

+        Boolean DecodeLum

+        Boolean EncodeLum

+        Boolean Blocks

+        Boolean ComputeMax

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Gui/Gui.vert

+        FragmentShader GLSL100: Common/MatDefs/Hdr/LogLum.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            TEXTURE

+            ENCODE_LUM : EncodeLum

+            DECODE_LUM : DecodeLum

+            BLOCKS : Blocks

+            COMPUTE_MAX : ComputeMax

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.frag b/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.frag
new file mode 100644
index 0000000..f221030
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.frag
@@ -0,0 +1,31 @@
+#import "Common/ShaderLib/Hdr.glsllib"

+

+varying vec2 texCoord;

+

+uniform sampler2D m_Texture;

+uniform sampler2D m_Lum;

+uniform sampler2D m_Lum2;

+

+uniform float m_A;

+uniform float m_White;

+uniform float m_BlendFactor;

+uniform float m_Gamma;

+

+void main() {

+    float avgLumA = HDR_DecodeLum( texture2D(m_Lum, vec2(0.0)) );

+    float avgLumB = HDR_DecodeLum( texture2D(m_Lum2, vec2(0.0)) );

+    float lerpedLum = mix(avgLumA, avgLumB, m_BlendFactor);

+

+    vec4 color = texture2D(m_Texture, texCoord);

+    vec3 c1 = HDR_ToneMap(color.rgb, lerpedLum, m_A, m_White);

+    //vec3 c2 = HDR_ToneMap2(color.rgb, lerpedLum, m_A * vec2(0.25), m_White);

+

+    //float l1 = HDR_GetLuminance(c1);

+    //float l2 = HDR_GetLuminance(c2);

+

+    //vec3 final = mix(c2, c1, clamp(l1, 0.0, 1.0));

+

+    //tonedColor = pow(tonedColor, vec3(m_Gamma));

+    gl_FragColor = vec4(c1, color.a);

+}

+

diff --git a/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.j3md b/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.j3md
new file mode 100644
index 0000000..24fbd04
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Hdr/ToneMap.j3md
@@ -0,0 +1,23 @@
+MaterialDef Tone Mapper {

+    MaterialParameters {

+        Texture2D Texture

+        Texture2D Lum

+        Texture2D Lum2

+        Float BlendFactor

+        Float White

+        Float A

+        Float Gamma

+    }

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Gui/Gui.vert

+        FragmentShader GLSL100: Common/MatDefs/Hdr/ToneMap.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            TEXTURE

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/Deferred.frag b/engine/src/core-data/Common/MatDefs/Light/Deferred.frag
new file mode 100644
index 0000000..9fc7ebb
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Deferred.frag
@@ -0,0 +1,146 @@
+#define ATTENUATION

+//#define HQ_ATTENUATION

+

+varying vec2 texCoord;

+

+uniform sampler2D m_DiffuseData;

+uniform sampler2D m_SpecularData;

+uniform sampler2D m_NormalData;

+uniform sampler2D m_DepthData;

+

+uniform vec3 m_FrustumCorner;

+uniform vec2 m_FrustumNearFar;

+

+uniform vec4 g_LightColor;

+uniform vec4 g_LightPosition;

+uniform vec3 g_CameraPosition;

+

+uniform mat4 m_ViewProjectionMatrixInverse;

+

+#ifdef COLORRAMP

+  uniform sampler2D m_ColorRamp;

+#endif

+

+float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){

+    #ifdef MINNAERT

+        float NdotL = max(0.0, dot(norm, lightdir));

+        float NdotV = max(0.0, dot(norm, viewdir));

+        return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5;

+    #else

+        return max(0.0, dot(norm, lightdir));

+    #endif

+}

+

+float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){

+//#ifdef LOW_QUALITY

+       // Blinn-Phong

+       // Note: preferably, H should be computed in the vertex shader

+       vec3 H = (viewdir + lightdir) * vec3(0.5);

+       return pow(max(dot(H, norm), 0.0), shiny);

+/*

+    #elif defined(WARDISO)

+        // Isotropic Ward

+        vec3 halfVec = normalize(viewdir + lightdir);

+        float NdotH  = max(0.001, tangDot(norm, halfVec));

+        float NdotV  = max(0.001, tangDot(norm, viewdir));

+        float NdotL  = max(0.001, tangDot(norm, lightdir));

+        float a      = tan(acos(NdotH));

+        float p      = max(shiny/128.0, 0.001);

+        return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL)));

+    #else

+       // Standard Phong

+       vec3 R = reflect(-lightdir, norm);

+       return pow(max(tangDot(R, viewdir), 0.0), shiny);

+    #endif

+*/

+}

+

+vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec4 wvLightDir, in float shiny){

+   float diffuseFactor  = lightComputeDiffuse(wvNorm, wvLightDir.xyz, wvViewDir);

+   float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir.xyz, shiny);

+   return vec2(diffuseFactor, specularFactor) * vec2(wvLightDir.w);

+}

+

+vec3 decodeNormal(in vec4 enc){

+    vec4 nn = enc * vec4(2.0,2.0,0.0,0.0) + vec4(-1.0,-1.0,1.0,-1.0);

+    float l = dot(nn.xyz, -nn.xyw);

+    nn.z = l;

+    nn.xy *= sqrt(l);

+    return nn.xyz * vec3(2.0) + vec3(0.0,0.0,-1.0);

+}

+

+vec3 getPosition(in vec2 newTexCoord){

+  //Reconstruction from depth

+  float depth = texture2D(m_DepthData, newTexCoord).r;

+  //if (depth == 1.0)

+  //  return vec3(0.0, 0.0, 2.0);

+  //depth = (2.0 * m_FrustumNearFar.x)

+  /// (m_FrustumNearFar.y + m_FrustumNearFar.x - depth * (m_FrustumNearFar.y-m_FrustumNearFar.x));

+

+  //one frustum corner method

+  //float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, newTexCoord.x);

+  //float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, newTexCoord.y);

+

+  //return depth * vec3(x, y, m_FrustumCorner.z);

+  vec4 pos;

+  pos.xy = (newTexCoord * vec2(2.0)) - vec2(1.0);

+  pos.z  = depth;

+  pos.w  = 1.0;

+  pos    = m_ViewProjectionMatrixInverse * pos;

+  //pos   /= pos.w;

+  return pos.xyz;

+}

+

+// JME3 lights in world space

+void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){

+    #ifdef DIR_LIGHT

+        lightDir.xyz = -position.xyz;

+    #else

+        lightDir.xyz = position.xyz - worldPos.xyz;

+        float dist = length(lightDir.xyz);

+        lightDir.w = clamp(1.0 - position.w * dist, 0.0, 1.0);

+        lightDir.xyz /= dist;

+    #endif

+

+/*

+    float posLight = step(0.5, color.w);

+    vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);

+    #ifdef ATTENUATION

+     float dist = length(tempVec);

+     lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);

+     lightDir.xyz = tempVec / vec3(dist);

+     #ifdef HQ_ATTENUATION

+       lightVec = tempVec;

+     #endif

+    #else

+     lightDir = vec4(normalize(tempVec), 1.0);

+    #endif

+*/

+}

+

+void main(){

+    vec2 newTexCoord = texCoord;

+    vec4 diffuseColor = texture2D(m_DiffuseData,  newTexCoord);

+    if (diffuseColor.a == 0.0)

+        discard;

+

+    vec4 specularColor = texture2D(m_SpecularData, newTexCoord);

+    vec3 worldPosition = getPosition(newTexCoord);

+    vec3 viewDir  = normalize(g_CameraPosition - worldPosition);

+

+    vec4 normalInfo = vec4(texture2D(m_NormalData, newTexCoord).rg, 0.0, 0.0);

+    vec3 normal = decodeNormal(normalInfo);

+

+    vec4 lightDir;

+    lightComputeDir(worldPosition, g_LightColor, g_LightPosition, lightDir);

+

+    vec2 light = computeLighting(worldPosition, normal, viewDir, lightDir, specularColor.w*128.0);

+

+    #ifdef COLORRAMP

+        diffuseColor.rgb  *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb;

+        specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb;

+    #endif

+

+    gl_FragColor = vec4(light.x * diffuseColor.xyz + light.y * specularColor.xyz, 1.0);

+    gl_FragColor.xyz *= g_LightColor.xyz;

+}

diff --git a/engine/src/core-data/Common/MatDefs/Light/Deferred.j3md b/engine/src/core-data/Common/MatDefs/Light/Deferred.j3md
new file mode 100644
index 0000000..c386a61
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Deferred.j3md
@@ -0,0 +1,61 @@
+MaterialDef Phong Lighting Deferred {

+

+    MaterialParameters {

+

+        // Use more efficent algorithms to improve performance

+        Boolean LowQuality

+

+        // Improve quality at the cost of performance

+        Boolean HighQuality

+

+        // Activate shading along the tangent, instead of the normal

+        // Requires tangent data to be available on the model.

+        Boolean VTangent

+

+        // Use minnaert diffuse instead of lambert

+        Boolean Minnaert

+

+        // Use ward specular instead of phong

+        Boolean WardIso

+

+        Texture2D DiffuseData

+        Texture2D SpecularData

+        Texture2D NormalData

+        Texture2D DepthData

+

+        Vector3 FrustumCorner

+        Vector2 FrustumNearFar

+        Matrix4 ViewProjectionMatrixInverse

+

+        // Color ramp, will map diffuse and specular values through it.

+        Texture2D ColorRamp

+    }

+

+    Technique {

+        LightMode MultiPass

+

+        VertexShader GLSL100:   Common/MatDefs/Light/Deferred.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Deferred.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            ViewMatrix

+            CameraPosition

+        }

+

+        Defines {

+            ATTENUATION : Attenuation

+            V_TANGENT : VTangent

+            MINNAERT  : Minnaert

+            WARDISO   : WardIso

+            LOW_QUALITY : LowQuality

+            HQ_ATTENUATION : HighQuality

+            COLORRAMP : ColorRamp

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/Deferred.vert b/engine/src/core-data/Common/MatDefs/Light/Deferred.vert
new file mode 100644
index 0000000..0743cc1
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Deferred.vert
@@ -0,0 +1,10 @@
+varying vec2 texCoord;

+

+attribute vec3 inPosition;

+attribute vec2 inTexCoord;

+

+void main(){

+   texCoord = inTexCoord;

+   vec4 pos = vec4(inPosition, 1.0);

+   gl_Position = vec4(sign(pos.xy-vec2(0.5)), 0.0, 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/GBuf.frag b/engine/src/core-data/Common/MatDefs/Light/GBuf.frag
new file mode 100644
index 0000000..3970624
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/GBuf.frag
@@ -0,0 +1,86 @@
+#import "Common/ShaderLib/Optics.glsllib"

+

+uniform float m_Shininess;

+

+varying vec2 texCoord;

+varying vec4 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec4 SpecularSum;

+

+varying float vDepth;

+varying vec3 vNormal;

+

+#ifdef DIFFUSEMAP

+  uniform sampler2D m_DiffuseMap;

+#endif

+

+#ifdef SPECULARMAP

+  uniform sampler2D m_SpecularMap;

+#endif

+

+#ifdef PARALLAXMAP

+  uniform sampler2D m_ParallaxMap;

+#endif

+

+#ifdef NORMALMAP

+  uniform sampler2D m_NormalMap;

+  varying mat3 tbnMat;

+#endif

+

+vec2 encodeNormal(in vec3 n){

+    vec2 enc = normalize(n.xy) * (sqrt(-n.z*0.5+0.5));

+    enc = enc*vec2(0.5)+vec2(0.5);

+    return enc;

+}

+

+void main(){

+    vec2 newTexCoord = texCoord;

+    float height = 0.0;

+    #if defined(PARALLAXMAP) || defined(NORMALMAP_PARALLAX)

+       #ifdef PARALLAXMAP

+          height = texture2D(m_ParallaxMap, texCoord).r;

+       #else

+          height = texture2D(m_NormalMap, texCoord).a;

+       #endif

+       float heightScale = 0.05;

+       float heightBias = heightScale * -0.5;

+       height = (height * heightScale + heightBias);

+    #endif

+

+

+    // ***********************

+    // Read from textures

+    // ***********************

+    #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)

+      vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);

+      vec3 normal = (normalHeight.xyz * vec3(2.0) - vec3(1.0));

+      normal.y = -normal.y;

+

+      normal = tbnMat * normal;

+    #else

+      vec3 normal = vNormal;

+      #if !defined(LOW_QUALITY) && !defined(V_TANGENT)

+         normal = normalize(normal);

+      #endif

+    #endif

+

+    #ifdef DIFFUSEMAP

+      vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord);

+    #else

+      vec4 diffuseColor = vec4(1.0);

+    #endif

+

+    #ifdef SPECULARMAP

+      vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);

+    #else

+      vec4 specularColor = vec4(1.0);

+    #endif

+

+    diffuseColor.rgb  *= DiffuseSum.rgb;

+    specularColor.rgb *= SpecularSum.rgb;

+

+    gl_FragData[0] = vec4(diffuseColor.rgb, 1.0);

+    gl_FragData[1] = vec4(encodeNormal(normal), 0.0, 0.0);

+                          /*encodeNormal(vNormal));*/

+    gl_FragData[2] = vec4(specularColor.rgb, m_Shininess / 128.0);

+}

diff --git a/engine/src/core-data/Common/MatDefs/Light/GBuf.vert b/engine/src/core-data/Common/MatDefs/Light/GBuf.vert
new file mode 100644
index 0000000..f4ad199
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/GBuf.vert
@@ -0,0 +1,71 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldMatrix;

+

+uniform vec4 m_Ambient;

+uniform vec4 m_Diffuse;

+uniform vec4 m_Specular;

+uniform float m_Shininess;

+

+varying vec2 texCoord;

+

+varying vec4 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec4 SpecularSum;

+

+attribute vec3 inPosition;

+attribute vec2 inTexCoord;

+attribute vec3 inNormal;

+

+#ifdef NORMALMAP

+attribute vec3 inTangent;

+varying mat3 tbnMat;

+#endif

+

+#ifdef VERTEX_COLOR

+  attribute vec4 inColor;

+#endif

+

+varying vec3 vNormal;

+varying float vDepth;

+

+void main(){

+   vec4 pos = vec4(inPosition, 1.0);

+   gl_Position = g_WorldViewProjectionMatrix * pos;

+   texCoord = inTexCoord;

+

+   #if defined(NORMALMAP)

+     vec4 wvNormal, wvTangent, wvBinormal;

+

+     wvNormal   = vec4(inNormal, 0.0);

+     wvTangent  = vec4(inTangent, 0.0);

+

+     wvNormal.xyz   = normalize( (g_WorldMatrix * wvNormal).xyz   );

+     wvTangent.xyz  = normalize( (g_WorldMatrix * wvTangent).xyz  );

+     wvBinormal.xyz = cross(wvNormal.xyz, wvTangent.xyz);

+     tbnMat = mat3(wvTangent.xyz, wvBinormal.xyz, wvNormal.xyz);

+

+     vNormal = wvNormal.xyz;

+   #else

+     vec4 wvNormal;

+     #ifdef V_TANGENT

+        wvNormal = vec4(inTangent, 0.0);

+     #else

+        wvNormal = vec4(inNormal, 0.0);

+     #endif

+     vNormal = normalize( (g_WorldMatrix * wvNormal).xyz );

+   #endif

+

+   #ifdef MATERIAL_COLORS

+      AmbientSum  = m_Ambient;

+      DiffuseSum  = m_Diffuse;

+      SpecularSum = m_Specular;

+    #else

+      AmbientSum  = vec4(0.0);

+      DiffuseSum  = vec4(1.0);

+      SpecularSum = vec4(1.0);

+    #endif

+

+    #ifdef VERTEX_COLOR

+      DiffuseSum *= inColor;

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/Glow.frag b/engine/src/core-data/Common/MatDefs/Light/Glow.frag
new file mode 100644
index 0000000..a18a228
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Glow.frag
@@ -0,0 +1,32 @@
+

+#if defined(NEED_TEXCOORD1) 

+    varying vec2 texCoord1;

+#else 

+    varying vec2 texCoord;

+#endif

+

+

+#ifdef HAS_GLOWMAP

+  uniform sampler2D m_GlowMap;

+#endif

+

+#ifdef HAS_GLOWCOLOR

+  uniform vec4 m_GlowColor;

+#endif

+

+

+void main(){

+    #ifdef HAS_GLOWMAP

+        #if defined(NEED_TEXCOORD1) 

+           gl_FragColor = texture2D(m_GlowMap, texCoord1);

+        #else 

+           gl_FragColor = texture2D(m_GlowMap, texCoord);

+        #endif

+    #else

+        #ifdef HAS_GLOWCOLOR

+            gl_FragColor =  m_GlowColor;

+        #else

+            gl_FragColor = vec4(0.0);

+        #endif

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/Lighting.frag b/engine/src/core-data/Common/MatDefs/Light/Lighting.frag
new file mode 100644
index 0000000..76f0e15
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.frag
@@ -0,0 +1,278 @@
+#import "Common/ShaderLib/Parallax.glsllib"

+#import "Common/ShaderLib/Optics.glsllib"

+#define ATTENUATION

+//#define HQ_ATTENUATION

+

+varying vec2 texCoord;

+#ifdef SEPARATE_TEXCOORD

+  varying vec2 texCoord2;

+#endif

+

+varying vec3 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec3 SpecularSum;

+

+#ifndef VERTEX_LIGHTING

+  uniform vec4 g_LightDirection;

+  //varying vec3 vPosition;

+  varying vec3 vViewDir;

+  varying vec4 vLightDir;

+  varying vec3 lightVec;

+#else

+  varying vec2 vertexLightValues;

+#endif

+

+#ifdef DIFFUSEMAP

+  uniform sampler2D m_DiffuseMap;

+#endif

+

+#ifdef SPECULARMAP

+  uniform sampler2D m_SpecularMap;

+#endif

+

+#ifdef PARALLAXMAP

+  uniform sampler2D m_ParallaxMap;  

+#endif

+#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) 

+    uniform float m_ParallaxHeight;

+#endif

+

+#ifdef LIGHTMAP

+  uniform sampler2D m_LightMap;

+#endif

+  

+#ifdef NORMALMAP

+  uniform sampler2D m_NormalMap;   

+#else

+  varying vec3 vNormal;

+#endif

+

+#ifdef ALPHAMAP

+  uniform sampler2D m_AlphaMap;

+#endif

+

+#ifdef COLORRAMP

+  uniform sampler2D m_ColorRamp;

+#endif

+

+uniform float m_AlphaDiscardThreshold;

+

+#ifndef VERTEX_LIGHTING

+uniform float m_Shininess;

+

+#ifdef HQ_ATTENUATION

+uniform vec4 g_LightPosition;

+#endif

+

+#ifdef USE_REFLECTION 

+    uniform float m_ReflectionPower;

+    uniform float m_ReflectionIntensity;

+    varying vec4 refVec;

+

+    uniform ENVMAP m_EnvMap;

+#endif

+

+float tangDot(in vec3 v1, in vec3 v2){

+    float d = dot(v1,v2);

+    #ifdef V_TANGENT

+        d = 1.0 - d*d;

+        return step(0.0, d) * sqrt(d);

+    #else

+        return d;

+    #endif

+}

+

+float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){

+    #ifdef MINNAERT

+        float NdotL = max(0.0, dot(norm, lightdir));

+        float NdotV = max(0.0, dot(norm, viewdir));

+        return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5;

+    #else

+        return max(0.0, dot(norm, lightdir));

+    #endif

+}

+

+float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){

+    // NOTE: check for shiny <= 1 removed since shininess is now 

+    // 1.0 by default (uses matdefs default vals)

+    #ifdef LOW_QUALITY

+       // Blinn-Phong

+       // Note: preferably, H should be computed in the vertex shader

+       vec3 H = (viewdir + lightdir) * vec3(0.5);

+       return pow(max(tangDot(H, norm), 0.0), shiny);

+    #elif defined(WARDISO)

+        // Isotropic Ward

+        vec3 halfVec = normalize(viewdir + lightdir);

+        float NdotH  = max(0.001, tangDot(norm, halfVec));

+        float NdotV  = max(0.001, tangDot(norm, viewdir));

+        float NdotL  = max(0.001, tangDot(norm, lightdir));

+        float a      = tan(acos(NdotH));

+        float p      = max(shiny/128.0, 0.001);

+        return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL)));

+    #else

+       // Standard Phong

+       vec3 R = reflect(-lightdir, norm);

+       return pow(max(tangDot(R, viewdir), 0.0), shiny);

+    #endif

+}

+

+vec2 computeLighting(in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){

+   float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);

+   float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);

+

+   #ifdef HQ_ATTENUATION

+    float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0);

+   #else

+    float att = vLightDir.w;

+   #endif

+

+   specularFactor *= diffuseFactor;

+

+   return vec2(diffuseFactor, specularFactor) * vec2(att);

+}

+#endif

+

+void main(){

+    vec2 newTexCoord;

+     

+    #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) 

+     

+       #ifdef STEEP_PARALLAX

+           #ifdef NORMALMAP_PARALLAX

+               //parallax map is stored in the alpha channel of the normal map         

+               newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);

+           #else

+               //parallax map is a texture

+               newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);         

+           #endif

+       #else

+           #ifdef NORMALMAP_PARALLAX

+               //parallax map is stored in the alpha channel of the normal map         

+               newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);

+           #else

+               //parallax map is a texture

+               newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);

+           #endif

+       #endif

+    #else

+       newTexCoord = texCoord;    

+    #endif

+    

+   #ifdef DIFFUSEMAP

+      vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord);

+    #else

+      vec4 diffuseColor = vec4(1.0);

+    #endif

+

+    float alpha = DiffuseSum.a * diffuseColor.a;

+    #ifdef ALPHAMAP

+       alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r;

+    #endif

+    if(alpha < m_AlphaDiscardThreshold){

+        discard;

+    }

+

+    #ifndef VERTEX_LIGHTING

+        float spotFallOff = 1.0;

+

+        #if __VERSION__ >= 110

+          // allow use of control flow

+          if(g_LightDirection.w != 0.0){

+        #endif

+

+          vec3 L       = normalize(lightVec.xyz);

+          vec3 spotdir = normalize(g_LightDirection.xyz);

+          float curAngleCos = dot(-L, spotdir);             

+          float innerAngleCos = floor(g_LightDirection.w) * 0.001;

+          float outerAngleCos = fract(g_LightDirection.w);

+          float innerMinusOuter = innerAngleCos - outerAngleCos;

+          spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;

+

+          #if __VERSION__ >= 110

+              if(spotFallOff <= 0.0){

+                  gl_FragColor.rgb = AmbientSum * diffuseColor.rgb;

+                  gl_FragColor.a   = alpha;

+                  return;

+              }else{

+                  spotFallOff = clamp(spotFallOff, 0.0, 1.0);

+              }

+             }

+          #else

+             spotFallOff = clamp(spotFallOff, step(g_LightDirection.w, 0.001), 1.0);

+          #endif

+     #endif

+ 

+    // ***********************

+    // Read from textures

+    // ***********************

+    #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)

+      vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);

+      vec3 normal = (normalHeight.xyz * vec3(2.0) - vec3(1.0));

+      #ifdef LATC

+        normal.z = sqrt(1.0 - (normal.x * normal.x) - (normal.y * normal.y));

+      #endif

+      //normal.y = -normal.y;

+    #elif !defined(VERTEX_LIGHTING)

+      vec3 normal = vNormal;

+      #if !defined(LOW_QUALITY) && !defined(V_TANGENT)

+         normal = normalize(normal);

+      #endif

+    #endif

+

+    #ifdef SPECULARMAP

+      vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);

+    #else

+      vec4 specularColor = vec4(1.0);

+    #endif

+

+    #ifdef LIGHTMAP

+       vec3 lightMapColor;

+       #ifdef SEPARATE_TEXCOORD

+          lightMapColor = texture2D(m_LightMap, texCoord2).rgb;

+       #else

+          lightMapColor = texture2D(m_LightMap, texCoord).rgb;

+       #endif

+       specularColor.rgb *= lightMapColor;

+       diffuseColor.rgb  *= lightMapColor;

+    #endif

+

+    #ifdef VERTEX_LIGHTING

+       vec2 light = vertexLightValues.xy;

+       #ifdef COLORRAMP

+           light.x = texture2D(m_ColorRamp, vec2(light.x, 0.0)).r;

+           light.y = texture2D(m_ColorRamp, vec2(light.y, 0.0)).r;

+       #endif

+

+       gl_FragColor.rgb =  AmbientSum     * diffuseColor.rgb + 

+                           DiffuseSum.rgb * diffuseColor.rgb  * vec3(light.x) +

+                           SpecularSum    * specularColor.rgb * vec3(light.y);

+    #else

+       vec4 lightDir = vLightDir;

+       lightDir.xyz = normalize(lightDir.xyz);

+       vec3 viewDir = normalize(vViewDir);

+

+       vec2   light = computeLighting(normal, viewDir, lightDir.xyz) * spotFallOff;

+       #ifdef COLORRAMP

+           diffuseColor.rgb  *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb;

+           specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb;

+       #endif

+

+       // Workaround, since it is not possible to modify varying variables

+       vec4 SpecularSum2 = vec4(SpecularSum, 1.0);

+       #ifdef USE_REFLECTION

+            vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz);

+

+            // Interpolate light specularity toward reflection color

+            // Multiply result by specular map

+            specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor;

+

+            SpecularSum2 = vec4(1.0);

+            light.y = 1.0;

+       #endif

+

+       gl_FragColor.rgb =  AmbientSum       * diffuseColor.rgb  +

+                           DiffuseSum.rgb   * diffuseColor.rgb  * vec3(light.x) +

+                           SpecularSum2.rgb * specularColor.rgb * vec3(light.y);

+    #endif

+    gl_FragColor.a = alpha;

+}

diff --git a/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md b/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md
new file mode 100644
index 0000000..95042b7
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md
@@ -0,0 +1,238 @@
+MaterialDef Phong Lighting {

+

+    MaterialParameters {

+

+        // Compute vertex lighting in the shader

+        // For better performance

+        Boolean VertexLighting

+

+        // Use more efficent algorithms to improve performance

+        Boolean LowQuality

+

+        // Improve quality at the cost of performance

+        Boolean HighQuality

+

+        // Output alpha from the diffuse map

+        Boolean UseAlpha

+

+        // Apha threshold for fragment discarding

+        Float AlphaDiscardThreshold

+

+        // Normal map is in BC5/ATI2n/LATC/3Dc compression format

+        Boolean LATC

+

+        // Use the provided ambient, diffuse, and specular colors

+        Boolean UseMaterialColors

+

+        // Activate shading along the tangent, instead of the normal

+        // Requires tangent data to be available on the model.

+        Boolean VTangent

+

+        // Use minnaert diffuse instead of lambert

+        Boolean Minnaert

+

+        // Use ward specular instead of phong

+        Boolean WardIso

+

+        // Use vertex color as an additional diffuse color.

+        Boolean UseVertexColor

+

+        // Ambient color

+        Color Ambient (MaterialAmbient)

+

+        // Diffuse color

+        Color Diffuse (MaterialDiffuse)

+

+        // Specular color

+        Color Specular (MaterialSpecular)

+

+        // Specular power/shininess

+        Float Shininess (MaterialShininess) : 1

+

+        // Diffuse map

+        Texture2D DiffuseMap

+

+        // Normal map

+        Texture2D NormalMap

+

+        // Specular/gloss map

+        Texture2D SpecularMap

+

+        // Parallax/height map

+        Texture2D ParallaxMap

+

+        //Set to true is parallax map is stored in the alpha channel of the normal map

+        Boolean PackedNormalParallax   

+

+        //Sets the relief height for parallax mapping

+        Float ParallaxHeight : 0.05       

+

+        //Set to true to activate Steep Parallax mapping

+        Boolean SteepParallax

+

+        // Texture that specifies alpha values

+        Texture2D AlphaMap

+

+        // Color ramp, will map diffuse and specular values through it.

+        Texture2D ColorRamp

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+

+        // Set to Use Lightmap

+        Texture2D LightMap

+

+        // Set to use TexCoord2 for the lightmap sampling

+        Boolean SeparateTexCoord

+

+        // The glow color of the object

+        Color GlowColor

+

+        // Parameters for fresnel

+        // X = bias

+        // Y = scale

+        // Z = power

+        Vector3 FresnelParams

+

+        // Env Map for reflection

+        TextureCubeMap EnvMap

+

+        // the env map is a spheremap and not a cube map

+        Boolean EnvMapAsSphereMap

+    }

+

+    Technique {

+

+        LightMode MultiPass

+

+        VertexShader GLSL100:   Common/MatDefs/Light/Lighting.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Lighting.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            NormalMatrix

+            WorldViewMatrix

+            ViewMatrix

+            CameraPosition

+            WorldMatrix

+        }

+

+        Defines {

+            LATC : LATC

+            VERTEX_COLOR : UseVertexColor

+            VERTEX_LIGHTING : VertexLighting

+            ATTENUATION : Attenuation

+            MATERIAL_COLORS : UseMaterialColors

+            V_TANGENT : VTangent

+            MINNAERT  : Minnaert

+            WARDISO   : WardIso

+            LOW_QUALITY : LowQuality

+            HQ_ATTENUATION : HighQuality

+

+            DIFFUSEMAP : DiffuseMap

+            NORMALMAP : NormalMap

+            SPECULARMAP : SpecularMap

+            PARALLAXMAP : ParallaxMap

+            NORMALMAP_PARALLAX : PackedNormalParallax

+            STEEP_PARALLAX : SteepParallax

+            ALPHAMAP : AlphaMap

+            COLORRAMP : ColorRamp

+            LIGHTMAP : LightMap

+            SEPARATE_TEXCOORD : SeparateTexCoord

+

+            USE_REFLECTION : EnvMap

+            SPHERE_MAP : SphereMap

+        }

+    }

+

+    Technique PreShadow {

+

+        VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert

+        FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+        }

+

+        Defines {

+            DIFFUSEMAP_ALPHA : DiffuseMap

+        }

+

+        RenderState {

+            FaceCull Off

+            DepthTest On

+            DepthWrite On

+            PolyOffset 5 0

+            ColorWrite Off

+        }

+

+    }

+

+  Technique PreNormalPass {

+

+        VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert

+        FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            NormalMatrix

+        }

+

+        Defines {

+            DIFFUSEMAP_ALPHA : DiffuseMap

+        }

+

+        RenderState {

+

+        }

+

+    }

+

+    Technique GBuf {

+

+        VertexShader GLSL100:   Common/MatDefs/Light/GBuf.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            NormalMatrix

+            WorldViewMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            VERTEX_COLOR : UseVertexColor

+            MATERIAL_COLORS : UseMaterialColors

+            V_TANGENT : VTangent

+            MINNAERT  : Minnaert

+            WARDISO   : WardIso

+

+            DIFFUSEMAP : DiffuseMap

+            NORMALMAP : NormalMap

+            SPECULARMAP : SpecularMap

+            PARALLAXMAP : ParallaxMap

+        }

+    }

+

+    Technique FixedFunc {

+        LightMode FixedPipeline

+    }

+

+    Technique Glow {

+

+        VertexShader GLSL100:   Common/MatDefs/Misc/SimpleTextured.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Light/Lighting.vert b/engine/src/core-data/Common/MatDefs/Light/Lighting.vert
new file mode 100644
index 0000000..042cba9
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.vert
@@ -0,0 +1,207 @@
+#define ATTENUATION

+//#define HQ_ATTENUATION

+

+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldViewMatrix;

+uniform mat3 g_NormalMatrix;

+uniform mat4 g_ViewMatrix;

+

+uniform vec4 m_Ambient;

+uniform vec4 m_Diffuse;

+uniform vec4 m_Specular;

+uniform float m_Shininess;

+

+uniform vec4 g_LightColor;

+uniform vec4 g_LightPosition;

+uniform vec4 g_AmbientLightColor;

+

+varying vec2 texCoord;

+#ifdef SEPARATE_TEXCOORD

+  varying vec2 texCoord2;

+  attribute vec2 inTexCoord2;

+#endif

+

+varying vec3 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec3 SpecularSum;

+

+attribute vec3 inPosition;

+attribute vec2 inTexCoord;

+attribute vec3 inNormal;

+

+varying vec3 lightVec;

+//varying vec4 spotVec;

+

+#ifdef VERTEX_COLOR

+  attribute vec4 inColor;

+#endif

+

+#ifndef VERTEX_LIGHTING

+  attribute vec4 inTangent;

+

+  #ifndef NORMALMAP

+    varying vec3 vNormal;

+  #endif

+  //varying vec3 vPosition;

+  varying vec3 vViewDir;

+  varying vec4 vLightDir;

+#else

+  varying vec2 vertexLightValues;

+  uniform vec4 g_LightDirection;

+#endif

+

+#ifdef USE_REFLECTION

+    uniform vec3 g_CameraPosition;

+    uniform mat4 g_WorldMatrix;

+

+    uniform vec3 m_FresnelParams;

+    varying vec4 refVec;

+

+

+    /**

+     * Input:

+     * attribute inPosition

+     * attribute inNormal

+     * uniform g_WorldMatrix

+     * uniform g_CameraPosition

+     *

+     * Output:

+     * varying refVec

+     */

+    void computeRef(){

+        vec3 worldPos = (g_WorldMatrix * vec4(inPosition,1.0)).xyz;

+

+        vec3 I = normalize( g_CameraPosition - worldPos  ).xyz;

+        vec3 N = normalize( (g_WorldMatrix * vec4(inNormal, 0.0)).xyz );

+

+        refVec.xyz = reflect(I, N);

+        refVec.w   = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + dot(I, N), m_FresnelParams.z);

+    }

+#endif

+

+// JME3 lights in world space

+void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){

+    float posLight = step(0.5, color.w);

+    vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);

+    lightVec = tempVec;  

+    #ifdef ATTENUATION

+     float dist = length(tempVec);

+     lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);

+     lightDir.xyz = tempVec / vec3(dist);

+    #else

+     lightDir = vec4(normalize(tempVec), 1.0);

+    #endif

+}

+

+#ifdef VERTEX_LIGHTING

+  float lightComputeDiffuse(in vec3 norm, in vec3 lightdir){

+      return max(0.0, dot(norm, lightdir));

+  }

+

+  float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){

+      if (shiny <= 1.0){

+          return 0.0;

+      }

+      #ifndef LOW_QUALITY

+        vec3 H = (viewdir + lightdir) * vec3(0.5);

+        return pow(max(dot(H, norm), 0.0), shiny);

+      #else

+        return 0.0;

+      #endif

+  }

+

+vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec4 wvLightPos){

+     vec4 lightDir;

+     lightComputeDir(wvPos, g_LightColor, wvLightPos, lightDir);

+     float spotFallOff = 1.0;

+     if(g_LightDirection.w != 0.0){

+          vec3 L=normalize(lightVec.xyz);

+          vec3 spotdir = normalize(g_LightDirection.xyz);

+          float curAngleCos = dot(-L, spotdir);    

+          float innerAngleCos = floor(g_LightDirection.w) * 0.001;

+          float outerAngleCos = fract(g_LightDirection.w);

+          float innerMinusOuter = innerAngleCos - outerAngleCos;

+          spotFallOff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0);

+     }

+     float diffuseFactor = lightComputeDiffuse(wvNorm, lightDir.xyz);

+     float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, lightDir.xyz, m_Shininess);

+     //specularFactor *= step(0.01, diffuseFactor);

+     return vec2(diffuseFactor, specularFactor) * vec2(lightDir.w)*spotFallOff;

+  }

+#endif

+

+void main(){

+   vec4 pos = vec4(inPosition, 1.0);

+   gl_Position = g_WorldViewProjectionMatrix * pos;

+   texCoord = inTexCoord;

+   #ifdef SEPARATE_TEXCOORD

+      texCoord2 = inTexCoord2;

+   #endif

+

+   vec3 wvPosition = (g_WorldViewMatrix * pos).xyz;

+   vec3 wvNormal  = normalize(g_NormalMatrix * inNormal);

+   vec3 viewDir = normalize(-wvPosition);

+  

+       //vec4 lightColor = g_LightColor[gl_InstanceID];

+       //vec4 lightPos   = g_LightPosition[gl_InstanceID];

+       //vec4 wvLightPos = (g_ViewMatrix * vec4(lightPos.xyz, lightColor.w));

+       //wvLightPos.w = lightPos.w;

+

+   vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0)));

+   wvLightPos.w = g_LightPosition.w;

+   vec4 lightColor = g_LightColor;

+

+   #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)

+     vec3 wvTangent = normalize(g_NormalMatrix * inTangent.xyz);

+     vec3 wvBinormal = cross(wvNormal, wvTangent);

+

+     mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal);

+     

+     //vPosition = wvPosition * tbnMat;

+     //vViewDir  = viewDir * tbnMat;

+     vViewDir  = -wvPosition * tbnMat;

+     lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);

+     vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz;

+   #elif !defined(VERTEX_LIGHTING)

+     vNormal = wvNormal;

+

+     //vPosition = wvPosition;

+     vViewDir = viewDir;

+

+     lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);

+

+     #ifdef V_TANGENT

+        vNormal = normalize(g_NormalMatrix * inTangent.xyz);

+        vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal);

+     #endif

+   #endif

+

+   //computing spot direction in view space and unpacking spotlight cos

+//   spotVec = (g_ViewMatrix * vec4(g_LightDirection.xyz, 0.0) );

+//   spotVec.w  = floor(g_LightDirection.w) * 0.001;

+//   lightVec.w = fract(g_LightDirection.w);

+

+   lightColor.w = 1.0;

+   #ifdef MATERIAL_COLORS

+      AmbientSum  = (m_Ambient  * g_AmbientLightColor).rgb;

+      DiffuseSum  =  m_Diffuse  * lightColor;

+      SpecularSum = (m_Specular * lightColor).rgb;

+    #else

+      AmbientSum  = vec3(0.2, 0.2, 0.2) * g_AmbientLightColor.rgb; // Default: ambient color is dark gray

+      DiffuseSum  = lightColor;

+      SpecularSum = vec3(0.0);

+    #endif

+

+    #ifdef VERTEX_COLOR

+      AmbientSum *= inColor.rgb;

+      DiffuseSum *= inColor;

+    #endif

+

+    #ifdef VERTEX_LIGHTING

+       vertexLightValues = computeLighting(wvPosition, wvNormal, viewDir, wvLightPos);

+    #endif

+

+    #ifdef USE_REFLECTION

+        computeRef();

+    #endif 

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.frag b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.frag
new file mode 100644
index 0000000..272f100
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.frag
@@ -0,0 +1,9 @@
+varying vec2 texCoord;

+

+uniform sampler2D m_ColorMap;

+uniform vec4 m_Color;

+

+void main(){

+    vec4 texColor = texture2D(m_ColorMap, texCoord);

+    gl_FragColor = vec4(mix(m_Color.rgb, texColor.rgb, texColor.a), 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.j3md b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.j3md
new file mode 100644
index 0000000..dde8ea8
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.j3md
@@ -0,0 +1,20 @@
+MaterialDef Colored Textured {

+

+    MaterialParameters {

+        Texture2D ColorMap

+        Color Color

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/ColoredTextured.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/ColoredTextured.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.vert b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.vert
new file mode 100644
index 0000000..572d841
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ColoredTextured.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inPosition;

+attribute vec2 inTexCoord;

+

+varying vec2 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Particle.frag b/engine/src/core-data/Common/MatDefs/Misc/Particle.frag
new file mode 100644
index 0000000..08cd2a3
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.frag
@@ -0,0 +1,22 @@
+#ifdef USE_TEXTURE

+uniform sampler2D m_Texture;

+varying vec4 texCoord;

+#endif

+

+varying vec4 color;

+

+void main(){

+    if (color.a <= 0.01)

+        discard;

+

+    #ifdef USE_TEXTURE

+        #ifdef POINT_SPRITE

+            vec2 uv = mix(texCoord.xy, texCoord.zw, gl_PointCoord.xy);

+        #else

+            vec2 uv = texCoord.xy;

+        #endif

+        gl_FragColor = texture2D(m_Texture, uv) * color;

+    #else

+        gl_FragColor = color;

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md b/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md
new file mode 100644
index 0000000..e28d41a
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.j3md
@@ -0,0 +1,89 @@
+MaterialDef Point Sprite {

+

+    MaterialParameters {

+        Texture2D Texture

+        Float Quadratic

+        Boolean PointSprite

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+        // The glow color of the object

+        Color GlowColor

+    }

+

+    Technique {

+

+        VertexShader   GLSL100 : Common/MatDefs/Misc/Particle.vert

+        FragmentShader GLSL120 : Common/MatDefs/Misc/Particle.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            WorldMatrix

+            CameraPosition

+        }

+

+        RenderState {

+            Blend AlphaAdditive

+            DepthWrite Off

+            PointSprite On

+            // AlphaTestFalloff 0.01

+        }

+

+        Defines {

+            USE_TEXTURE : Texture

+            POINT_SPRITE : PointSprite

+        }

+    }

+

+    Technique {

+

+        VertexShader   GLSL100 : Common/MatDefs/Misc/Particle.vert

+        FragmentShader GLSL100 : Common/MatDefs/Misc/Particle.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            WorldMatrix

+            CameraPosition

+        }

+

+        RenderState {

+            Blend AlphaAdditive

+            DepthWrite Off

+        }

+

+        Defines {

+            USE_TEXTURE : Texture

+        }

+    }

+

+    Technique FixedFunc {

+        RenderState {

+            Blend AlphaAdditive

+            // DepthWrite Off

+            // AlphaTestFalloff 0.01

+        }

+    }

+

+   Technique Glow {

+

+        VertexShader GLSL100:   Common/MatDefs/Misc/SimpleTextured.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+        }

+

+        RenderState {

+            PointSprite On

+            Blend AlphaAdditive

+            DepthWrite Off

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Particle.vert b/engine/src/core-data/Common/MatDefs/Misc/Particle.vert
new file mode 100644
index 0000000..9c27336
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Particle.vert
@@ -0,0 +1,42 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inPosition;

+attribute vec4 inColor;

+attribute vec4 inTexCoord;

+

+varying vec4 color;

+

+#ifdef USE_TEXTURE

+varying vec4 texCoord;

+#endif

+

+#ifdef POINT_SPRITE

+uniform mat4 g_WorldViewMatrix;

+uniform mat4 g_WorldMatrix;

+uniform vec3 g_CameraPosition;

+uniform float m_Quadratic;

+const float SIZE_MULTIPLIER = 4.0;

+attribute float inSize;

+#endif

+

+void main(){

+    vec4 pos = vec4(inPosition, 1.0);

+

+    gl_Position = g_WorldViewProjectionMatrix * pos;

+    color = inColor;

+

+    #ifdef USE_TEXTURE

+        texCoord = inTexCoord;

+    #endif

+

+    #ifdef POINT_SPRITE

+        vec4 worldPos = g_WorldMatrix * pos;

+        float d = distance(g_CameraPosition.xyz, worldPos.xyz);

+        gl_PointSize = max(1.0, (inSize * SIZE_MULTIPLIER * m_Quadratic) / d);

+

+        //vec4 worldViewPos = g_WorldViewMatrix * pos;

+        //gl_PointSize = (inSize * SIZE_MULTIPLIER * m_Quadratic)*100.0 / worldViewPos.z;

+

+        color.a *= min(gl_PointSize, 1.0);

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.frag b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.frag
new file mode 100644
index 0000000..93e4882
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.frag
@@ -0,0 +1,5 @@
+varying vec3 normal;

+

+void main(){

+   gl_FragColor = vec4((normal * vec3(0.5)) + vec3(0.5), 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.j3md b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.j3md
new file mode 100644
index 0000000..db480b7
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.j3md
@@ -0,0 +1,10 @@
+MaterialDef Debug Normals {

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/ShowNormals.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/ShowNormals.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.vert b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.vert
new file mode 100644
index 0000000..3813043
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/ShowNormals.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inPosition;

+attribute vec3 inNormal;

+

+varying vec3 normal;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);

+    normal = inNormal;

+}

diff --git a/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.frag b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.frag
new file mode 100644
index 0000000..395f3d1
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.frag
@@ -0,0 +1,27 @@
+#import "Common/ShaderLib/Texture.glsllib"

+

+varying vec2 texCoord;

+

+uniform sampler2D m_ColorMap;

+

+void main(){

+    //Texture_GetColor(m_ColorMap, texCoord)

+    //vec4 color = texture2D(m_ColorMap, texCoord);

+    //color.rgb *= color.a;

+    //gl_FragColor = vec4(color.a);

+

+    #ifdef NORMAL_LATC

+        vec3 newNorm = vec3(texture2D(m_ColorMap, texCoord).ag, 0.0);

+        newNorm = Common_UnpackNormal(newNorm);

+        newNorm.b = sqrt(1.0 - (newNorm.x * newNorm.x) - (newNorm.y * newNorm.y));

+        newNorm = Common_PackNormal(newNorm);

+        gl_FragColor = vec4(newNorm, 1.0);

+    #elif defined(SHOW_ALPHA)

+        gl_FragColor = vec4(texture2D(m_ColorMap, texCoord).a);

+    #else

+        gl_FragColor = Texture_GetColor(m_ColorMap, texCoord);

+    #endif

+    #ifdef NORMALIZE

+        gl_FragColor = vec4(normalize(gl_FragColor.xyz), gl_FragColor.a);

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.j3md b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.j3md
new file mode 100644
index 0000000..53469e2
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.j3md
@@ -0,0 +1,32 @@
+Exception SimpleTextured.j3md has been marked as obsolete. Please use Unshaded.j3md instead.

+

+MaterialDef Plain Texture {

+

+    MaterialParameters {

+        Texture2D ColorMap

+        Boolean YCoCg

+        Boolean LATC

+        Boolean Normalize

+        Boolean ShowAlpha

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/SimpleTextured.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/SimpleTextured.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            DXT_YCOCG : YCoCg

+            NORMAL_LATC : LATC

+            NORMALIZE : Normalize

+            SHOW_ALPHA : ShowAlpha

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.vert b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.vert
new file mode 100644
index 0000000..572d841
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/SimpleTextured.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inPosition;

+attribute vec2 inTexCoord;

+

+varying vec2 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Sky.frag b/engine/src/core-data/Common/MatDefs/Misc/Sky.frag
new file mode 100644
index 0000000..3e36e0a
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Sky.frag
@@ -0,0 +1,11 @@
+#import "Common/ShaderLib/Optics.glsllib"

+

+uniform ENVMAP m_Texture;

+

+varying vec3 direction;

+

+void main() {

+    vec3 dir = normalize(direction);

+    gl_FragColor = Optics_GetEnvColor(m_Texture, direction);

+}

+

diff --git a/engine/src/core-data/Common/MatDefs/Misc/Sky.j3md b/engine/src/core-data/Common/MatDefs/Misc/Sky.j3md
new file mode 100644
index 0000000..919309b
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Sky.j3md
@@ -0,0 +1,27 @@
+MaterialDef Sky Plane {

+    MaterialParameters {

+        TextureCubeMap Texture

+        Boolean SphereMap

+        Vector3 NormalScale

+    }

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/Sky.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/Sky.frag

+

+        RenderState {

+            FaceCull Off

+        }

+

+        WorldParameters {

+            ViewMatrix

+            ProjectionMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            SPHERE_MAP : SphereMap

+        }

+    }

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Sky.vert b/engine/src/core-data/Common/MatDefs/Misc/Sky.vert
new file mode 100644
index 0000000..0426f87
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Sky.vert
@@ -0,0 +1,25 @@
+uniform mat4 g_ViewMatrix;

+uniform mat4 g_ProjectionMatrix;

+uniform mat4 g_WorldMatrix;

+

+uniform vec3 m_NormalScale;

+

+attribute vec3 inPosition;

+attribute vec3 inNormal;

+

+varying vec3 direction;

+

+void main(){

+    // set w coordinate to 0

+    vec4 pos = vec4(inPosition, 0.0);

+

+    // compute rotation only for view matrix

+    pos = g_ViewMatrix * pos;

+

+    // now find projection

+    pos.w = 1.0;

+    gl_Position = g_ProjectionMatrix * pos;

+

+    vec4 normal = vec4(inNormal * m_NormalScale, 0.0);

+    direction = normalize( (g_WorldMatrix * normal).xyz );

+}

diff --git a/engine/src/core-data/Common/MatDefs/Misc/SolidColor.j3md b/engine/src/core-data/Common/MatDefs/Misc/SolidColor.j3md
new file mode 100644
index 0000000..15cdff2
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/SolidColor.j3md
@@ -0,0 +1,44 @@
+Exception SolidColor.j3md has been marked as obsolete. Please use Unshaded.j3md instead.

+

+MaterialDef Solid Color {

+

+    MaterialParameters {

+        Vector4 Color

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+        // The glow color of the object

+        Color GlowColor

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag

+

+        Defines {

+            HAS_COLOR : Color

+        }

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+   Technique Glow {

+

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Unshaded.frag b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.frag
new file mode 100644
index 0000000..ab28778
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.frag
@@ -0,0 +1,50 @@
+uniform vec4 m_Color;

+

+#if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))

+    #define NEED_TEXCOORD1

+#endif

+

+#ifdef HAS_COLORMAP

+    uniform sampler2D m_ColorMap;

+#endif

+

+#ifdef NEED_TEXCOORD1

+    varying vec2 texCoord1;

+#endif

+

+#ifdef HAS_LIGHTMAP

+    uniform sampler2D m_LightMap;

+    #ifdef SEPARATE_TEXCOORD

+        varying vec2 texCoord2;

+    #endif

+#endif

+

+#ifdef HAS_VERTEXCOLOR

+    varying vec4 vertColor;

+#endif

+

+void main(){

+    vec4 color = vec4(1.0);

+

+    #ifdef HAS_COLORMAP

+        color *= texture2D(m_ColorMap, texCoord1);

+    #endif

+

+    #ifdef HAS_VERTEXCOLOR

+        color *= vertColor;

+    #endif

+

+    #ifdef HAS_COLOR

+        color *= m_Color;

+    #endif

+

+    #ifdef HAS_LIGHTMAP

+        #ifdef SEPARATE_TEXCOORD

+            color.rgb *= texture2D(m_LightMap, texCoord2).rgb;

+        #else

+            color.rgb *= texture2D(m_LightMap, texCoord1).rgb;

+        #endif

+    #endif

+

+    gl_FragColor = color;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md
new file mode 100644
index 0000000..5a33d44
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.j3md
@@ -0,0 +1,70 @@
+MaterialDef Unshaded {

+

+    MaterialParameters {

+        Texture2D ColorMap

+        Texture2D LightMap

+        Color Color ( Color )

+        Boolean VertexColor

+        Boolean SeparateTexCoord

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+        // The glow color of the object

+        Color GlowColor

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            SEPARATE_TEXCOORD : SeparateTexCoord

+            HAS_COLORMAP : ColorMap

+            HAS_LIGHTMAP : LightMap

+            HAS_VERTEXCOLOR : VertexColor

+            HAS_COLOR : Color

+        }

+    }

+

+      Technique PreNormalPass {

+

+            VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert

+            FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

+

+            WorldParameters {

+                WorldViewProjectionMatrix

+                WorldViewMatrix

+                NormalMatrix

+            }

+

+            RenderState {

+

+            }

+

+        }

+

+

+    Technique Glow {

+

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+            HAS_COLORMAP // Must be passed so that Unshaded.vert exports texCoord.

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/Unshaded.vert b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.vert
new file mode 100644
index 0000000..7bf9f7e
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/Unshaded.vert
@@ -0,0 +1,37 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+attribute vec3 inPosition;

+

+#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))

+    #define NEED_TEXCOORD1

+#endif

+

+#ifdef NEED_TEXCOORD1

+    attribute vec2 inTexCoord;

+    varying vec2 texCoord1;

+#endif

+

+#ifdef SEPARATE_TEXCOORD

+    attribute vec2 inTexCoord2;

+    varying vec2 texCoord2;

+#endif

+

+#ifdef HAS_VERTEXCOLOR

+    attribute vec4 inColor;

+    varying vec4 vertColor;

+#endif

+

+void main(){

+    #ifdef NEED_TEXCOORD1

+        texCoord1 = inTexCoord;

+    #endif

+

+    #ifdef SEPARATE_TEXCOORD

+        texCoord2 = inTexCoord2;

+    #endif

+

+    #ifdef HAS_VERTEXCOLOR

+        vertColor = inColor;

+    #endif

+

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/VertexColor.j3md b/engine/src/core-data/Common/MatDefs/Misc/VertexColor.j3md
new file mode 100644
index 0000000..d5b3eba
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/VertexColor.j3md
@@ -0,0 +1,18 @@
+Exception VertexColor.j3md has been marked as obsolete. Please use Unshaded.j3md instead.

+

+MaterialDef Vertex Color {

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag

+

+        Defines {

+            HAS_VERTEXCOLOR

+        }

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Misc/WireColor.j3md b/engine/src/core-data/Common/MatDefs/Misc/WireColor.j3md
new file mode 100644
index 0000000..51a4733
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Misc/WireColor.j3md
@@ -0,0 +1,38 @@
+Exception WireColor.j3md has been marked as obsolete. Please use Unshaded.j3md instead.

+

+MaterialDef Wire Color {

+

+    MaterialParameters {

+        Vector4 Color : Color

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Misc/Unshaded.vert

+        FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag

+

+        RenderState {

+            FaceCull Off

+            Blend Alpha

+            AlphaTestFalloff 0.01

+            Wireframe On

+        }

+

+        Defines {

+            HAS_COLOR : Color

+        }

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+        RenderState {

+            FaceCull Off

+            Blend Alpha

+            AlphaTestFalloff 0.01

+            Wireframe On

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.frag b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.frag
new file mode 100644
index 0000000..8c3d133
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.frag
@@ -0,0 +1,12 @@
+#import "Common/ShaderLib/Shadow.glsllib"

+

+uniform SHADOWMAP m_ShadowMap;

+varying vec4 projCoord;

+

+void main() {

+   vec4 coord = projCoord;

+   coord.xyz /= coord.w;

+   float shad = Shadow_GetShadow(m_ShadowMap, coord) * 0.7 + 0.3;

+   gl_FragColor = vec4(shad,shad,shad,1.0);

+}

+

diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.j3md b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.j3md
new file mode 100644
index 0000000..0fc8bfc
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.j3md
@@ -0,0 +1,26 @@
+MaterialDef Post Shadow {

+

+    MaterialParameters {

+        Texture2D ShadowMap

+        Matrix4 LightViewProjectionMatrix

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadow.vert

+        FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            NO_SHADOW2DPROJ

+        }

+

+        RenderState {

+            Blend Modulate

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.vert b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.vert
new file mode 100644
index 0000000..3f09753
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadow.vert
@@ -0,0 +1,31 @@
+uniform mat4 m_LightViewProjectionMatrix;

+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldMatrix;

+

+varying vec4 projCoord;

+

+attribute vec3 inPosition;

+

+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(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+

+    // get the vertex in world space

+    vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);

+

+    // convert vertex to light viewProj space

+    //projCoord = biasMat * (m_LightViewProjectionMatrix * worldPos);

+    vec4 coord = m_LightViewProjectionMatrix * worldPos;

+    projCoord = biasMat * coord;

+    //projCoord.z /= gl_DepthRange.far;

+    //projCoord = (m_LightViewProjectionMatrix * worldPos);

+    //projCoord /= projCoord.w;

+    //projCoord.xy = projCoord.xy * vec2(0.5, -0.5) + vec2(0.5);

+

+    // bias from [-1, 1] to [0, 1] for sampling shadow map

+    //projCoord = (projCoord.xyzw * vec4(0.5)) + vec4(0.5);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag
new file mode 100644
index 0000000..ab5993b
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag
@@ -0,0 +1,119 @@
+#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 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

+#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_DoPCF

+    #define KERNEL 8.0

+#endif

+

+uniform SHADOWMAP m_ShadowMap0;

+uniform SHADOWMAP m_ShadowMap1;

+uniform SHADOWMAP m_ShadowMap2;

+uniform SHADOWMAP m_ShadowMap3;

+

+uniform vec4 m_Splits;

+

+uniform float m_ShadowIntensity;

+

+varying vec4 projCoord0;

+varying vec4 projCoord1;

+varying vec4 projCoord2;

+varying vec4 projCoord3;

+

+varying float shadowPosition;

+

+const float texSize = 1024.0;

+const float pixSize = 1.0 / texSize;

+const vec2 pixSize2 = vec2(pixSize);

+

+float Shadow_DoShadowCompareOffset(in SHADOWMAP tex, in vec4 projCoord, in vec2 offset){

+    vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2, projCoord.zw);

+    return SHADOWCOMPARE(tex, coord);

+}

+

+float Shadow_DoShadowCompare(in SHADOWMAP tex, vec4 projCoord){

+    return SHADOWCOMPARE(tex, projCoord);

+}

+

+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_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){

+    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 ;

+    return shadow;

+}

+

+float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){

+    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 * texSize );

+    vec2 mx = mix( gather.xz, gather.yw, f.x );

+    return mix( mx.x, mx.y, f.y );

+}

+

+float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){

+    float shadow = 0.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)) +

+                            Shadow_BorderCheck(projCoord.xy),

+                            0.0, 1.0);

+        }

+    }

+

+    shadow = shadow / (KERNEL * KERNEL);

+    return shadow;

+}

+

+void main(){

+    vec4 shadowPerSplit = vec4(0.0);

+    shadowPerSplit.x = GETSHADOW(m_ShadowMap0, projCoord0);

+    shadowPerSplit.y = GETSHADOW(m_ShadowMap1, projCoord1);

+    shadowPerSplit.z = GETSHADOW(m_ShadowMap2, projCoord2);

+    shadowPerSplit.w = GETSHADOW(m_ShadowMap3, projCoord3);

+

+    vec4 less = step( shadowPosition, m_Splits );

+    vec4 more = vec4(1.0) - step( shadowPosition, vec4(0.0, m_Splits.xyz) );

+    float shadow = dot(shadowPerSplit, less * more );

+    

+    shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);

+    gl_FragColor = vec4(shadow, shadow, shadow, 1.0);

+}

+

diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md
new file mode 100644
index 0000000..8b75748
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md
@@ -0,0 +1,63 @@
+MaterialDef Post Shadow {

+

+    MaterialParameters {

+        Int FilterMode

+        Boolean HardwareShadows

+

+        Texture2D ShadowMap0

+        Texture2D ShadowMap1

+        Texture2D ShadowMap2

+        Texture2D ShadowMap3

+        

+        Float ShadowIntensity

+        Vector4 Splits

+

+        Matrix4 LightViewProjectionMatrix0

+        Matrix4 LightViewProjectionMatrix1

+        Matrix4 LightViewProjectionMatrix2

+        Matrix4 LightViewProjectionMatrix3

+

+        Float PCFEdge

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadowPSSM.vert

+        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowPSSM15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            HARDWARE_SHADOWS : HardwareShadows

+            FILTER_MODE : FilterMode

+            PCFEDGE : PCFEdge

+        }

+

+        RenderState {

+            Blend Modulate

+        }

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadowPSSM.vert

+        FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowPSSM.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            HARDWARE_SHADOWS : HardwareShadows

+            FILTER_MODE : FilterMode

+            PCFEDGE : PCFEdge

+        }

+

+        RenderState {

+            Blend Modulate

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert
new file mode 100644
index 0000000..b1c1690
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert
@@ -0,0 +1,37 @@
+uniform mat4 m_LightViewProjectionMatrix0;

+uniform mat4 m_LightViewProjectionMatrix1;

+uniform mat4 m_LightViewProjectionMatrix2;

+uniform mat4 m_LightViewProjectionMatrix3;

+

+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldMatrix;

+

+varying vec4 projCoord0;

+varying vec4 projCoord1;

+varying vec4 projCoord2;

+varying vec4 projCoord3;

+

+varying float shadowPosition;

+

+attribute vec3 inPosition;

+

+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(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+

+    shadowPosition = gl_Position.z;

+    // get the vertex in world space

+    vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);

+

+

+    // 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;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag
new file mode 100644
index 0000000..0dc1ddf
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag
@@ -0,0 +1,139 @@
+// 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 SHADOWCOMPARE
+    #define KERNEL 1
+#elif FILTER_MODE == 1
+    #ifdef HARDWARE_SHADOWS
+        #define GETSHADOW SHADOWCOMPARE
+    #else
+        #define GETSHADOW Shadow_DoBilinear_2x2
+    #endif
+    #define KERNEL 1
+#elif FILTER_MODE == 2
+    #define GETSHADOW Shadow_DoDither_2x2
+    #define KERNEL 1
+#elif FILTER_MODE == 3
+    #define GETSHADOW Shadow_DoPCF
+    #define KERNEL 4
+#elif FILTER_MODE == 4
+    #define GETSHADOW Shadow_DoPCF
+    #define KERNEL 8
+#endif
+
+out vec4 outFragColor;
+
+uniform SHADOWMAP m_ShadowMap0;
+uniform SHADOWMAP m_ShadowMap1;
+uniform SHADOWMAP m_ShadowMap2;
+uniform SHADOWMAP m_ShadowMap3;
+
+uniform vec4 m_Splits;
+uniform float m_ShadowIntensity;
+
+in vec4 projCoord0;
+in vec4 projCoord1;
+in vec4 projCoord2;
+in vec4 projCoord3;
+in float shadowPosition;
+
+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_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
+    float border = Shadow_BorderCheck(projCoord.xy);
+    if (border > 0.0)
+        return 1.0;
+
+    ivec2 texSize = textureSize(tex, 0);
+    vec2 pixSize = 1.0 / vec2(texSize);
+
+    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;
+
+    ivec2 texSize = textureSize(tex, 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, 0));
+        gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
+        gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
+        gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
+   #endif
+
+   vec2 f = fract( projCoord.xy * texSize );
+   vec2 mx = mix( gather.xz, gather.yw, f.x );
+   return mix( mx.x, mx.y, f.y );
+}
+
+float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
+    float pixSize = 1.0 / textureSize(tex,0).x;
+
+    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;
+}
+
+void main(){
+    float shadow = 0.0;
+
+    if(shadowPosition < m_Splits.x){
+        shadow = GETSHADOW(m_ShadowMap0, projCoord0);
+    }else if( shadowPosition <  m_Splits.y){
+        shadow = GETSHADOW(m_ShadowMap1, projCoord1);
+    }else if( shadowPosition <  m_Splits.z){
+        shadow = GETSHADOW(m_ShadowMap2, projCoord2);
+    }else if( shadowPosition <  m_Splits.w){
+        shadow = GETSHADOW(m_ShadowMap3, projCoord3);
+    }
+    
+    shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
+    outFragColor = vec4(shadow, shadow, shadow, 1.0);
+}
+
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.frag b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.frag
new file mode 100644
index 0000000..5d957e9
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.frag
@@ -0,0 +1,15 @@
+varying vec2 texCoord;

+

+#ifdef DIFFUSEMAP_ALPHA

+uniform sampler2D m_DiffuseMap;

+#endif

+

+

+void main(){

+   #ifdef DIFFUSEMAP_ALPHA

+      if (texture2D(m_DiffuseMap, texCoord).a <= 0.50)

+          discard;

+   #endif

+

+   gl_FragColor = vec4(1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.j3md b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.j3md
new file mode 100644
index 0000000..070949f
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.j3md
@@ -0,0 +1,19 @@
+MaterialDef Pre Shadow {

+    Technique {

+        VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert

+        FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+        }

+

+        RenderState {

+            FaceCull Off

+            DepthTest On

+            DepthWrite On

+            PolyOffset 5 0

+            ColorWrite Off

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.vert b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.vert
new file mode 100644
index 0000000..fdd3a25
--- /dev/null
+++ b/engine/src/core-data/Common/MatDefs/Shadow/PreShadow.vert
@@ -0,0 +1,12 @@
+attribute vec4 inPosition;

+attribute vec2 inTexCoord;

+

+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldViewMatrix;

+

+varying vec2 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * inPosition;

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/Materials/RedColor.j3m b/engine/src/core-data/Common/Materials/RedColor.j3m
new file mode 100644
index 0000000..c7c8e77
--- /dev/null
+++ b/engine/src/core-data/Common/Materials/RedColor.j3m
@@ -0,0 +1,5 @@
+Material Red Color : Common/MatDefs/Misc/Unshaded.j3md {

+     MaterialParameters {

+         Color : 1 0 0 1

+     }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/Materials/VertexColor.j3m b/engine/src/core-data/Common/Materials/VertexColor.j3m
new file mode 100644
index 0000000..ae72092
--- /dev/null
+++ b/engine/src/core-data/Common/Materials/VertexColor.j3m
@@ -0,0 +1,5 @@
+Material Vertex Color Ext : Common/MatDefs/Misc/Unshaded.j3md {

+    MaterialParameters {

+        VertexColor : true

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/Materials/WhiteColor.j3m b/engine/src/core-data/Common/Materials/WhiteColor.j3m
new file mode 100644
index 0000000..1a5d78e
--- /dev/null
+++ b/engine/src/core-data/Common/Materials/WhiteColor.j3m
@@ -0,0 +1,5 @@
+Material White Color : Common/MatDefs/Misc/Unshaded.j3md {

+     MaterialParameters {

+         Color : 1 1 1 1

+     }

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Bump.glsllib b/engine/src/core-data/Common/ShaderLib/Bump.glsllib
new file mode 100644
index 0000000..6b9149b
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Bump.glsllib
@@ -0,0 +1,44 @@
+#define SCALE 0.12

+#define BIAS -0.04

+#define BIN_ITER 5

+

+#ifndef BUMP_HQ

+    #define LIN_ITER 5

+#endif

+

+vec2 Bump_DoOcclusionParallax(in sampler2D heightMap, in vec2 texCoord, in vec3 tanViewDir){

+    float size = 1.0 / float(BIN_ITER);

+

+     // depth

+    float d = 1.0;

+    // best depth

+    float bd = 0.0;

+

+    #ifdef BUMP_HQ

+        const int N = 8;

+        int LIN_ITER = mix(2 * N, N, tanViewDir.z);

+    #endif

+

+    // search from front to back

+    for (int i = 0; i < LIN_ITER; i++){

+        d -= dstep;

+        float h = texture2D(heightMap, dp + ds * (1.0 - d)).a;

+        if (bd < 0.005) // if no depth found yet

+        if (d <= h) bd = depth; // best depth

+    }

+

+    for (int i = 0; i < BIN_ITER; i++) {

+        size *= 0.5;

+        float t = texture2D(heightMap, dp + ds * (1.0 - d)).a;

+        if (d <= t) {

+            bd = depth;

+            d += 2 * size;

+        }

+        d -= size;

+    }

+}

+

+vec2 Bump_DoParallax(in sampler2D heightMap, in vec2 texCoord, in vec3 tanViewDir){

+    float h = texture2D(heightMap, texCoord).a * SCALE + BIAS;

+    return texCoord + h * tanViewDir.xy;

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Common.glsllib b/engine/src/core-data/Common/ShaderLib/Common.glsllib
new file mode 100644
index 0000000..8dce15b
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Common.glsllib
@@ -0,0 +1,13 @@
+vec3 Common_UnpackNormal(in vec3 norm){

+    return (norm * vec3(2.0)) - vec3(1.0);

+}

+

+vec3 Common_UnpackNormalLA(in vec4 norm){

+    vec3 newNorm = norm.agb;

+    newNorm.b = sqrt(1.0 - (newNorm.x * newNorm.x) - (newNorm.y * newNorm.y));

+    return (newNorm * vec3(2.0)) - vec3(1.0);

+}

+

+vec3 Common_PackNormal(in vec3 norm){

+    return (norm * vec3(0.5)) + vec3(0.5);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Fog.glsllib b/engine/src/core-data/Common/ShaderLib/Fog.glsllib
new file mode 100644
index 0000000..0a28362
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Fog.glsllib
@@ -0,0 +1,41 @@
+#ifdef FOG

+

+#ifdef FOG_TEXTURE

+uniform sampler2D m_FogTexture;

+#endif

+

+uniform vec3 m_FogColor;

+

+// x == density

+// y == factor

+// z == ystart

+// w == yend

+uniform vec4 m_FogParams;

+

+varying vec3 fogCoord;

+

+void Fog_PerVertex(inout vec4 color, in vec3 wvPosition){

+    float density = g_FogParams.x;

+    float factor  = g_FogParams.y;

+    float dist    = length(wvPosition.xyz);

+

+    float yf = wvPosition.y;

+    float y0 = g_FogParams.z;

+    float y1 = g_FogParams.w;

+    float yh = (y1 - y0) * 0.5;

+

+    float fogAmt1 = max(step(yh, 0.0), smoothstep(0, yh, max(y1-yf, yf-y0)));

+    float fogAmt2 = exp(-density * density * dist * dist);

+

+    color.rgb = mix(color.rgb, m_FogColor, fogAmt1 * fogAmt2);

+}

+

+void Fog_PerPixel(inout vec4 color){

+    Fog_PerVertex(color, fogCoord);

+}

+

+void Fog_WVPos(in vec4 wvPosition){

+    fogCoord = wvPosition.xyz;

+}

+

+#endif
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Hdr.glsllib b/engine/src/core-data/Common/ShaderLib/Hdr.glsllib
new file mode 100644
index 0000000..5db1423
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Hdr.glsllib
@@ -0,0 +1,65 @@
+const float epsilon = 0.0001;

+const vec3 lumConv = vec3(0.27, 0.67, 0.06);

+

+float HDR_GetLum(in vec3 color){

+    return dot(color, lumConv);

+}

+

+vec4 HDR_EncodeLum(in float lum){

+    float Le = 2.0 * log2(lum + epsilon) + 127.0;

+    vec4 result = vec4(0.0);

+    result.a = fract(Le);

+    result.rgb = vec3((Le - (floor(result.a * 255.0)) / 255.0) / 255.0);

+    return result;

+}

+

+float HDR_DecodeLum(in vec4 logLum){

+    float Le = logLum.r * 255.0 + logLum.a;

+    return exp2((Le - 127.0) / 2.0);

+}

+

+const mat3 rgbToXyz = mat3(

+    0.2209, 0.3390, 0.4184,

+    0.1138, 0.6780, 0.7319,

+    0.0102, 0.1130, 0.2969);

+

+const mat3 xyzToRgb = mat3(

+    6.0013,    -2.700,    -1.7995,

+    -1.332,    3.1029,    -5.7720,

+    .3007,    -1.088,    5.6268);

+

+vec4 HDR_LogLuvEncode(in vec3 rgb){

+    vec4 result;

+    vec3 Xp_Y_XYZp = rgb * rgbToXyz;

+    Xp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));

+    result.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;

+    float Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;

+    result.w = fract(Le);

+    result.z = (Le - (floor(result.w * 255.0)) / 255.0) / 255.0;

+    return result;

+}

+

+vec3 HDR_LogLuvDecode(in vec4 logLuv){

+    float Le = logLuv.z * 255.0 + logLuv.w;

+    vec3 Xp_Y_XYZp;

+    Xp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);

+    Xp_Y_XYZp.z = Xp_Y_XYZp.y / logLuv.y;

+    Xp_Y_XYZp.x = logLuv.x * Xp_Y_XYZp.z;

+    vec3 rgb = Xp_Y_XYZp * xyzToRgb;

+    return max(rgb, 0.0);

+}

+

+vec3 HDR_ToneMap(in vec3 color, in float lumAvg, in float a, in float white){

+    white *= white;

+    float lumHDR = HDR_GetLum(color);

+    float L = (a / lumAvg) * lumHDR;

+    float Ld = 1.0 + (L / white);

+    Ld = (Ld * L) / (1.0 + L);

+    return (color / lumHDR) * Ld;

+    //return color * vec3(Ld);

+}

+

+vec3 HDR_ToneMap2(in vec3 color, in float lumAvg, in float a, in float white){

+    float scale = a / (lumAvg + 0.001);

+    return (vec3(scale) * color) / (color + vec3(1.0));

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Lighting.glsllib b/engine/src/core-data/Common/ShaderLib/Lighting.glsllib
new file mode 100644
index 0000000..4d1b404
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Lighting.glsllib
@@ -0,0 +1,48 @@
+#ifndef NUM_LIGHTS

+    #define NUM_LIGHTS 4

+#endif

+

+uniform mat4 g_ViewMatrix;

+uniform vec4 g_LightPosition[NUM_LIGHTS];

+uniform vec4 g_g_LightColor[NUM_LIGHTS];

+uniform float m_Shininess;

+

+float Lighting_Diffuse(vec3 norm, vec3 lightdir){

+    return max(0.0, dot(norm, lightdir));

+}

+

+float Lighting_Specular(vec3 norm, vec3 viewdir, vec3 lightdir, float shiny){

+    vec3 refdir = reflect(-lightdir, norm);

+    return pow(max(dot(refdir, viewdir), 0.0), shiny);

+}

+

+void Lighting_Direction(vec3 worldPos, vec4 color, vec4 position, out vec4 lightDir){

+    float posLight = step(0.5, color.w);

+    vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);

+    float dist = length(tempVec);

+

+    lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);

+    lightDir.xyz = tempVec / dist;

+}

+

+void Lighting_ComputePS(vec3 tanNormal, mat3 tbnMat,

+                     int lightCount, out vec3 outDiffuse, out vec3 outSpecular){

+   // find tangent view dir & vert pos

+   vec3 tanViewDir = viewDir * tbnMat;

+

+   for (int i = 0; i < lightCount; i++){

+       // find light dir in tangent space, works for point & directional lights

+       vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition[i].xyz, g_LightColor[i].w));

+       wvLightPos.w = g_LightPosition[i].w;

+

+       vec4 tanLightDir;

+       Lighting_Direction(wvPosition, g_LightColor[i], wvLightPos, tanLightDir);

+       tanLightDir.xyz = tanLightDir.xyz * tbnMat;

+

+       vec3 lightScale = g_LightColor[i].rgb * tanLightDir.w;

+       float specular = Lighting_Specular(tanNormal, tanViewDir, tanLightDir.xyz, m_Shininess);

+       float diffuse = Lighting_Diffuse(tanNormal, tanLightDir.xyz);

+       outSpecular += specular * lightScale * step(0.01, diffuse) * g_LightColor[i].rgb;

+       outDiffuse += diffuse * lightScale * g_LightColor[i].rgb;

+   }

+}

diff --git a/engine/src/core-data/Common/ShaderLib/Math.glsllib b/engine/src/core-data/Common/ShaderLib/Math.glsllib
new file mode 100644
index 0000000..6f4cc90
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Math.glsllib
@@ -0,0 +1,4 @@
+/// Multiplies the vector by the quaternion, then returns the resultant vector.

+vec3 Math_QuaternionMult(in vec4 quat, in vec3 vec){

+	return vec + 2.0 * cross(quat.xyz, cross(quat.xyz, vec) + quat.w * vec);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/MultiSample.glsllib b/engine/src/core-data/Common/ShaderLib/MultiSample.glsllib
new file mode 100644
index 0000000..0e5355c
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/MultiSample.glsllib
@@ -0,0 +1,62 @@
+#extension GL_ARB_texture_multisample : enable
+
+uniform int m_NumSamples;
+uniform int m_NumSamplesDepth;
+
+#ifdef RESOLVE_MS
+    #define COLORTEXTURE sampler2DMS
+#else
+    #define COLORTEXTURE sampler2D
+#endif
+
+#ifdef RESOLVE_DEPTH_MS
+    #define DEPTHTEXTURE sampler2DMS
+#else
+    #define DEPTHTEXTURE sampler2D
+#endif
+
+// NOTE: Only define multisample functions if multisample is available and is being used!
+#if defined(GL_ARB_texture_multisample) && (defined(RESOLVE_MS) || defined(RESOLVE_DEPTH_MS))
+vec4 textureFetch(in sampler2DMS tex,in vec2 texC, in int numSamples){
+      ivec2 iTexC = ivec2(texC * textureSize(tex));
+      vec4 color = vec4(0.0);
+      for (int i = 0; i < numSamples; i++){
+         color += texelFetch(tex, iTexC, i);
+      }
+      return color / numSamples;
+}
+
+vec4 fetchTextureSample(in sampler2DMS tex,in vec2 texC,in int sample){
+    ivec2 iTexC = ivec2(texC * textureSize(tex));
+    return texelFetch(tex, iTexC, sample);
+}
+
+vec4 getColor(in sampler2DMS tex, in vec2 texC){
+      return textureFetch(tex, texC, m_NumSamples);
+}
+
+vec4 getColorSingle(in sampler2DMS tex, in vec2 texC){
+    ivec2 iTexC = ivec2(texC * textureSize(tex));
+    return texelFetch(tex, iTexC, 0);
+}
+
+vec4 getDepth(in sampler2DMS tex,in vec2 texC){
+      return textureFetch(tex,texC,m_NumSamplesDepth);
+}
+#endif
+
+vec4 fetchTextureSample(in sampler2D tex,in vec2 texC,in int sample){
+    return texture2D(tex,texC);
+}
+
+vec4 getColor(in sampler2D tex, in vec2 texC){
+    return texture2D(tex,texC);
+}
+
+vec4 getColorSingle(in sampler2D tex, in vec2 texC){
+    return texture2D(tex, texC);
+}
+
+vec4 getDepth(in sampler2D tex,in vec2 texC){
+    return texture2D(tex,texC);
+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Optics.glsllib b/engine/src/core-data/Common/ShaderLib/Optics.glsllib
new file mode 100644
index 0000000..5f76243
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Optics.glsllib
@@ -0,0 +1,32 @@
+#ifdef SPHERE_MAP

+#define ENVMAP sampler2D

+#define TEXENV texture2D

+#else

+#define ENVMAP samplerCube

+#define TEXENV textureCube

+#endif

+

+// converts a normalized direction vector

+// into a texture coordinate for fetching

+// texel from a sphere map

+vec2 Optics_SphereCoord(in vec3 dir){

+    float dzplus1 = dir.z + 1.0;

+

+    // compute 1/2p

+    // NOTE: this simplification only works if dir is normalized.

+    float inv_two_p = 1.414 * sqrt(dzplus1);

+    //float inv_two_p = sqrt(dir.x * dir.x + dir.y * dir.y + dzplus1 * dzplus1);

+    inv_two_p *= 2.0;

+    inv_two_p = 1.0 / inv_two_p;

+

+    // compute texcoord

+    return (dir.xy * vec2(inv_two_p)) + vec2(0.5);

+}

+

+vec4 Optics_GetEnvColor(in ENVMAP envMap, in vec3 dir){

+    #ifdef SPHERE_MAP

+    return texture2D(envMap, Optics_SphereCoord(dir));

+    #else

+    return textureCube(envMap, dir);

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Parallax.glsllib b/engine/src/core-data/Common/ShaderLib/Parallax.glsllib
new file mode 100644
index 0000000..61b9d4b
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Parallax.glsllib
@@ -0,0 +1,78 @@
+#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING)    

+    vec2 steepParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){

+        vec2 vParallaxDirection = normalize(  vViewDir.xy );

+

+        // The length of this vector determines the furthest amount of displacement: (Ati's comment)

+        float fLength         = length( vViewDir );

+        float fParallaxLength = sqrt( fLength * fLength - vViewDir.z * vViewDir.z ) / vViewDir.z; 

+

+        // Compute the actual reverse parallax displacement vector: (Ati's comment)

+        vec2 vParallaxOffsetTS = vParallaxDirection * fParallaxLength;

+

+        // Need to scale the amount of displacement to account for different height ranges

+        // in height maps. This is controlled by an artist-editable parameter: (Ati's comment)              

+        parallaxScale *=0.3;

+        vParallaxOffsetTS *= parallaxScale;

+

+       vec3 eyeDir = normalize(vViewDir).xyz;   

+

+        float nMinSamples = 6.0;

+        float nMaxSamples = 1000.0 * parallaxScale;   

+        float nNumSamples = mix( nMinSamples, nMaxSamples, 1.0 - eyeDir.z );   //In reference shader: int nNumSamples = (int)(lerp( nMinSamples, nMaxSamples, dot( eyeDirWS, N ) ));

+        float fStepSize = 1.0 / nNumSamples;   

+        float fCurrHeight = 0.0;

+        float fPrevHeight = 1.0;

+        float fNextHeight = 0.0;

+        float nStepIndex = 0.0;

+        vec2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS;

+        vec2 vTexCurrentOffset = texCoord;

+        float  fCurrentBound     = 1.0;

+        float  fParallaxAmount   = 0.0;   

+

+        while ( nStepIndex < nNumSamples && fCurrHeight <= fCurrentBound ) {

+            vTexCurrentOffset -= vTexOffsetPerStep;

+            fPrevHeight = fCurrHeight;

+            

+           

+           #ifdef NORMALMAP_PARALLAX

+               //parallax map is stored in the alpha channel of the normal map         

+               fCurrHeight = texture2DLod( parallaxMap, vTexCurrentOffset,1.0).a; 

+           #else

+               //parallax map is a texture

+               fCurrHeight = texture2DLod( parallaxMap, vTexCurrentOffset,1.0).r;                

+           #endif

+           

+            fCurrentBound -= fStepSize;

+            nStepIndex+=1.0;

+        } 

+        vec2 pt1 = vec2( fCurrentBound, fCurrHeight );

+        vec2 pt2 = vec2( fCurrentBound + fStepSize, fPrevHeight );

+

+        float fDelta2 = pt2.x - pt2.y;

+        float fDelta1 = pt1.x - pt1.y;

+

+        float fDenominator = fDelta2 - fDelta1;

+

+        fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / fDenominator;

+

+        vec2 vParallaxOffset = vParallaxOffsetTS * (1.0 - fParallaxAmount );

+       return texCoord - vParallaxOffset;  

+    }

+

+    vec2 classicParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ 

+       float h;

+       h = texture2D(parallaxMap, texCoord).a;

+       #ifdef NORMALMAP_PARALLAX

+               //parallax map is stored in the alpha channel of the normal map         

+               h = texture2D(parallaxMap, texCoord).a;               

+       #else

+               //parallax map is a texture

+               h = texture2D(parallaxMap, texCoord).r;

+       #endif

+       float heightScale = parallaxScale;

+       float heightBias = heightScale* -0.6;

+       vec3 normView = normalize(vViewDir);       

+       h = (h * heightScale + heightBias) * normView.z;

+       return texCoord + (h * normView.xy);

+    }

+#endif
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Shadow.glsllib b/engine/src/core-data/Common/ShaderLib/Shadow.glsllib
new file mode 100644
index 0000000..81a5361
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Shadow.glsllib
@@ -0,0 +1,105 @@
+#ifdef NO_SHADOW2DPROJ

+#define SHADOWMAP sampler2D

+#define SHADOWTEX texture2D

+#define SHADCOORD(coord) coord.xy

+#else

+#define SHADOWMAP sampler2DShadow

+#define SHADOWTEX shadow2D

+#define SHADCOORD(coord) vec3(coord.xy,0.0)

+#endif

+

+//float shadowDepth = texture2DProj(tex, projCoord);

+

+const float texSize = 1024.0;

+const float pixSize = 1.0 / texSize;

+const vec2 pixSize2 = vec2(pixSize);

+

+float Shadow_DoShadowCompareOffset(in SHADOWMAP tex, vec4 projCoord, vec2 offset){

+     return step(projCoord.z, SHADOWTEX(tex, SHADCOORD(projCoord.xy + offset * pixSize2)).r);

+}

+

+float Shadow_DoShadowCompare(in SHADOWMAP tex, vec4 projCoord){

+    return step(projCoord.z, SHADOWTEX(tex, SHADCOORD(projCoord.xy)).r);

+}

+

+float Shadow_BorderCheck(in vec2 coord){

+    // Very slow method (uses 24 instructions)

+    //if (coord.x >= 1.0)

+    //    return 1.0;

+    //else if (coord.x <= 0.0)

+    //    return 1.0;

+    //else if (coord.y >= 1.0)

+    //    return 1.0;

+    //else if (coord.y <= 0.0)

+    //    return 1.0;

+    //else

+    //    return 0.0;

+

+    // 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_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){

+    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 ;

+    return shadow;

+}

+

+float Shadow_DoBilinear(in SHADOWMAP tex, in vec4 projCoord){

+    const vec2 size  = vec2(256.0);

+    const vec2 pixel = vec2(1.0) / vec2(256.0);

+

+    vec2 tc = projCoord.xy * size;

+    vec2 bl = fract(tc);

+    vec2 dn = floor(tc) * pixel;

+    vec2 up = dn + pixel;

+   

+    vec4 coord = vec4(dn.xy, projCoord.zw);

+    float s_00 = Shadow_DoShadowCompare(tex, coord);

+    s_00 = clamp(s_00, 0.0, 1.0);

+

+    coord = vec4(up.x, dn.y, projCoord.zw);

+    float s_10 = Shadow_DoShadowCompare(tex, coord);

+    s_10 = clamp(s_10, 0.0, 1.0);

+

+    coord = vec4(dn.x, up.y, projCoord.zw);

+    float s_01 = Shadow_DoShadowCompare(tex, coord);

+    s_01 = clamp(s_01, 0.0, 1.0);

+

+    coord = vec4(up.xy, projCoord.zw);

+    float s_11 = Shadow_DoShadowCompare(tex, coord);

+    s_11 = clamp(s_11, 0.0, 1.0);

+

+    float xb0   = mix(s_00, s_10, clamp(bl.x, 0.0, 1.0));

+    float xb1   = mix(s_01, s_11, clamp(bl.x, 0.0, 1.0));

+    float yb    = mix(xb0, xb1,   clamp(bl.y, 0.0, 1.0));

+    return yb;

+}

+

+float Shadow_DoPCF_2x2(in SHADOWMAP tex, in vec4 projCoord){

+

+    float shadow = 0.0;

+    float x,y;

+    for (y = -1.5 ; y <=1.5 ; y+=1.0)

+            for (x = -1.5 ; x <=1.5 ; x+=1.0)

+                    shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) +

+                                    Shadow_BorderCheck(projCoord.xy),

+                                    0.0, 1.0);

+

+    shadow /= 16.0 ;

+    return shadow;

+}

+

+

+float Shadow_GetShadow(in SHADOWMAP tex, in vec4 projCoord){

+    return clamp(Shadow_DoDither_2x2(tex, projCoord) + Shadow_BorderCheck(projCoord.xy), 0.0, 1.0);

+}

+

+

diff --git a/engine/src/core-data/Common/ShaderLib/Skinning.glsllib b/engine/src/core-data/Common/ShaderLib/Skinning.glsllib
new file mode 100644
index 0000000..f0641b6
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Skinning.glsllib
@@ -0,0 +1,36 @@
+#ifdef USE_HWSKINNING

+

+#ifndef NUM_BONES

+#error A required pre-processor define "NUM_BONES" is not set!

+#endif

+

+attribute vec4 inBoneWeight;

+attribute vec4 inBoneIndices;

+uniform mat4 m_BoneMatrices[NUM_BONES];

+

+void Skinning_Compute(inout vec4 position, inout vec4 normal){

+    vec4 index  = inBoneIndices;

+    vec4 weight = inBoneWeight;

+

+    vec4 newPos    = vec4(0.0);

+    vec4 newNormal = vec4(0.0);

+

+    for (float i = 0.0; i < 4.0; i += 1.0){

+        mat4 skinMat = m_BoneMatrices[int(index.x)];

+        newPos    += weight.x * (skinMat * position);

+        newNormal += weight.x * (skinMat * normal);

+        index = index.yzwx;

+        weight = weight.yzwx;

+    }

+

+    position = newPos;

+    normal = newNormal;

+}

+

+#else

+

+void Skinning_Compute(inout vec4 position, inout vec4 normal){

+   // skinning disabled, leave position and normal unaltered

+}

+

+#endif
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Splatting.glsllib b/engine/src/core-data/Common/ShaderLib/Splatting.glsllib
new file mode 100644
index 0000000..044fee3
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Splatting.glsllib
@@ -0,0 +1,10 @@
+void Splatting_Base(in sampler2D baseMap, in vec2 tc, in float scale, out vec3 outColor){

+    outColor = texture2D(baseMap, tc * vec2(scale)).rgb;

+}

+

+void Splatting_AlphaDetail(in sampler2D alphaMap, in sampler2D detailMap, in vec2 tc, in float scale, out vec3 outColor){

+    float alpha = sampler2D(alphaMap, tc).r;

+    vec3  color = sampler2D(detailMap, tc * vec2(scale)).rgb;

+    //outColor  = mix(outColor, color, alpha);

+    outColor    = outColor + color * vec3(alpha);

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Tangent.glsllib b/engine/src/core-data/Common/ShaderLib/Tangent.glsllib
new file mode 100644
index 0000000..308c13d
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Tangent.glsllib
@@ -0,0 +1,11 @@
+uniform mat3 g_NormalMatrix;

+

+void Tangent_ComputeVS(out vec3 outNormal, out vec3 outTangent){

+    outNormal = normalize(g_NormalMatrix * inNormal);

+    outTangent = normalize(g_NormalMatrix * inTangent);

+}

+

+mat3 Tangent_GetBasis(){

+    vec3 wvBinormal = cross(wvNormal, wvTangent);

+    return mat3(wvTangent, wvBinormal, wvNormal);

+}

diff --git a/engine/src/core-data/Common/ShaderLib/Texture.glsllib b/engine/src/core-data/Common/ShaderLib/Texture.glsllib
new file mode 100644
index 0000000..829f51b
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Texture.glsllib
@@ -0,0 +1,41 @@
+#import "Common/ShaderLib/Common.glsllib"

+

+vec3 Texture_GetNormal(in sampler2D normalMap, in vec2 texCoord){

+    #ifdef NORMAL_LATC

+        return Common_UnpackNormalLA( texture2D(normalMap, texCoord) );

+    #else

+        return Common_UnpackNormal( texture2D(normalMap, texCoord).rgb );

+    #endif

+}

+

+#ifdef DXT_YCOCG

+const mat4 ycocg_mat = mat4( 1.0, -1.0,  0.0,                 1.0,

+                             0.0,  1.0, -0.5 * 256.0 / 255.0, 1.0,

+                            -1.0, -1.0,        256.0 / 255.0, 1.0,

+                             0.0,  0.0,  0.0,                 0.0 );

+#endif

+

+vec4 Texture_GetColor(in sampler2D colorMap, in vec2 texCoord){

+    #ifdef DXT_YCOCG

+        vec4 color = texture2D(colorMap, texCoord);

+        // fast YCoCg decode:

+        color.z = 1.0 / ((color.z * ( 255.0 / 8.0 )) + 1.0);

+        color.xy *= color.z;

+        return color * ycocg_mat;

+

+        // slow decode:

+        //float Y = color.a;

+        //float scale = 1.0 / ((255.0 / 8.0) * color.b + 1.0);

+        //const float offset = 128.0 / 255.0;

+        //float Co = (color.r - offset) * scale;

+        //float Cg = (color.g - offset) * scale;

+

+        //float R = Y + Co - Cg;

+        //float G = Y + Cg;

+        //float B = Y - Co - Cg;

+

+        //return vec4(R, G, B, 1.0);

+    #else

+        return texture2D(colorMap, texCoord);

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-data/Common/ShaderLib/Ubo.glsllib b/engine/src/core-data/Common/ShaderLib/Ubo.glsllib
new file mode 100644
index 0000000..5dbb5b9
--- /dev/null
+++ b/engine/src/core-data/Common/ShaderLib/Ubo.glsllib
@@ -0,0 +1,15 @@
+

+

+#ifdef ENABLE_UBO

+    // #version 140

+    #extension GL_ARB_uniform_buffer_object : enable

+

+    #define START_MATPARAMS layout(std140) uniform matparams {

+    #define END_MATPARAMS }

+    #define MATPARAM

+    #define attribute in

+#else

+    #define START_MATPARAMS

+    #define END_MATPARAMS

+    #define MATPARAM uniform

+#endif

diff --git a/engine/src/core-data/Interface/Fonts/Console.fnt b/engine/src/core-data/Interface/Fonts/Console.fnt
new file mode 100644
index 0000000..15ea60c
--- /dev/null
+++ b/engine/src/core-data/Interface/Fonts/Console.fnt
@@ -0,0 +1,99 @@
+info face="Lucida Console" size=11 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0

+common lineHeight=11 base=9 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0

+page id=0 file="Console.png"

+chars count=95

+char id=32   x=61    y=18    width=1     height=0     xoffset=0     yoffset=11    xadvance=7     page=0  chnl=15

+char id=33   x=147   y=8     width=1     height=7     xoffset=3     yoffset=2     xadvance=7     page=0  chnl=15

+char id=34   x=26    y=19    width=4     height=3     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=35   x=214   y=0     width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=36   x=59    y=0     width=5     height=9     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=37   x=142   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=38   x=150   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=39   x=31    y=19    width=1     height=3     xoffset=3     yoffset=1     xadvance=7     page=0  chnl=15

+char id=40   x=26    y=0     width=4     height=10    xoffset=2     yoffset=1     xadvance=7     page=0  chnl=15

+char id=41   x=16    y=0     width=4     height=10    xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=42   x=9     y=19    width=5     height=4     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=43   x=245   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=44   x=15    y=19    width=2     height=4     xoffset=3     yoffset=7     xadvance=7     page=0  chnl=15

+char id=45   x=55    y=18    width=5     height=1     xoffset=1     yoffset=5     xadvance=7     page=0  chnl=15

+char id=46   x=41    y=19    width=2     height=2     xoffset=2     yoffset=7     xadvance=7     page=0  chnl=15

+char id=47   x=0     y=0     width=7     height=10    xoffset=0     yoffset=1     xadvance=7     page=0  chnl=15

+char id=48   x=31    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=49   x=37    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=50   x=132   y=9     width=4     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=51   x=127   y=9     width=4     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=52   x=43    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=53   x=142   y=8     width=4     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=54   x=103   y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=55   x=49    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=56   x=55    y=10    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=57   x=61    y=10    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=58   x=6     y=19    width=2     height=6     xoffset=2     yoffset=3     xadvance=7     page=0  chnl=15

+char id=59   x=131   y=0     width=2     height=8     xoffset=2     yoffset=3     xadvance=7     page=0  chnl=15

+char id=60   x=188   y=8     width=6     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=61   x=18    y=19    width=7     height=3     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15

+char id=62   x=202   y=8     width=6     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=63   x=79    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=64   x=190   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=65   x=166   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=66   x=91    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=67   x=242   y=0     width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=68   x=13    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=69   x=67    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=70   x=109   y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=71   x=235   y=0     width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=72   x=115   y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=73   x=121   y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=74   x=137   y=8     width=4     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=75   x=228   y=0     width=6     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=76   x=7     y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=77   x=221   y=0     width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=78   x=85    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=79   x=0     y=11    width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=80   x=73    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=81   x=51    y=0     width=7     height=9     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=82   x=174   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=83   x=25    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=84   x=182   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=85   x=19    y=11    width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=86   x=198   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=87   x=206   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=88   x=134   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=89   x=158   y=0     width=7     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=90   x=249   y=0     width=6     height=7     xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15

+char id=91   x=41    y=0     width=3     height=10    xoffset=2     yoffset=1     xadvance=7     page=0  chnl=15

+char id=92   x=8     y=0     width=7     height=10    xoffset=0     yoffset=1     xadvance=7     page=0  chnl=15

+char id=93   x=45    y=0     width=3     height=10    xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=94   x=149   y=8     width=7     height=6     xoffset=0     yoffset=1     xadvance=7     page=0  chnl=15

+char id=95   x=47    y=19    width=7     height=1     xoffset=0     yoffset=9     xadvance=7     page=0  chnl=15

+char id=96   x=44    y=19    width=2     height=2     xoffset=2     yoffset=0     xadvance=7     page=0  chnl=15

+char id=97   x=181   y=8     width=6     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=98   x=87    y=0     width=5     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=99   x=227   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=100  x=111   y=0     width=5     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=101  x=221   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=102  x=73    y=0     width=6     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=103  x=93    y=0     width=5     height=8     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=104  x=99    y=0     width=5     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=105  x=123   y=0     width=3     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=106  x=36    y=0     width=4     height=10    xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=107  x=80    y=0     width=6     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=108  x=127   y=0     width=3     height=8     xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=109  x=157   y=8     width=7     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=110  x=209   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=111  x=215   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=112  x=117   y=0     width=5     height=8     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=113  x=105   y=0     width=5     height=8     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=114  x=239   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=115  x=251   y=8     width=4     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=116  x=97    y=9     width=5     height=7     xoffset=1     yoffset=2     xadvance=7     page=0  chnl=15

+char id=117  x=0     y=19    width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=118  x=165   y=8     width=7     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=119  x=173   y=8     width=7     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=120  x=195   y=8     width=6     height=6     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=121  x=65    y=0     width=7     height=8     xoffset=0     yoffset=3     xadvance=7     page=0  chnl=15

+char id=122  x=233   y=8     width=5     height=6     xoffset=1     yoffset=3     xadvance=7     page=0  chnl=15

+char id=123  x=31    y=0     width=4     height=10    xoffset=2     yoffset=1     xadvance=7     page=0  chnl=15

+char id=124  x=49    y=0     width=1     height=10    xoffset=3     yoffset=1     xadvance=7     page=0  chnl=15

+char id=125  x=21    y=0     width=4     height=10    xoffset=1     yoffset=1     xadvance=7     page=0  chnl=15

+char id=126  x=33    y=19    width=7     height=2     xoffset=0     yoffset=5     xadvance=7     page=0  chnl=15

diff --git a/engine/src/core-data/Interface/Fonts/Console.png b/engine/src/core-data/Interface/Fonts/Console.png
new file mode 100644
index 0000000..821f92a
--- /dev/null
+++ b/engine/src/core-data/Interface/Fonts/Console.png
Binary files differ
diff --git a/engine/src/core-data/Interface/Fonts/Default.fnt b/engine/src/core-data/Interface/Fonts/Default.fnt
new file mode 100644
index 0000000..97230e8
--- /dev/null
+++ b/engine/src/core-data/Interface/Fonts/Default.fnt
@@ -0,0 +1,229 @@
+info face="null" size=17 bold=0 italic=0 charset="ASCII" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1

+common lineHeight=21 base=26 scaleW=256 scaleH=256 pages=1 packed=0

+page id=0 file="Default.png"

+chars count=224

+char id=32   x=30     y=110     width=5     height=3     xoffset=0     yoffset=16    xadvance=4     page=0  chnl=0 

+char id=33   x=110     y=60     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=34   x=185     y=95     width=6     height=8     xoffset=0     yoffset=2    xadvance=5     page=0  chnl=0 

+char id=35   x=116     y=60     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=36   x=242     y=0     width=10     height=19     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 

+char id=37   x=189     y=22     width=14     height=17     xoffset=0     yoffset=3    xadvance=13     page=0  chnl=0 

+char id=38   x=204     y=22     width=11     height=17     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=39   x=181     y=95     width=3     height=9     xoffset=0     yoffset=2    xadvance=2     page=0  chnl=0 

+char id=40   x=0     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=41   x=6     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=42   x=161     y=95     width=9     height=11     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=43   x=150     y=95     width=10     height=12     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=44   x=192     y=95     width=5     height=8     xoffset=0     yoffset=14    xadvance=4     page=0  chnl=0 

+char id=45   x=245     y=95     width=6     height=5     xoffset=0     yoffset=10    xadvance=5     page=0  chnl=0 

+char id=46   x=0     y=110     width=5     height=5     xoffset=0     yoffset=14    xadvance=4     page=0  chnl=0 

+char id=47   x=12     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=48   x=216     y=22     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=49   x=128     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=50   x=139     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=51   x=227     y=22     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=52   x=150     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=53   x=238     y=22     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=54   x=0     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=55   x=161     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=56   x=11     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=57   x=22     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=58   x=43     y=95     width=5     height=13     xoffset=0     yoffset=6    xadvance=4     page=0  chnl=0 

+char id=59   x=172     y=60     width=5     height=16     xoffset=0     yoffset=6    xadvance=4     page=0  chnl=0 

+char id=60   x=49     y=95     width=10     height=13     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=61   x=198     y=95     width=10     height=7     xoffset=0     yoffset=9    xadvance=9     page=0  chnl=0 

+char id=62   x=60     y=95     width=10     height=13     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=63   x=178     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=64   x=18     y=22     width=13     height=19     xoffset=0     yoffset=3    xadvance=12     page=0  chnl=0 

+char id=65   x=189     y=60     width=12     height=16     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=66   x=202     y=60     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=67   x=33     y=42     width=11     height=17     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=68   x=214     y=60     width=12     height=16     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=69   x=227     y=60     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=70   x=239     y=60     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=71   x=45     y=42     width=12     height=17     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=72   x=0     y=78     width=13     height=16     xoffset=0     yoffset=3    xadvance=12     page=0  chnl=0 

+char id=73   x=14     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=74   x=58     y=42     width=8     height=17     xoffset=0     yoffset=3    xadvance=7     page=0  chnl=0 

+char id=75   x=20     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=76   x=32     y=78     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=77   x=43     y=78     width=15     height=16     xoffset=0     yoffset=3    xadvance=14     page=0  chnl=0 

+char id=78   x=59     y=78     width=13     height=16     xoffset=0     yoffset=3    xadvance=12     page=0  chnl=0 

+char id=79   x=67     y=42     width=14     height=17     xoffset=0     yoffset=3    xadvance=13     page=0  chnl=0 

+char id=80   x=73     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=81   x=32     y=22     width=14     height=19     xoffset=0     yoffset=3    xadvance=13     page=0  chnl=0 

+char id=82   x=85     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=83   x=82     y=42     width=11     height=17     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=84   x=97     y=78     width=10     height=16     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=85   x=94     y=42     width=13     height=17     xoffset=0     yoffset=3    xadvance=12     page=0  chnl=0 

+char id=86   x=108     y=78     width=12     height=16     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=87   x=121     y=78     width=16     height=16     xoffset=0     yoffset=3    xadvance=15     page=0  chnl=0 

+char id=88   x=138     y=78     width=12     height=16     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=89   x=151     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=90   x=163     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=91   x=47     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=92   x=53     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=93   x=59     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=94   x=171     y=95     width=9     height=10     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=95   x=6     y=110     width=9     height=5     xoffset=0     yoffset=15    xadvance=8     page=0  chnl=0 

+char id=96   x=209     y=95     width=4     height=7     xoffset=0     yoffset=2    xadvance=3     page=0  chnl=0 

+char id=97   x=237     y=78     width=9     height=14     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=98   x=145     y=22     width=10     height=18     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 

+char id=99   x=247     y=78     width=9     height=14     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=100   x=156     y=22     width=10     height=18     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 

+char id=101   x=0     y=95     width=10     height=14     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=102   x=108     y=42     width=6     height=17     xoffset=0     yoffset=2    xadvance=5     page=0  chnl=0 

+char id=103   x=115     y=42     width=9     height=17     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=104   x=125     y=42     width=10     height=17     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 

+char id=105   x=175     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=106   x=69     y=0     width=7     height=20     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=107   x=136     y=42     width=9     height=17     xoffset=0     yoffset=2    xadvance=8     page=0  chnl=0 

+char id=108   x=146     y=42     width=5     height=17     xoffset=0     yoffset=2    xadvance=4     page=0  chnl=0 

+char id=109   x=71     y=95     width=15     height=13     xoffset=0     yoffset=6    xadvance=14     page=0  chnl=0 

+char id=110   x=87     y=95     width=10     height=13     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=111   x=11     y=95     width=10     height=14     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=112   x=152     y=42     width=10     height=17     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=113   x=163     y=42     width=10     height=17     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=114   x=98     y=95     width=7     height=13     xoffset=0     yoffset=6    xadvance=6     page=0  chnl=0 

+char id=115   x=22     y=95     width=9     height=14     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=116   x=181     y=78     width=6     height=16     xoffset=0     yoffset=4    xadvance=5     page=0  chnl=0 

+char id=117   x=32     y=95     width=10     height=14     xoffset=0     yoffset=6    xadvance=9     page=0  chnl=0 

+char id=118   x=106     y=95     width=9     height=13     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=119   x=116     y=95     width=13     height=13     xoffset=0     yoffset=6    xadvance=12     page=0  chnl=0 

+char id=120   x=130     y=95     width=9     height=13     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=121   x=174     y=42     width=9     height=17     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=122   x=140     y=95     width=9     height=13     xoffset=0     yoffset=6    xadvance=8     page=0  chnl=0 

+char id=123   x=65     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=124   x=71     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=125   x=77     y=22     width=5     height=19     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=126   x=214     y=95     width=10     height=7     xoffset=0     yoffset=9    xadvance=9     page=0  chnl=0 

+char id=127   x=36     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=128   x=46     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=129   x=56     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=130   x=66     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=131   x=76     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=132   x=86     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=133   x=96     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=134   x=106     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=135   x=116     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=136   x=126     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=137   x=136     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=138   x=146     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=139   x=156     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=140   x=166     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=141   x=176     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=142   x=186     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=143   x=196     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=144   x=206     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=145   x=216     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=146   x=226     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=147   x=236     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=148   x=246     y=110     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=149   x=0     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=150   x=10     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=151   x=20     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=152   x=30     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=153   x=40     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=154   x=50     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=155   x=60     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=156   x=70     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=157   x=80     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=158   x=90     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=159   x=100     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=160   x=110     y=116     width=5     height=3     xoffset=0     yoffset=16    xadvance=4     page=0  chnl=0 

+char id=161   x=116     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=162   x=126     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=163   x=136     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=164   x=146     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=165   x=156     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=166   x=166     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=167   x=176     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=168   x=225     y=95     width=13     height=6     xoffset=0     yoffset=3    xadvance=12     page=0  chnl=0 

+char id=169   x=186     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=170   x=196     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=171   x=206     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=172   x=216     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=173   x=226     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=174   x=236     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=175   x=16     y=110     width=13     height=5     xoffset=0     yoffset=4    xadvance=12     page=0  chnl=0 

+char id=176   x=246     y=116     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=177   x=0     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=178   x=10     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=179   x=20     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=180   x=239     y=95     width=5     height=6     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=181   x=30     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=182   x=40     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=183   x=50     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=184   x=60     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=185   x=70     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=186   x=80     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=187   x=90     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=188   x=100     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=189   x=110     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=190   x=120     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=191   x=130     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=192   x=77     y=0     width=12     height=20     xoffset=0     yoffset=-1    xadvance=11     page=0  chnl=0 

+char id=193   x=90     y=0     width=12     height=20     xoffset=0     yoffset=-1    xadvance=11     page=0  chnl=0 

+char id=194   x=83     y=22     width=12     height=19     xoffset=0     yoffset=0    xadvance=11     page=0  chnl=0 

+char id=195   x=140     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=196   x=96     y=22     width=12     height=19     xoffset=0     yoffset=0    xadvance=11     page=0  chnl=0 

+char id=197   x=103     y=0     width=12     height=20     xoffset=0     yoffset=-1    xadvance=11     page=0  chnl=0 

+char id=198   x=150     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=199   x=160     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=200   x=116     y=0     width=11     height=20     xoffset=0     yoffset=-1    xadvance=10     page=0  chnl=0 

+char id=201   x=128     y=0     width=11     height=20     xoffset=0     yoffset=-1    xadvance=10     page=0  chnl=0 

+char id=202   x=109     y=22     width=11     height=19     xoffset=0     yoffset=0    xadvance=10     page=0  chnl=0 

+char id=203   x=121     y=22     width=11     height=19     xoffset=0     yoffset=0    xadvance=10     page=0  chnl=0 

+char id=204   x=140     y=0     width=5     height=20     xoffset=0     yoffset=-1    xadvance=4     page=0  chnl=0 

+char id=205   x=146     y=0     width=5     height=20     xoffset=0     yoffset=-1    xadvance=4     page=0  chnl=0 

+char id=206   x=133     y=22     width=5     height=19     xoffset=0     yoffset=0    xadvance=4     page=0  chnl=0 

+char id=207   x=139     y=22     width=5     height=19     xoffset=0     yoffset=0    xadvance=4     page=0  chnl=0 

+char id=208   x=188     y=78     width=12     height=16     xoffset=0     yoffset=3    xadvance=11     page=0  chnl=0 

+char id=209   x=170     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=210   x=0     y=0     width=14     height=21     xoffset=0     yoffset=-1    xadvance=13     page=0  chnl=0 

+char id=211   x=15     y=0     width=14     height=21     xoffset=0     yoffset=-1    xadvance=13     page=0  chnl=0 

+char id=212   x=152     y=0     width=14     height=20     xoffset=0     yoffset=0    xadvance=13     page=0  chnl=0 

+char id=213   x=180     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=214   x=167     y=0     width=14     height=20     xoffset=0     yoffset=0    xadvance=13     page=0  chnl=0 

+char id=215   x=190     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=216   x=200     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=217   x=30     y=0     width=13     height=21     xoffset=0     yoffset=-1    xadvance=12     page=0  chnl=0 

+char id=218   x=44     y=0     width=13     height=21     xoffset=0     yoffset=-1    xadvance=12     page=0  chnl=0 

+char id=219   x=182     y=0     width=13     height=20     xoffset=0     yoffset=0    xadvance=12     page=0  chnl=0 

+char id=220   x=196     y=0     width=13     height=20     xoffset=0     yoffset=0    xadvance=12     page=0  chnl=0 

+char id=221   x=210     y=0     width=11     height=20     xoffset=0     yoffset=-1    xadvance=10     page=0  chnl=0 

+char id=222   x=201     y=78     width=11     height=16     xoffset=0     yoffset=3    xadvance=10     page=0  chnl=0 

+char id=223   x=167     y=22     width=11     height=18     xoffset=0     yoffset=2    xadvance=10     page=0  chnl=0 

+char id=224   x=184     y=42     width=9     height=17     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=225   x=194     y=42     width=9     height=17     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=226   x=204     y=42     width=9     height=17     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=227   x=210     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=228   x=214     y=42     width=9     height=17     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=229   x=179     y=22     width=9     height=18     xoffset=0     yoffset=2    xadvance=8     page=0  chnl=0 

+char id=230   x=220     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=231   x=230     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=232   x=224     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=233   x=235     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=234   x=246     y=42     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=235   x=0     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=236   x=213     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=237   x=219     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=238   x=225     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=239   x=231     y=78     width=5     height=16     xoffset=0     yoffset=3    xadvance=4     page=0  chnl=0 

+char id=240   x=11     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=241   x=240     y=120     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=242   x=22     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=243   x=33     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=244   x=44     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=245   x=0     y=124     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=246   x=55     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=247   x=10     y=124     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=248   x=20     y=124     width=9     height=3     xoffset=0     yoffset=16    xadvance=8     page=0  chnl=0 

+char id=249   x=66     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=250   x=77     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=251   x=88     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=252   x=99     y=60     width=10     height=17     xoffset=0     yoffset=3    xadvance=9     page=0  chnl=0 

+char id=253   x=222     y=0     width=9     height=20     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+char id=254   x=58     y=0     width=10     height=21     xoffset=0     yoffset=2    xadvance=9     page=0  chnl=0 

+char id=255   x=232     y=0     width=9     height=20     xoffset=0     yoffset=3    xadvance=8     page=0  chnl=0 

+kernings count=0

diff --git a/engine/src/core-data/Interface/Fonts/Default.png b/engine/src/core-data/Interface/Fonts/Default.png
new file mode 100644
index 0000000..3c2dee2
--- /dev/null
+++ b/engine/src/core-data/Interface/Fonts/Default.png
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Post/BloomExtract.j3md b/engine/src/core-effects/Common/MatDefs/Post/BloomExtract.j3md
new file mode 100644
index 0000000..76614cc
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/BloomExtract.j3md
@@ -0,0 +1,43 @@
+MaterialDef Bloom {

+

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Float ExposurePow

+        Float ExposureCutoff

+        Boolean Extract

+        Texture2D GlowMap

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/bloomExtract15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            DO_EXTRACT : Extract

+            RESOLVE_MS : NumSamples

+        }

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/bloomExtract.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            DO_EXTRACT : Extract

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/BloomFinal.j3md b/engine/src/core-effects/Common/MatDefs/Post/BloomFinal.j3md
new file mode 100644
index 0000000..68f8c28
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/BloomFinal.j3md
@@ -0,0 +1,36 @@
+MaterialDef Bloom Final {

+

+    MaterialParameters {

+          Int NumSamples

+        Texture2D Texture

+        Texture2D BloomTex

+        Float BloomIntensity

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/bloomFinal15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+        }

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/bloomFinal.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.frag b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.frag
new file mode 100644
index 0000000..7ee7f78
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.frag
@@ -0,0 +1,55 @@
+uniform vec4 m_EdgeColor;

+

+uniform float m_EdgeWidth;

+uniform float m_EdgeIntensity;

+

+uniform float m_NormalThreshold;

+uniform float m_DepthThreshold;

+

+uniform float m_NormalSensitivity;

+uniform float m_DepthSensitivity;

+

+varying vec2 texCoord;

+

+uniform sampler2D m_Texture;

+uniform sampler2D m_NormalsTexture;

+uniform sampler2D m_DepthTexture;

+

+uniform vec2 g_Resolution;

+

+vec4 fetchNormalDepth(vec2 tc){

+    vec4 nd;

+    nd.xyz = texture2D(m_NormalsTexture, tc).rgb;

+    nd.w   = texture2D(m_DepthTexture,   tc).r;

+    return nd;

+}

+

+void main(){

+    vec3 color = texture2D(m_Texture, texCoord).rgb;

+

+    vec2 edgeOffset = vec2(m_EdgeWidth) / g_Resolution;

+

+    vec4 n1 = fetchNormalDepth(texCoord + vec2(-1.0, -1.0) * edgeOffset);

+    vec4 n2 = fetchNormalDepth(texCoord + vec2( 1.0,  1.0) * edgeOffset);

+    vec4 n3 = fetchNormalDepth(texCoord + vec2(-1.0,  1.0) * edgeOffset);

+    vec4 n4 = fetchNormalDepth(texCoord + vec2( 1.0, -1.0) * edgeOffset);

+

+    // Work out how much the normal and depth values are changing.

+    vec4 diagonalDelta = abs(n1 - n2) + abs(n3 - n4);

+

+    float normalDelta = dot(diagonalDelta.xyz, vec3(1.0));

+    float depthDelta = diagonalDelta.w;

+

+    // Filter out very small changes, in order to produce nice clean results.

+    normalDelta = clamp((normalDelta - m_NormalThreshold) * m_NormalSensitivity, 0.0, 1.0);

+    depthDelta  = clamp((depthDelta - m_DepthThreshold) * m_DepthSensitivity,    0.0, 1.0);

+

+    // Does this pixel lie on an edge?

+    float edgeAmount = clamp(normalDelta + depthDelta, 0.0, 1.0) * m_EdgeIntensity;

+

+    // Apply the edge detection result to the main scene color.

+    //color *= (1.0 - edgeAmount);

+    color = mix (color,m_EdgeColor.rgb,edgeAmount);

+   

+    gl_FragColor = vec4(color, 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.j3md b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.j3md
new file mode 100644
index 0000000..687193e
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge.j3md
@@ -0,0 +1,48 @@
+MaterialDef Cartoon Edge {

+

+    MaterialParameters {

+        Int NumSamples

+        Int NumSamplesDepth

+        Texture2D Texture

+        Texture2D NormalsTexture

+        Texture2D DepthTexture

+        Color EdgeColor

+        Float EdgeWidth

+        Float EdgeIntensity

+        Float NormalThreshold

+        Float DepthThreshold

+        Float NormalSensitivity

+        Float DepthSensitivity

+    }

+

+     Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/CartoonEdge15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+            RESOLVE_DEPTH_MS : NumSamplesDepth

+        }

+    }

+

+   

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/CartoonEdge.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge15.frag b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge15.frag
new file mode 100644
index 0000000..3c3921a
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CartoonEdge15.frag
@@ -0,0 +1,57 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+

+uniform sampler2D m_NormalsTexture;

+uniform vec2 g_Resolution;

+

+uniform vec4 m_EdgeColor;

+

+uniform float m_EdgeWidth;

+uniform float m_EdgeIntensity;

+

+uniform float m_NormalThreshold;

+uniform float m_DepthThreshold;

+

+uniform float m_NormalSensitivity;

+uniform float m_DepthSensitivity;

+

+in vec2 texCoord;

+out vec4 outFragColor;

+

+vec4 fetchNormalDepth(vec2 tc){

+    vec4 nd;

+    nd.xyz = texture2D(m_NormalsTexture, tc).rgb;

+    nd.w   = fetchTextureSample(m_DepthTexture,   tc,0).r;

+    return nd;

+}

+

+void main(){

+    vec3 color = getColor(m_Texture, texCoord).rgb;

+

+    vec2 edgeOffset = vec2(m_EdgeWidth) / textureSize(m_NormalsTexture, 0);

+    vec4 n1 = fetchNormalDepth(texCoord + vec2(-1.0, -1.0) * edgeOffset);

+    vec4 n2 = fetchNormalDepth(texCoord + vec2( 1.0,  1.0) * edgeOffset);

+    vec4 n3 = fetchNormalDepth(texCoord + vec2(-1.0,  1.0) * edgeOffset);

+    vec4 n4 = fetchNormalDepth(texCoord + vec2( 1.0, -1.0) * edgeOffset);

+

+    // Work out how much the normal and depth values are changing.

+    vec4 diagonalDelta = abs(n1 - n2) + abs(n3 - n4);

+

+    float normalDelta = dot(diagonalDelta.xyz, vec3(1.0));

+    float depthDelta = diagonalDelta.w;

+

+    // Filter out very small changes, in order to produce nice clean results.

+    normalDelta = clamp((normalDelta - m_NormalThreshold) * m_NormalSensitivity, 0.0, 1.0);

+    depthDelta  = clamp((depthDelta - m_DepthThreshold) * m_DepthSensitivity,    0.0, 1.0);

+

+    // Does this pixel lie on an edge?

+    float edgeAmount = clamp(normalDelta + depthDelta, 0.0, 1.0) * m_EdgeIntensity;

+

+    // Apply the edge detection result to the main scene color.

+    //color *= (1.0 - edgeAmount);

+    color = mix (color,m_EdgeColor.rgb,edgeAmount);

+

+    outFragColor = vec4(color, 1.0);

+}

diff --git a/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.frag b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.frag
new file mode 100644
index 0000000..fc700f8
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.frag
@@ -0,0 +1,51 @@
+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+ 

+uniform vec4 m_LineColor;

+uniform vec4 m_PaperColor;

+uniform float m_ColorInfluenceLine;

+uniform float m_ColorInfluencePaper;

+ 

+uniform float m_FillValue;

+uniform float m_Luminance1;

+uniform float m_Luminance2;

+uniform float m_Luminance3;

+uniform float m_Luminance4;

+uniform float m_Luminance5;

+ 

+uniform float m_LineDistance;

+uniform float m_LineThickness;

+ 

+void main() {

+    vec4 texVal = texture2D(m_Texture, texCoord);

+    float linePixel = 0.0;

+ 

+    float lum = texVal.r*0.2126 + texVal.g*0.7152 + texVal.b*0.0722;

+ 

+    if (lum < m_Luminance1){

+        if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness)

+            linePixel = 1.0;

+    }

+    if (lum < m_Luminance2){

+        if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness)

+            linePixel = 1.0;

+    }

+    if (lum < m_Luminance3){

+        if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)

+            linePixel = 1.0;

+    }

+    if (lum < m_Luminance4){

+        if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)

+            linePixel = 1.0;

+    }

+    if (lum < m_Luminance5){ // No line, make a blob instead

+        linePixel = m_FillValue;

+    }

+ 

+    // Mix line color with existing color information

+    vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine);

+    // Mix paper color with existing color information

+    vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper);

+ 

+    gl_FragColor = mix(paperColor, lineColor, linePixel);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.j3md b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.j3md
new file mode 100644
index 0000000..bf850dc
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch.j3md
@@ -0,0 +1,41 @@
+MaterialDef CrossHatch {

+ 

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture;

+        Vector4 LineColor;

+        Vector4 PaperColor;

+        Float ColorInfluenceLine;

+        Float ColorInfluencePaper;

+        Float FillValue;

+        Float Luminance1;

+        Float Luminance2;

+        Float Luminance3;

+        Float Luminance4;

+        Float Luminance5;

+        Float LineThickness;

+        Float LineDistance;

+    }

+ 

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/CrossHatch15.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+ 

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/CrossHatch.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+ 

+    Technique FixedFunc {

+    }

+ 

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/CrossHatch15.frag b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch15.frag
new file mode 100644
index 0000000..8daa4f7
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/CrossHatch15.frag
@@ -0,0 +1,53 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+ 

+uniform COLORTEXTURE m_Texture;

+in vec2 texCoord;

+ 

+uniform vec4 m_LineColor;

+uniform vec4 m_PaperColor;

+uniform float m_ColorInfluenceLine;

+uniform float m_ColorInfluencePaper;

+ 

+uniform float m_FillValue;

+uniform float m_Luminance1;

+uniform float m_Luminance2;

+uniform float m_Luminance3;

+uniform float m_Luminance4;

+uniform float m_Luminance5;

+ 

+uniform float m_LineDistance;

+uniform float m_LineThickness;

+ 

+void main() {

+    vec4 texVal = getColor(m_Texture, texCoord);

+    float linePixel = 0;

+ 

+    float lum = texVal.r*0.2126 + texVal.g*0.7152 + texVal.b*0.0722;

+ 

+    if (lum < m_Luminance1){

+        if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness)

+            linePixel = 1;

+    }

+    if (lum < m_Luminance2){

+        if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness)

+            linePixel = 1;

+    }

+    if (lum < m_Luminance3){

+        if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)

+            linePixel = 1;

+    }

+    if (lum < m_Luminance4){

+        if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness)

+            linePixel = 1;

+    }

+    if (lum < m_Luminance5){ // No line, make a blob instead

+        linePixel = m_FillValue;

+    }

+ 

+    // Mix line color with existing color information

+    vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine);

+    // Mix paper color with existing color information

+    vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper);

+ 

+    gl_FragColor = mix(paperColor, lineColor, linePixel);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.frag b/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.frag
new file mode 100644
index 0000000..658da54
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.frag
@@ -0,0 +1,89 @@
+uniform sampler2D m_Texture;

+uniform sampler2D m_DepthTexture;

+varying vec2 texCoord;

+

+uniform float m_FocusRange;

+uniform float m_FocusDistance;

+uniform float m_XScale;

+uniform float m_YScale;

+

+vec2 m_NearFar = vec2( 0.1, 1000.0 );

+

+void main() {

+

+    vec4 texVal = texture2D( m_Texture, texCoord );

+

+    float zBuffer = texture2D( m_DepthTexture, texCoord ).r;

+

+    //

+    // z_buffer_value = a + b / z;

+    //

+    // Where:

+    //  a = zFar / ( zFar - zNear )

+    //  b = zFar * zNear / ( zNear - zFar )

+    //  z = distance from the eye to the object

+    //

+    // Which means:

+    // zb - a = b / z;

+    // z * (zb - a) = b

+    // z = b / (zb - a)

+    //

+    float a = m_NearFar.y / (m_NearFar.y - m_NearFar.x);

+    float b = m_NearFar.y * m_NearFar.x / (m_NearFar.x - m_NearFar.y);

+    float z = b / (zBuffer - a);

+

+    // Above could be the same for any depth-based filter

+

+    // We want to be purely focused right at

+    // m_FocusDistance and be purely unfocused

+    // at +/- m_FocusRange to either side of that.

+    float unfocus = min( 1.0, abs( z - m_FocusDistance ) / m_FocusRange );

+

+    if( unfocus < 0.2 ) {

+        // If we are mostly in focus then don't bother with the

+        // convolution filter

+        gl_FragColor = texVal;

+    } else {

+    // Perform a wide convolution filter and we scatter it

+    // a bit to avoid some texture look-ups.  Instead of

+    // a full 5x5 (25-1 lookups) we'll skip every other one

+    // to only perform 12.

+    // 1  0  1  0  1

+    // 0  1  0  1  0

+    // 1  0  x  0  1

+    // 0  1  0  1  0

+    // 1  0  1  0  1

+    //

+    // You can get away with 8 just around the outside but

+    // it looks more jittery to me.

+

+    vec4 sum = vec4(0.0);

+

+    float x = texCoord.x;

+    float y = texCoord.y;

+

+    float xScale = m_XScale;

+    float yScale = m_YScale;

+

+    // In order from lower left to right, depending on how you look at it

+    sum += texture2D( m_Texture, vec2(x - 2.0 * xScale, y - 2.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 0.0 * xScale, y - 2.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x + 2.0 * xScale, y - 2.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 1.0 * xScale, y - 1.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x + 1.0 * xScale, y - 1.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 2.0 * xScale, y - 0.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x + 2.0 * xScale, y - 0.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 1.0 * xScale, y + 1.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x + 1.0 * xScale, y + 1.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 2.0 * xScale, y + 2.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x - 0.0 * xScale, y + 2.0 * yScale) );

+    sum += texture2D( m_Texture, vec2(x + 2.0 * xScale, y + 2.0 * yScale) );

+

+    sum = sum / 12.0;

+

+    gl_FragColor = mix( texVal, sum, unfocus );

+

+    // I used this for debugging the range

+   // gl_FragColor.r = unfocus;

+}

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.j3md b/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.j3md
new file mode 100644
index 0000000..db30a09
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/DepthOfField.j3md
@@ -0,0 +1,25 @@
+MaterialDef Depth Of Field {

+

+    MaterialParameters {

+        Int NumSamples

+        Int NumSamplesDepth

+        Texture2D Texture

+        Texture2D DepthTexture

+        Float FocusRange;

+        Float FocusDistance;

+        Float XScale;

+        Float YScale;

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/DepthOfField.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/FXAA.frag b/engine/src/core-effects/Common/MatDefs/Post/FXAA.frag
new file mode 100644
index 0000000..d630b35
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/FXAA.frag
@@ -0,0 +1,88 @@
+#extension GL_EXT_gpu_shader4 : enable

+

+uniform sampler2D m_Texture;

+uniform vec2 g_Resolution;

+

+uniform float m_VxOffset;

+uniform float m_SpanMax;

+uniform float m_ReduceMul;

+

+varying vec2 texCoord;

+varying vec4 posPos;

+

+#define FxaaTex(t, p) texture2D(t, p)

+

+#if __VERSION__ >= 130

+    #define OffsetVec(a, b) ivec2(a, b)

+    #define FxaaTexOff(t, p, o, r) textureOffset(t, p, o)

+#elif defined(GL_EXT_gpu_shader4)

+    #define OffsetVec(a, b) ivec2(a, b)

+    #define FxaaTexOff(t, p, o, r) texture2DLodOffset(t, p, 0.0, o)

+#else

+    #define OffsetVec(a, b) vec2(a, b)

+    #define FxaaTexOff(t, p, o, r) texture2D(t, p + o * r)

+#endif

+

+vec3 FxaaPixelShader(

+  vec4 posPos,   // Output of FxaaVertexShader interpolated across screen.

+  sampler2D tex, // Input texture.

+  vec2 rcpFrame) // Constant {1.0/frameWidth, 1.0/frameHeight}.

+{

+

+    #define FXAA_REDUCE_MIN   (1.0/128.0)

+    //#define FXAA_REDUCE_MUL   (1.0/8.0)

+    //#define FXAA_SPAN_MAX     8.0

+

+    vec3 rgbNW = FxaaTex(tex, posPos.zw).xyz;

+    vec3 rgbNE = FxaaTexOff(tex, posPos.zw, OffsetVec(1,0), rcpFrame.xy).xyz;

+    vec3 rgbSW = FxaaTexOff(tex, posPos.zw, OffsetVec(0,1), rcpFrame.xy).xyz;

+    vec3 rgbSE = FxaaTexOff(tex, posPos.zw, OffsetVec(1,1), rcpFrame.xy).xyz;

+

+    vec3 rgbM  = FxaaTex(tex, posPos.xy).xyz;

+

+    vec3 luma = vec3(0.299, 0.587, 0.114);

+    float lumaNW = dot(rgbNW, luma);

+    float lumaNE = dot(rgbNE, luma);

+    float lumaSW = dot(rgbSW, luma);

+    float lumaSE = dot(rgbSE, luma);

+    float lumaM  = dot(rgbM,  luma);

+

+    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));

+    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));

+

+    vec2 dir;

+    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));

+    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));

+

+    float dirReduce = max(

+        (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * m_ReduceMul),

+        FXAA_REDUCE_MIN);

+    float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);

+    dir = min(vec2( m_SpanMax,  m_SpanMax),

+          max(vec2(-m_SpanMax, -m_SpanMax),

+          dir * rcpDirMin)) * rcpFrame.xy;

+

+    vec3 rgbA = (1.0/2.0) * (

+        FxaaTex(tex, posPos.xy + dir * vec2(1.0/3.0 - 0.5)).xyz +

+        FxaaTex(tex, posPos.xy + dir * vec2(2.0/3.0 - 0.5)).xyz);

+    vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (

+        FxaaTex(tex, posPos.xy + dir * vec2(0.0/3.0 - 0.5)).xyz +

+        FxaaTex(tex, posPos.xy + dir * vec2(3.0/3.0 - 0.5)).xyz);

+

+    float lumaB = dot(rgbB, luma);

+

+    if ((lumaB < lumaMin) || (lumaB > lumaMax))

+    {

+        return rgbA;

+    }

+    else

+    {

+        return rgbB; 

+    }

+}

+

+void main()

+{

+    vec2 rcpFrame = vec2(1.0) / g_Resolution;

+    gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, rcpFrame), 1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/FXAA.j3md b/engine/src/core-effects/Common/MatDefs/Post/FXAA.j3md
new file mode 100644
index 0000000..6685585
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/FXAA.j3md
@@ -0,0 +1,20 @@
+MaterialDef FXAA {

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Float SubPixelShift

+        Float VxOffset

+        Float SpanMax

+        Float ReduceMul

+    }

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/FXAA.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/FXAA.frag

+        WorldParameters {

+            WorldViewProjectionMatrix

+            Resolution

+        }

+    }

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/FXAA.vert b/engine/src/core-effects/Common/MatDefs/Post/FXAA.vert
new file mode 100644
index 0000000..2390384
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/FXAA.vert
@@ -0,0 +1,18 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform vec2 g_Resolution;

+

+uniform float m_SubPixelShift;

+

+attribute vec4 inPosition;

+attribute vec2 inTexCoord;

+

+varying vec2 texCoord;

+varying vec4 posPos;

+

+void main() {

+    gl_Position = inPosition * 2.0 - 1.0; //vec4(pos, 0.0, 1.0);

+    texCoord = inTexCoord;

+    vec2 rcpFrame = vec2(1.0) / g_Resolution;

+    posPos.xy = inTexCoord.xy;

+    posPos.zw = inTexCoord.xy - (rcpFrame * vec2(0.5 + m_SubPixelShift));

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fade.frag b/engine/src/core-effects/Common/MatDefs/Post/Fade.frag
new file mode 100644
index 0000000..b616239
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fade.frag
@@ -0,0 +1,11 @@
+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+

+uniform float m_Value;

+

+void main() {

+       vec4 texVal = texture2D(m_Texture, texCoord);

+

+       gl_FragColor = texVal * m_Value;

+

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fade.j3md b/engine/src/core-effects/Common/MatDefs/Post/Fade.j3md
new file mode 100644
index 0000000..e93b276
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fade.j3md
@@ -0,0 +1,34 @@
+MaterialDef Fade {
+
+    MaterialParameters {
+        Int NumSamples
+        Texture2D Texture
+        Float Value
+    }
+
+    Technique {
+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert
+        FragmentShader GLSL150: Common/MatDefs/Post/Fade15.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+
+        Defines {
+            RESOLVE_MS : NumSamples
+        }
+    }
+
+    Technique {
+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert
+        FragmentShader GLSL100: Common/MatDefs/Post/Fade.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix           
+        }
+    }
+
+    Technique FixedFunc {
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fade15.frag b/engine/src/core-effects/Common/MatDefs/Post/Fade15.frag
new file mode 100644
index 0000000..c99de34
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fade15.frag
@@ -0,0 +1,11 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform float m_Value;

+

+in vec2 texCoord;

+

+void main() {

+    vec4 texVal = getColor(m_Texture, texCoord);

+    gl_FragColor = texVal * m_Value;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fog.frag b/engine/src/core-effects/Common/MatDefs/Post/Fog.frag
new file mode 100644
index 0000000..7321b15
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fog.frag
@@ -0,0 +1,21 @@
+uniform sampler2D m_Texture;

+uniform sampler2D m_DepthTexture;

+varying vec2 texCoord;

+

+uniform vec4 m_FogColor;

+uniform float m_FogDensity;

+uniform float m_FogDistance;

+

+vec2 m_FrustumNearFar=vec2(1.0,m_FogDistance);

+const float LOG2 = 1.442695;

+

+void main() {

+       vec4 texVal = texture2D(m_Texture, texCoord);      

+       float fogVal =texture2D(m_DepthTexture,texCoord).r;

+       float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - fogVal* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+

+       float fogFactor = exp2( -m_FogDensity * m_FogDensity * depth *  depth * LOG2 );

+       fogFactor = clamp(fogFactor, 0.0, 1.0);

+       gl_FragColor =mix(m_FogColor,texVal,fogFactor);

+

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fog.j3md b/engine/src/core-effects/Common/MatDefs/Post/Fog.j3md
new file mode 100644
index 0000000..e5d6c8d
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fog.j3md
@@ -0,0 +1,39 @@
+MaterialDef Fade {
+
+    MaterialParameters {
+        Int NumSamples
+        Int NumSamplesDepth
+        Texture2D Texture
+        Texture2D DepthTexture
+        Vector4 FogColor;
+        Float FogDensity;
+        Float FogDistance;
+    }
+
+    Technique {
+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert
+        FragmentShader GLSL150: Common/MatDefs/Post/Fog15.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+
+        Defines {
+            RESOLVE_MS : NumSamples
+            RESOLVE_DEPTH_MS : NumSamplesDepth
+        }
+    }
+
+    Technique {
+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert
+        FragmentShader GLSL100: Common/MatDefs/Post/Fog.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+    }
+
+    Technique FixedFunc {
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Fog15.frag b/engine/src/core-effects/Common/MatDefs/Post/Fog15.frag
new file mode 100644
index 0000000..65a3407
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Fog15.frag
@@ -0,0 +1,24 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+

+uniform vec4 m_FogColor;

+uniform float m_FogDensity;

+uniform float m_FogDistance;

+

+in vec2 texCoord;

+

+vec2 m_FrustumNearFar=vec2(1.0,m_FogDistance);

+const float LOG2 = 1.442695;

+

+void main() {

+       vec4 texVal = getColor(m_Texture, texCoord);

+       float fogVal = getDepth(m_DepthTexture,texCoord).r;

+       float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - fogVal* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+

+       float fogFactor = exp2( -m_FogDensity * m_FogDensity * depth *  depth * LOG2 );

+       fogFactor = clamp(fogFactor, 0.0, 1.0);

+       gl_FragColor =mix(m_FogColor,texVal,fogFactor);

+

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.frag b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.frag
new file mode 100644
index 0000000..90b2ade
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.frag
@@ -0,0 +1,23 @@
+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+

+uniform float m_gamma;

+

+vec3 gamma(vec3 L,float gamma)

+{

+	return pow(L, vec3(1.0 / gamma));

+}

+

+void main() {

+    vec4 texVal = texture2D(m_Texture, texCoord);

+ 

+ 	if(m_gamma > 0.0)

+ 	{

+    	texVal.rgb = gamma(texVal.rgb , m_gamma);

+ 	}

+ 	#ifdef COMPUTE_LUMA

+ 		texVal.a = dot(texVal.rgb, vec3(0.299, 0.587, 0.114));

+ 	#endif

+ 	

+    gl_FragColor = texVal;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.j3md b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.j3md
new file mode 100644
index 0000000..b9c94c0
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection.j3md
@@ -0,0 +1,39 @@
+MaterialDef GammaCorrection {

+ 

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Float gamma

+        Boolean computeLuma

+    }

+ 

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/GammaCorrection15.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+        

+        Defines {

+            COMPUTE_LUMA : computeLuma

+        }

+    }

+ 

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/GammaCorrection.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+        

+         Defines {

+            COMPUTE_LUMA : computeLuma

+        }

+    }

+ 

+    Technique FixedFunc {

+    }

+ 

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection15.frag b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection15.frag
new file mode 100644
index 0000000..b786190
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/GammaCorrection15.frag
@@ -0,0 +1,26 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+ 

+uniform COLORTEXTURE m_Texture;

+in vec2 texCoord;

+ 

+uniform float m_gamma;

+

+vec3 gamma(vec3 L,float gamma)

+{

+	return pow(L, vec3(1.0 / gamma));

+}

+ 

+void main() {

+    vec4 texVal = texture2D(m_Texture, texCoord);

+ 

+ 	if(m_gamma > 0.0)

+ 	{

+    	texVal.rgb = gamma(texVal.rgb , m_gamma);

+ 	}

+ 	

+ 	#ifdef COMPUTE_LUMA

+ 		texVal.a = dot(texVal.rgb, vec3(0.299, 0.587, 0.114));

+ 	#endif

+ 

+    gl_FragColor = texVal;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/LightScattering.frag b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.frag
new file mode 100644
index 0000000..683bbea
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.frag
@@ -0,0 +1,36 @@
+uniform sampler2D m_Texture;

+uniform sampler2D m_DepthTexture;

+uniform int m_NbSamples;

+uniform float m_BlurStart;

+uniform float m_BlurWidth;

+uniform float m_LightDensity;

+uniform bool m_Display;

+

+varying vec2 lightPos;

+varying vec2 texCoord;

+

+void main(void)

+{

+   if(m_Display){

+

+       vec4 colorRes= texture2D(m_Texture,texCoord);

+       float factor=(m_BlurWidth/float(m_NbSamples-1.0));

+       float scale;

+       vec2 texCoo=texCoord-lightPos;

+       vec2 scaledCoord;

+       vec4 res = vec4(0.0);

+       for(int i=0; i<m_NbSamples; i++) {

+            scale = i * factor + m_BlurStart ;

+            scaledCoord=texCoo*scale+lightPos;

+            if(texture2D(m_DepthTexture,scaledCoord).r==1.0){

+                res += texture2D(m_Texture,scaledCoord);

+            }

+        }

+        res /= m_NbSamples;

+

+        //Blend the original color with the averaged pixels

+        gl_FragColor =mix( colorRes, res, m_LightDensity);

+    }else{

+        gl_FragColor= texture2D(m_Texture,texCoord);

+    }

+}

diff --git a/engine/src/core-effects/Common/MatDefs/Post/LightScattering.j3md b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.j3md
new file mode 100644
index 0000000..37e0e85
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.j3md
@@ -0,0 +1,41 @@
+MaterialDef Light Scattering {

+ MaterialParameters {

+        Int NumSamples

+        Int NumSamplesDepth

+        Texture2D Texture

+        Texture2D DepthTexture       

+        Vector3 LightPosition

+        Int NbSamples

+        Float BlurStart

+        Float BlurWidth

+        Float LightDensity

+        Boolean Display

+        Boolean multiSampledDepth

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/LightScattering15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/LightScattering15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+            RESOLVE_DEPTH_MS : NumSamplesDepth

+        }

+    }

+

+    Technique {

+        VertexShader GLSL120:   Common/MatDefs/Post/LightScattering.vert

+        FragmentShader GLSL120: Common/MatDefs/Post/LightScattering.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/LightScattering.vert b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.vert
new file mode 100644
index 0000000..f58f66e
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/LightScattering.vert
@@ -0,0 +1,14 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform vec3 m_LightPosition;

+

+attribute vec4 inPosition;

+attribute vec2 inTexCoord;

+varying vec2 texCoord;

+varying vec2 lightPos;

+

+void main() {

+    vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy;

+    gl_Position = vec4(pos, 0.0, 1.0);

+    lightPos=m_LightPosition.xy;

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.frag b/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.frag
new file mode 100644
index 0000000..2a5ae26
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.frag
@@ -0,0 +1,39 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+

+uniform int m_NbSamples;

+uniform float m_BlurStart;

+uniform float m_BlurWidth;

+uniform float m_LightDensity;

+uniform bool m_Display;

+

+in vec2 lightPos;

+in vec2 texCoord;

+

+void main(void)

+{

+   if(m_Display){

+

+       vec4 colorRes= getColor(m_Texture,texCoord);

+       float factor=(m_BlurWidth/float(m_NbSamples-1.0));

+       float scale;

+       vec2 texCoo=texCoord-lightPos;

+       vec2 scaledCoord;

+       vec4 res = vec4(0.0);

+       for(int i=0; i<m_NbSamples; i++) {

+            scale = i * factor + m_BlurStart ;

+            scaledCoord=texCoo*scale+lightPos;            

+            if(fetchTextureSample(m_DepthTexture, scaledCoord,0).r==1.0){

+                res += fetchTextureSample(m_Texture,scaledCoord,0);

+            }

+        }

+        res /= m_NbSamples;

+

+        //Blend the original color with the averaged pixels

+        gl_FragColor =mix( colorRes, res, m_LightDensity);

+    }else{

+        gl_FragColor= getColor(m_Texture,texCoord);

+    }

+}

diff --git a/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.vert b/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.vert
new file mode 100644
index 0000000..990951b
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/LightScattering15.vert
@@ -0,0 +1,14 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform vec3 m_LightPosition;

+

+in vec4 inPosition;

+in vec2 inTexCoord;

+out vec2 texCoord;

+out vec2 lightPos;

+

+void main() {

+    vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy;

+    gl_Position = vec4(pos, 0.0, 1.0);

+    lightPos=m_LightPosition.xy;

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Overlay.frag b/engine/src/core-effects/Common/MatDefs/Post/Overlay.frag
new file mode 100644
index 0000000..15d4bcb
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Overlay.frag
@@ -0,0 +1,9 @@
+uniform sampler2D m_Texture;

+uniform vec4 m_Color;

+varying vec2 texCoord;

+

+void main() {

+      vec4 texVal = texture2D(m_Texture, texCoord);

+      gl_FragColor = texVal * m_Color;

+}

+

diff --git a/engine/src/core-effects/Common/MatDefs/Post/Overlay.j3md b/engine/src/core-effects/Common/MatDefs/Post/Overlay.j3md
new file mode 100644
index 0000000..fbf2d24
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Overlay.j3md
@@ -0,0 +1,36 @@
+MaterialDef Default GUI {

+

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture

+        Color Color        

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/Overlay15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+        }

+

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/Overlay.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Overlay15.frag b/engine/src/core-effects/Common/MatDefs/Post/Overlay15.frag
new file mode 100644
index 0000000..7dc4d1e
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Overlay15.frag
@@ -0,0 +1,11 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform vec4 m_Color;

+in vec2 texCoord;

+

+void main() {

+      vec4 texVal = getColor(m_Texture, texCoord);

+      gl_FragColor = texVal * m_Color;

+}

+

diff --git a/engine/src/core-effects/Common/MatDefs/Post/Post.vert b/engine/src/core-effects/Common/MatDefs/Post/Post.vert
new file mode 100644
index 0000000..6d80905
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Post.vert
@@ -0,0 +1,10 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec4 inPosition;

+attribute vec2 inTexCoord;

+varying vec2 texCoord;

+

+void main() {  

+    gl_Position = inPosition * 2.0 - 1.0; //vec4(pos, 0.0, 1.0);

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Post15.vert b/engine/src/core-effects/Common/MatDefs/Post/Post15.vert
new file mode 100644
index 0000000..367c330
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Post15.vert
@@ -0,0 +1,12 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+in vec4 inPosition;

+in vec2 inTexCoord;

+

+out vec2 texCoord;

+

+void main() {

+    vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy;

+    gl_Position = vec4(pos, 0.0, 1.0);

+    texCoord = inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Posterization.frag b/engine/src/core-effects/Common/MatDefs/Post/Posterization.frag
new file mode 100644
index 0000000..d184bc6
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Posterization.frag
@@ -0,0 +1,18 @@
+uniform sampler2D m_Texture;

+varying vec2 texCoord;

+ 

+uniform int m_NumColors;

+uniform float m_Gamma;

+uniform float m_Strength;

+ 

+void main() {

+    vec4 texVal = texture2D(m_Texture, texCoord);

+ 

+    texVal = pow(texVal, vec4(m_Gamma));

+    texVal = texVal * vec4(m_NumColors);

+    texVal = floor(texVal);

+    texVal = texVal / vec4(m_NumColors);

+    texVal = pow(texVal, vec4(1.0/m_Gamma));

+ 

+    gl_FragColor = mix(texture2D(m_Texture, texCoord), texVal, m_Strength);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Posterization.j3md b/engine/src/core-effects/Common/MatDefs/Post/Posterization.j3md
new file mode 100644
index 0000000..244dcbc
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Posterization.j3md
@@ -0,0 +1,32 @@
+MaterialDef Posterization {

+ 

+    MaterialParameters {

+        Int NumSamples

+        Texture2D Texture;

+        Int NumColors;

+        Float Gamma;

+        Float Strength;

+    }

+ 

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/Post/Posterization15.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+ 

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL100: Common/MatDefs/Post/Posterization.frag

+ 

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+ 

+    Technique FixedFunc {

+    }

+ 

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/Posterization15.frag b/engine/src/core-effects/Common/MatDefs/Post/Posterization15.frag
new file mode 100644
index 0000000..f55ac5b
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/Posterization15.frag
@@ -0,0 +1,20 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+ 

+uniform COLORTEXTURE m_Texture;

+in vec2 texCoord;

+ 

+uniform int m_NumColors;

+uniform float m_Gamma;

+uniform float m_Strength;

+ 

+void main() {

+    vec4 texVal = getColor(m_Texture, texCoord);

+ 

+    texVal = pow(texVal, vec4(m_Gamma));

+    texVal = texVal * m_NumColors;

+    texVal = floor(texVal);

+    texVal = texVal / m_NumColors;

+    texVal = pow(texVal, vec4(1.0/m_Gamma));

+ 

+    gl_FragColor = mix(getColor(m_Texture, texCoord), texVal, m_Strength);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Post/bloomExtract.frag b/engine/src/core-effects/Common/MatDefs/Post/bloomExtract.frag
new file mode 100644
index 0000000..23aa45e
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/bloomExtract.frag
@@ -0,0 +1,29 @@
+uniform float m_ExposurePow;

+uniform float m_ExposureCutoff;

+uniform sampler2D m_Texture;

+

+varying vec2 texCoord;

+

+#ifdef HAS_GLOWMAP

+  uniform sampler2D m_GlowMap;

+#endif

+

+void main(){ 

+   vec4 color = vec4(0.0);

+   #ifdef DO_EXTRACT

+    color = texture2D( m_Texture, texCoord );

+    if ( (color.r+color.g+color.b)/3.0 < m_ExposureCutoff ) {

+          color = vec4(0.0);

+    }else{

+          color = pow(color,vec4(m_ExposurePow));

+    }

+   #endif

+

+   #ifdef HAS_GLOWMAP

+        vec4 glowColor = texture2D(m_GlowMap, texCoord);

+        glowColor = pow(glowColor, vec4(m_ExposurePow));

+        color += glowColor;

+   #endif

+   

+   gl_FragColor = color;

+}

diff --git a/engine/src/core-effects/Common/MatDefs/Post/bloomExtract15.frag b/engine/src/core-effects/Common/MatDefs/Post/bloomExtract15.frag
new file mode 100644
index 0000000..7194f9c
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/bloomExtract15.frag
@@ -0,0 +1,33 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+

+uniform float m_ExposurePow;

+uniform float m_ExposureCutoff;

+

+in vec2 texCoord;

+out vec4 outFragColor;

+

+#ifdef HAS_GLOWMAP

+  uniform sampler2D m_GlowMap;

+#endif

+

+void main(){

+   vec4 color = vec4(0.0);

+   #ifdef DO_EXTRACT

+     color = getColorSingle(m_Texture, texCoord);

+     if ( (color.r + color.g + color.b) / 3.0 >= m_ExposureCutoff ) {

+         color = pow(color, vec4(m_ExposurePow));

+     }else{

+         color = vec4(0.0);

+     }

+   #endif

+

+   #ifdef HAS_GLOWMAP

+        vec4 glowColor = texture2D( m_GlowMap, texCoord );

+        glowColor = pow(glowColor, vec4(m_ExposurePow));

+        color += glowColor;

+   #endif

+   

+   outFragColor = color;

+}

diff --git a/engine/src/core-effects/Common/MatDefs/Post/bloomFinal.frag b/engine/src/core-effects/Common/MatDefs/Post/bloomFinal.frag
new file mode 100644
index 0000000..9499e58
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/bloomFinal.frag
@@ -0,0 +1,12 @@
+uniform sampler2D m_Texture;

+uniform sampler2D m_BloomTex;

+uniform float m_BloomIntensity;

+

+varying vec2 texCoord;

+

+void main(){

+   vec4 colorRes = texture2D(m_Texture, texCoord);

+   vec4 bloom = texture2D(m_BloomTex, texCoord);

+   gl_FragColor = bloom * m_BloomIntensity + colorRes;

+}

+

diff --git a/engine/src/core-effects/Common/MatDefs/Post/bloomFinal15.frag b/engine/src/core-effects/Common/MatDefs/Post/bloomFinal15.frag
new file mode 100644
index 0000000..34268c5
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Post/bloomFinal15.frag
@@ -0,0 +1,15 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+

+uniform sampler2D m_BloomTex;

+uniform float m_BloomIntensity;

+

+in vec2 texCoord;

+

+void main(){

+  vec4 colorRes = getColor(m_Texture,texCoord);

+  vec4 bloom = texture2D(m_BloomTex, texCoord);

+  gl_FragColor = bloom * m_BloomIntensity + colorRes;

+}

+

diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/Textures/random.png b/engine/src/core-effects/Common/MatDefs/SSAO/Textures/random.png
new file mode 100644
index 0000000..e19b7f5
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/Textures/random.png
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/normal.frag b/engine/src/core-effects/Common/MatDefs/SSAO/normal.frag
new file mode 100644
index 0000000..289c4c0
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/normal.frag
@@ -0,0 +1,21 @@
+varying vec3 normal;

+varying vec2 texCoord;

+

+

+#ifdef DIFFUSEMAP_ALPHA

+    uniform sampler2D m_DiffuseMap;

+    uniform float m_AlphaDiscardThreshold;

+#endif

+

+void main(void)

+{

+

+    #ifdef DIFFUSEMAP_ALPHA

+        if(texture2D(m_DiffuseMap,texCoord).a<m_AlphaDiscardThreshold){

+            discard;

+        }

+    #endif

+    gl_FragColor = vec4(normal.xy* 0.5 + 0.5,-normal.z* 0.5 + 0.5, 1.0);

+

+}

+

diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/normal.vert b/engine/src/core-effects/Common/MatDefs/SSAO/normal.vert
new file mode 100644
index 0000000..24a76cb
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/normal.vert
@@ -0,0 +1,16 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat3 g_NormalMatrix;

+

+attribute vec3 inPosition;

+attribute vec3 inNormal;

+attribute vec4 inTexCoord;

+

+varying vec3 normal;

+varying vec2 texCoord;

+

+void main(void)

+{

+   texCoord=inTexCoord.xy;

+   normal = normalize(g_NormalMatrix * inNormal);

+   gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssao.frag b/engine/src/core-effects/Common/MatDefs/SSAO/ssao.frag
new file mode 100644
index 0000000..3cd860d
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssao.frag
@@ -0,0 +1,104 @@
+uniform vec2 g_Resolution;

+uniform vec2 m_FrustumNearFar;

+uniform sampler2D m_Texture;

+uniform sampler2D m_Normals;

+uniform sampler2D m_DepthTexture;

+uniform vec3 m_FrustumCorner;

+uniform float m_SampleRadius;

+uniform float m_Intensity;

+uniform float m_Scale;

+uniform float m_Bias;

+uniform bool m_UseOnlyAo;

+uniform bool m_UseAo;

+uniform vec2[4] m_Samples;

+

+varying vec2 texCoord;

+

+float depthv;

+

+vec3 getPosition(in vec2 uv){

+  //Reconstruction from depth

+  depthv =texture2D(m_DepthTexture,uv).r;

+  float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - depthv* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+

+  //one frustum corner method

+  float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, uv.x);

+  float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, uv.y);

+

+  return depth* vec3(x, y, m_FrustumCorner.z);

+}

+

+vec3 getNormal(in vec2 uv){

+  return normalize(texture2D(m_Normals, uv).xyz * 2.0 - 1.0);

+}

+

+vec2 getRandom(in vec2 uv){

+   float rand=(fract(uv.x*(g_Resolution.x/2.0))*0.25)+(fract(uv.y*(g_Resolution.y/2.0))*0.5);

+   return normalize(vec2(rand,rand));

+}

+

+float doAmbientOcclusion(in vec2 tc, in vec3 pos, in vec3 norm){

+   vec3 diff = getPosition(tc)- pos;

+   vec3 v = normalize(diff);

+   float d = length(diff) * m_Scale;

+

+   return max(0.0, dot(norm, v) - m_Bias) * ( 1.0/(1.0 + d) ) * m_Intensity;

+}

+

+vec4 getColor(in float result){

+

+ if(m_UseOnlyAo){

+     return vec4(result,result,result, 1.0);

+ }

+ if(m_UseAo){

+      return texture2D(m_Texture,texCoord)* vec4(result,result,result, 1.0);

+  }else{

+      return texture2D(m_Texture,texCoord);

+  }

+

+}

+

+vec2 reflection(in vec2 v1,in vec2 v2){

+    vec2 result= 2.0 * dot(v2, v1) * v2;

+    result=v1-result;

+    return result;

+}

+

+

+//const vec2 vec[4] = vec2[4](vec2(1.0,0.0), vec2(-1.0,0.0), vec2(0.0,1.0), vec2(0.0,-1.0));

+void main(){

+

+   float result;

+

+   //vec2 vec[4] = { vec2(1.0, 0.0), vec2(-1.0, 0.0), vec2(0.0, 1.0), vec2(0.0, -1.0) };

+   vec3 position = getPosition(texCoord);

+    //optimization, do not calculate AO if depth is 1

+   if(depthv==1.0){

+        gl_FragColor=getColor(1.0);

+        return;

+   }

+   vec3 normal = getNormal(texCoord);

+   vec2 rand = getRandom(texCoord);

+

+   float ao = 0.0;

+   float rad =m_SampleRadius / position.z;

+

+

+   int iterations = 4;

+   for (int j = 0; j < iterations; ++j){

+      vec2 coord1 = reflection(vec2(m_Samples[j]), vec2(rand)) * vec2(rad,rad);

+      vec2 coord2 = vec2(coord1.x* 0.707 - coord1.y* 0.707, coord1.x* 0.707 + coord1.y* 0.707) ;

+

+      ao += doAmbientOcclusion(texCoord + coord1.xy * 0.25, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord2 * 0.50, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord1.xy * 0.75, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord2 * 1.00, position, normal);

+

+   }

+   ao /= float(iterations) * 4.0;

+   result = 1.0-ao;

+

+   gl_FragColor=getColor(result);

+

+//gl_FragColor=vec4(normal,1.0);

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssao.j3md b/engine/src/core-effects/Common/MatDefs/SSAO/ssao.j3md
new file mode 100644
index 0000000..762c1e5
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssao.j3md
@@ -0,0 +1,51 @@
+MaterialDef SSAO {

+

+    MaterialParameters {

+        Int NumSamples

+        Int NumSamplesDepth

+        Texture2D Texture

+        Texture2D RandomMap

+        Texture2D Normals

+        Texture2D DepthTexture

+        Vector3 FrustumCorner

+        Float SampleRadius

+        Float Intensity

+        Float Scale

+        Float Bias

+        Vector2 FrustumNearFar

+        Vector2Array Samples

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/SSAO/ssao15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+        }

+

+        Defines {

+            RESOLVE_MS : NumSamples

+            RESOLVE_DEPTH_MS : NumSamplesDepth

+        }

+    }

+

+    Technique {

+        VertexShader GLSL120:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL120: Common/MatDefs/SSAO/ssao.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+

+        }

+    }

+

+

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssao15.frag b/engine/src/core-effects/Common/MatDefs/SSAO/ssao15.frag
new file mode 100644
index 0000000..de3b846
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssao15.frag
@@ -0,0 +1,96 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+

+uniform vec2 g_Resolution;

+uniform vec2 m_FrustumNearFar;

+uniform sampler2D m_Normals;

+uniform sampler2D m_RandomMap;

+uniform vec3 m_FrustumCorner;

+uniform float m_SampleRadius;

+uniform float m_Intensity;

+uniform float m_Scale;

+uniform float m_Bias;

+uniform vec2[4] m_Samples;

+

+in vec2 texCoord;

+

+float depthv;

+

+vec3 getPosition(in vec2 uv){

+  //Reconstruction from depth

+  depthv =getDepth(m_DepthTexture,uv).r;

+  float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - depthv* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+

+  //one frustum corner method

+  float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, uv.x);

+  float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, uv.y);

+

+  return depth* vec3(x, y, m_FrustumCorner.z);

+}

+

+vec3 getNormal(in vec2 uv){

+  return normalize(texture2D(m_Normals, uv).xyz * 2.0 - 1.0);

+}

+

+vec2 getRandom(in vec2 uv){

+   //float rand=(fract(uv.x*(g_Resolution.x/2.0))*0.25)+(fract(uv.y*(g_Resolution.y/2.0))*0.5);

+   vec4 rand=texture2D(m_RandomMap,g_Resolution * uv / 128.0 * 3.0)*2.0 -1.0;

+

+   return normalize(rand.xy);

+}

+

+float doAmbientOcclusion(in vec2 tc, in vec3 pos, in vec3 norm){

+   vec3 diff = getPosition(tc)- pos;

+   vec3 v = normalize(diff);

+   float d = length(diff) * m_Scale;

+

+   return max(0.0, dot(norm, v) - m_Bias) * ( 1.0/(1.0 + d) ) * m_Intensity;

+}

+

+

+vec2 reflection(in vec2 v1,in vec2 v2){

+    vec2 result= 2.0 * dot(v2, v1) * v2;

+    result=v1-result;

+    return result;

+}

+

+

+//const vec2 vec[4] = vec2[4](vec2(1.0,0.0), vec2(-1.0,0.0), vec2(0.0,1.0), vec2(0.0,-1.0));

+void main(){

+

+   float result;

+

+   //vec2 vec[4] = { vec2(1.0, 0.0), vec2(-1.0, 0.0), vec2(0.0, 1.0), vec2(0.0, -1.0) };

+   vec3 position = getPosition(texCoord);

+    //optimization, do not calculate AO if depth is 1

+   if(depthv==1.0){

+        gl_FragColor=vec4(1.0);

+        return;

+   }

+   vec3 normal = getNormal(texCoord);

+   vec2 rand = getRandom(texCoord);

+

+   float ao = 0.0;

+   float rad =m_SampleRadius / position.z;

+

+

+   int iterations = 4;

+   for (int j = 0; j < iterations; ++j){

+      vec2 coord1 = reflection(vec2(m_Samples[j]), rand) * vec2(rad,rad);

+      vec2 coord2 = vec2(coord1.x* 0.707 - coord1.y* 0.707, coord1.x* 0.707 + coord1.y* 0.707) ;

+

+      ao += doAmbientOcclusion(texCoord + coord1.xy * 0.25, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord2 * 0.50, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord1.xy * 0.75, position, normal);

+      ao += doAmbientOcclusion(texCoord + coord2 * 1.00, position, normal);

+

+   }

+   ao /= float(iterations) * 4.0;

+   result = 1.0-ao;

+

+   gl_FragColor=vec4(result,result,result, 1.0);

+//gl_FragColor=vec4(depthv,depthv,depthv, 1.0);

+

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.frag b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.frag
new file mode 100644
index 0000000..48f4a2f
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.frag
@@ -0,0 +1,159 @@
+uniform sampler2D m_Texture;

+uniform sampler2D m_DepthTexture;

+uniform sampler2D m_SSAOMap;

+uniform vec2 g_Resolution;

+uniform bool m_UseOnlyAo;

+uniform bool m_UseAo;

+uniform float m_XScale;

+uniform float m_YScale;

+uniform vec2 m_FrustumNearFar;

+

+varying vec2 texCoord;

+

+vec4 getColor(vec4 color){

+

+    

+    #ifdef USE_ONLY_AO

+        return color;

+    #endif

+    #ifdef USE_AO

+        return texture2D(m_Texture,texCoord)* color;

+    #endif

+    

+    return texture2D(m_Texture,texCoord);

+

+}

+

+float readDepth(in vec2 uv){

+    float depthv =texture2D(m_DepthTexture,uv).r;

+    return (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - depthv* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+}

+

+ const float epsilon = 0.005;

+

+

+/*

+    const int kernelSize=7;

+   

+    vec4 bilateralFilter() {

+        vec4 color = vec4(0.0);

+

+        vec2 sample;

+        float sum = 0.0;

+        float coefZ;

+        float Zp = readDepth(texCoord);

+

+        for(int i = -(kernelSize-1); i <= (kernelSize-1); i+=2) {

+            for(int j = -(kernelSize-1); j <= (kernelSize-1); j+=2) {

+                  sample = texCoord + vec2(i,j) / g_Resolution;           

+                float zTmp =readDepth(sample);

+                coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+                sum += coefZ;

+

+                color += coefZ * texture2D(m_SSAOMap,sample);

+             

+            }

+        }

+

+        return color / sum;

+    }

+*/

+

+    vec4 convolutionFilter(){

+           vec4 sum = vec4(0.0);

+

+            float x = texCoord.x;

+            float y = texCoord.y;

+

+            float xScale = m_XScale;

+            float yScale = m_YScale;

+       

+            float zsum = 1.0;

+        float Zp =readDepth(texCoord);

+

+

+        vec2 sample = vec2(x - 2.0 * xScale, y - 2.0 * yScale);           

+        float zTmp =readDepth(sample);

+        float coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 0.0 * xScale, y - 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y - 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 1.0 * xScale, y - 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 1.0 * xScale, y - 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+  

+        sample = vec2(x - 2.0 * xScale, y - 0.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y - 0.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 1.0 * xScale, y + 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+   

+        sample = vec2(x + 1.0 * xScale, y + 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 2.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+  

+        sample = vec2(x - 0.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+

+        return  sum / zsum;

+    }

+

+

+    void main(){

+        //  float depth =texture2D(m_DepthTexture,uv).r;

+

+       gl_FragColor=getColor(convolutionFilter());

+      // gl_FragColor=getColor(bilateralFilter());

+      //  gl_FragColor=texture2D(m_SSAOMap,texCoord);

+

+    }
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.j3md b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.j3md
new file mode 100644
index 0000000..dcc5e49
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur.j3md
@@ -0,0 +1,57 @@
+MaterialDef SSAOBlur {

+

+    MaterialParameters {       

+        Int NumSamples

+        Int NumSamplesDepth

+        Texture2D Texture

+        Texture2D SSAOMap

+        Texture2D DepthTexture

+        Vector2 FrustumNearFar

+        Boolean UseAo

+        Boolean UseOnlyAo        

+        Float XScale

+        Float YScale

+    }

+

+    Technique {

+        VertexShader GLSL150:   Common/MatDefs/Post/Post15.vert

+        FragmentShader GLSL150: Common/MatDefs/SSAO/ssaoBlur15.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+        }

+

+        Defines {

+            USE_AO : UseAo

+            USE_ONLY_AO : UseOnlyAo

+            RESOLVE_MS : NumSamples

+            RESOLVE_DEPTH_MS : NumSamplesDepth

+        }

+    }

+

+    Technique {

+        VertexShader GLSL120:   Common/MatDefs/Post/Post.vert

+        FragmentShader GLSL120: Common/MatDefs/SSAO/ssaoBlur.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            Resolution

+

+        }

+        

+        Defines {

+            USE_AO : UseAo

+            USE_ONLY_AO : UseOnlyAo

+            RESOLVE_MS : NumSamples

+            RESOLVE_DEPTH_MS : NumSamplesDepth

+        }

+    }

+

+

+

+    Technique FixedFunc {

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur15.frag b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur15.frag
new file mode 100644
index 0000000..e90a76f
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/SSAO/ssaoBlur15.frag
@@ -0,0 +1,160 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+uniform sampler2D m_SSAOMap;

+uniform vec2 g_Resolution;

+uniform bool m_UseOnlyAo;

+uniform bool m_UseAo;

+uniform float m_XScale;

+uniform float m_YScale;

+uniform vec2 m_FrustumNearFar;

+

+in vec2 texCoord;

+

+vec4 getResult(vec4 color){

+ 

+    #ifdef USE_ONLY_AO

+        return color;

+    #endif

+    #ifdef USE_AO

+        return getColor(m_Texture,texCoord)* color;

+    #endif

+    

+    return getColor(m_Texture,texCoord);

+

+}

+

+float readDepth(in vec2 uv){

+    float depthv =fetchTextureSample(m_DepthTexture,uv,0).r;

+    return (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - depthv* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+}

+

+ const float epsilon = 0.005;

+

+

+/*

+    const int kernelSize=7;

+   

+    vec4 bilateralFilter() {

+        vec4 color = vec4(0.0);

+

+        vec2 sample;

+        float sum = 0.0;

+        float coefZ;

+        float Zp = readDepth(texCoord);

+

+        for(int i = -(kernelSize-1); i <= (kernelSize-1); i+=2) {

+            for(int j = -(kernelSize-1); j <= (kernelSize-1); j+=2) {

+                  sample = texCoord + vec2(i,j) / g_Resolution;           

+                float zTmp =readDepth(sample);

+                coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+                sum += coefZ;

+

+                color += coefZ * texture2D(m_SSAOMap,sample);

+             

+            }

+        }

+

+        return color / sum;

+    }

+*/

+

+    vec4 convolutionFilter(){

+           vec4 sum = vec4(0.0);

+

+            float x = texCoord.x;

+            float y = texCoord.y;

+

+            float xScale = m_XScale;

+            float yScale = m_YScale;

+       

+            float zsum = 1.0;

+        float Zp =readDepth(texCoord);

+

+

+        vec2 sample = vec2(x - 2.0 * xScale, y - 2.0 * yScale);           

+        float zTmp =readDepth(sample);

+        float coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 0.0 * xScale, y - 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y - 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 1.0 * xScale, y - 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 1.0 * xScale, y - 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+  

+        sample = vec2(x - 2.0 * xScale, y - 0.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y - 0.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 1.0 * xScale, y + 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+   

+        sample = vec2(x + 1.0 * xScale, y + 1.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x - 2.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+  

+        sample = vec2(x - 0.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+        sample = vec2(x + 2.0 * xScale, y + 2.0 * yScale);           

+        zTmp =readDepth(sample);

+        coefZ = 1.0 / (epsilon + abs(Zp - zTmp));               

+        zsum += coefZ;

+        sum += coefZ* texture2D( m_SSAOMap, sample);

+

+

+        return  sum / zsum;

+    }

+

+

+    void main(){

+        //  float depth =texture2D(m_DepthTexture,uv).r;

+

+       gl_FragColor=getResult(convolutionFilter());

+      // gl_FragColor=getResult(bilateralFilter());

+      //  gl_FragColor=getColor(m_SSAOMap,texCoord);

+

+    }
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Water/SimpleWater.j3md b/engine/src/core-effects/Common/MatDefs/Water/SimpleWater.j3md
new file mode 100644
index 0000000..1288324
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/SimpleWater.j3md
@@ -0,0 +1,34 @@
+MaterialDef Simple Water {
+
+    MaterialParameters {
+        Texture2D water_reflection
+        Texture2D water_refraction
+        Texture2D water_depthmap
+        Texture2D water_normalmap
+        Texture2D water_dudvmap
+        Vector4 waterColor
+        Vector3 lightPos
+        Float time
+        Float waterDepth
+        Vector4 distortionScale
+        Vector4 distortionMix
+        Vector4 texScale
+        Vector2 FrustumNearFar
+        Float waterTransparency
+    }
+
+    Technique {
+        VertexShader GLSL100:   Common/MatDefs/Water/simple_water.vert
+        FragmentShader GLSL100: Common/MatDefs/Water/simple_water.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            Resolution
+            CameraPosition
+        }
+    }
+
+    Technique FixedFunc {
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/caustics.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/caustics.jpg
new file mode 100644
index 0000000..fb4f21c
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/caustics.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/dudv_map.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/dudv_map.jpg
new file mode 100644
index 0000000..a2734bb
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/dudv_map.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/foam.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam.jpg
new file mode 100644
index 0000000..fc17ac2
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/foam2.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam2.jpg
new file mode 100644
index 0000000..ef19b87
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam2.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/foam3.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam3.jpg
new file mode 100644
index 0000000..8295b3f
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/foam3.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/heightmap.jpg b/engine/src/core-effects/Common/MatDefs/Water/Textures/heightmap.jpg
new file mode 100644
index 0000000..cfb5846
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/heightmap.jpg
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Textures/water_normalmap.dds b/engine/src/core-effects/Common/MatDefs/Water/Textures/water_normalmap.dds
new file mode 100644
index 0000000..13582e1
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Textures/water_normalmap.dds
Binary files differ
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Water.frag b/engine/src/core-effects/Common/MatDefs/Water/Water.frag
new file mode 100644
index 0000000..928ee22
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Water.frag
@@ -0,0 +1,402 @@
+// Water pixel shader

+// Copyright (C) JMonkeyEngine 3.0

+// by Remy Bouquet (nehon) for JMonkeyEngine 3.0

+// original HLSL version by Wojciech Toman 2009

+

+uniform sampler2D m_HeightMap;

+uniform sampler2D m_Texture;

+uniform sampler2D m_DepthTexture;

+uniform sampler2D m_NormalMap;

+uniform sampler2D m_FoamMap;

+uniform sampler2D m_CausticsMap;

+uniform sampler2D m_ReflectionMap;

+

+uniform mat4 m_ViewProjectionMatrixInverse;

+uniform mat4 m_TextureProjMatrix;

+uniform vec3 m_CameraPosition;

+

+uniform float m_WaterHeight;

+uniform float m_Time;

+uniform float m_WaterTransparency;

+uniform float m_NormalScale;

+uniform float m_R0;

+uniform float m_MaxAmplitude;

+uniform vec3 m_LightDir;

+uniform vec4 m_LightColor;

+uniform float m_ShoreHardness;

+uniform float m_FoamHardness;

+uniform float m_RefractionStrength;

+uniform vec3 m_FoamExistence;

+uniform vec3 m_ColorExtinction;

+uniform float m_Shininess;

+uniform vec4 m_WaterColor;

+uniform vec4 m_DeepWaterColor;

+uniform vec2 m_WindDirection;

+uniform float m_SunScale;

+uniform float m_WaveScale;

+uniform float m_UnderWaterFogDistance;

+uniform float m_CausticsIntensity;

+

+vec2 scale = vec2(m_WaveScale, m_WaveScale);

+float refractionScale = m_WaveScale;

+

+// Modifies 4 sampled normals. Increase first values to have more

+// smaller "waves" or last to have more bigger "waves"

+const vec4 normalModifier = vec4(3.0, 2.0, 4.0, 10.0);

+// Strength of displacement along normal.

+// Strength of displacement along normal.

+uniform float m_ReflectionDisplace;

+// Water transparency along eye vector.

+const float visibility = 3.0;

+// foam intensity

+uniform float m_FoamIntensity ;

+

+varying vec2 texCoord;

+

+mat3 MatrixInverse(in mat3 inMatrix){  

+    float det = dot(cross(inMatrix[0], inMatrix[1]), inMatrix[2]);

+    mat3 T = transpose(inMatrix);

+    return mat3(cross(T[1], T[2]),

+        cross(T[2], T[0]),

+        cross(T[0], T[1])) / det;

+}

+

+

+mat3 computeTangentFrame(in vec3 N, in vec3 P, in vec2 UV) {

+    vec3 dp1 = dFdx(P);

+    vec3 dp2 = dFdy(P);

+    vec2 duv1 = dFdx(UV);

+    vec2 duv2 = dFdy(UV);

+

+    // solve the linear system

+    mat3 M = mat3(dp1, dp2, cross(dp1, dp2));

+    //vec3 dp1xdp2 = cross(dp1, dp2);

+    mat3 inverseM = MatrixInverse(M);

+    //mat2x3 inverseM = mat2x3(cross(dp2, dp1xdp2), cross(dp1xdp2, dp1));

+

+    vec3 T = inverseM * vec3(duv1.x, duv2.x, 0.0);

+    vec3 B = inverseM * vec3(duv1.y, duv2.y, 0.0);

+

+    //vec3 T = inverseM * vec2(duv1.x, duv2.x);

+    //vec3 B = inverseM * vec2(duv1.y, duv2.y);

+

+    // construct tangent frame

+    float maxLength = max(length(T), length(B));

+    T = T / maxLength;

+    B = B / maxLength;

+

+    //vec3 tangent = normalize(T);

+    //vec3 binormal = normalize(B);

+

+    return mat3(T, B, N);

+}

+

+float saturate(in float val){

+    return clamp(val,0.0,1.0);

+}

+

+vec3 saturate(in vec3 val){

+    return clamp(val,vec3(0.0),vec3(1.0));

+}

+

+

+vec3 getPosition(in float depth, in vec2 uv){

+    vec4 pos = vec4(uv, depth, 1.0) * 2.0 - 1.0;

+    pos = m_ViewProjectionMatrixInverse * pos;

+    return pos.xyz / pos.w;

+}

+

+// Function calculating fresnel term.

+// - normal - normalized normal vector

+// - eyeVec - normalized eye vector

+float fresnelTerm(in vec3 normal,in vec3 eyeVec){

+    float angle = 1.0 - saturate(dot(normal, eyeVec));

+    float fresnel = angle * angle;

+    fresnel = fresnel * fresnel;

+    fresnel = fresnel * angle;

+    return saturate(fresnel * (1.0 - saturate(m_R0)) + m_R0 - m_RefractionStrength);

+}

+

+vec2 m_FrustumNearFar=vec2(1.0,m_UnderWaterFogDistance);

+const float LOG2 = 1.442695;

+

+vec4 underWater(){

+

+

+    float sceneDepth = texture2D(m_DepthTexture, texCoord).r;

+    vec3 color2 = texture2D(m_Texture, texCoord).rgb;

+    

+    vec3 position = getPosition(sceneDepth, texCoord);

+    float level = m_WaterHeight;

+

+    vec3 eyeVec = position - m_CameraPosition;    

+ 

+    // Find intersection with water surface

+    vec3 eyeVecNorm = normalize(eyeVec);

+    float t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t;

+

+    vec2 texC = vec2(0.0);

+

+    float cameraDepth = length(m_CameraPosition - surfacePoint);  

+    texC = (surfacePoint.xz + eyeVecNorm.xz) * scale + m_Time * 0.03 * m_WindDirection;

+    float bias = texture2D(m_HeightMap, texC).r;

+    level += bias * m_MaxAmplitude;

+    t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    surfacePoint = m_CameraPosition + eyeVecNorm * t; 

+    eyeVecNorm = normalize(m_CameraPosition - surfacePoint);

+

+    // Find normal of water surface

+    float normal1 = texture2D(m_HeightMap, (texC + vec2(-1.0, 0.0) / 256.0)).r;

+    float normal2 = texture2D(m_HeightMap, (texC + vec2(1.0, 0.0) / 256.0)).r;

+    float normal3 = texture2D(m_HeightMap, (texC + vec2(0.0, -1.0) / 256.0)).r;

+    float normal4 = texture2D(m_HeightMap, (texC + vec2(0.0, 1.0) / 256.0)).r;

+

+    vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude));

+    vec3 normal = myNormal*-1.0;

+    float fresnel = fresnelTerm(normal, eyeVecNorm); 

+

+    vec3 refraction = color2;

+    #ifdef ENABLE_REFRACTION

+        texC = texCoord.xy *sin (fresnel+1.0);

+        refraction = texture2D(m_Texture, texC).rgb;

+    #endif 

+

+   float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale);

+   refraction = mix(mix(refraction, m_DeepWaterColor.rgb * waterCol, m_WaterTransparency),  m_WaterColor.rgb* waterCol,m_WaterTransparency);

+

+    vec3 foam = vec3(0.0);

+    #ifdef ENABLE_FOAM    

+        texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005;

+        vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005;

+

+        if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){

+            foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity  * m_FoamIntensity * 0.3 *

+               saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb;

+        }

+        foam *= m_LightColor.rgb;    

+    #endif

+

+

+

+    vec3 specular = vec3(0.0);   

+    vec3 color ;

+    float fogFactor;

+

+    if(position.y>level){

+        #ifdef ENABLE_SPECULAR

+            if(step(0.9999,sceneDepth)==1.0){

+                vec3 lightDir=normalize(m_LightDir);

+                vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm);

+                float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5);

+                specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2)));

+                specular += specular * 25.0 * saturate(m_Shininess - 0.05);

+                specular=specular * m_LightColor.rgb * 100.0;

+            }

+        #endif

+        float fogIntensity= 8.0 * m_WaterTransparency;

+        fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 );

+        fogFactor = clamp(fogFactor, 0.0, 1.0);        

+        color =mix(m_DeepWaterColor.rgb,refraction,fogFactor);   

+        specular=specular*fogFactor;    

+        color = saturate(color + max(specular, foam ));

+    }else{

+        vec3 caustics = vec3(0.0);

+        #ifdef ENABLE_CAUSTICS 

+            vec2 windDirection=m_WindDirection;

+            texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time  + position.x) * 0.01;

+            vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time  + position.z) * 0.01;

+            caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb;      

+            caustics=saturate(mix(m_WaterColor.rgb,caustics,m_CausticsIntensity));            

+            color=mix(color2,caustics,m_CausticsIntensity);          

+        #else

+            color=color2;

+        #endif

+                

+        float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+        float fogIntensity= 18 * m_WaterTransparency;

+        fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth *  fogDepth * LOG2 );

+        fogFactor = clamp(fogFactor, 0.0, 1.0);

+        color =mix(m_DeepWaterColor.rgb,color,fogFactor);

+    }

+

+    return vec4(color, 1.0);   

+}

+

+void main(){

+    float sceneDepth = texture2D(m_DepthTexture, texCoord).r;

+    float isAtFarPlane = step(0.99998, sceneDepth);

+

+    vec3 color2 = texture2D(m_Texture, texCoord).rgb;

+    vec3 color = color2;

+

+    vec3 position = getPosition(sceneDepth,texCoord);

+

+    float level = m_WaterHeight;

+

+    // If we are underwater let's go to under water function

+    if(level >= m_CameraPosition.y){

+        gl_FragColor = underWater();

+        return ;

+    }

+

+    //#ifndef ENABLE_RIPPLES

+    // This optimization won't work on NVIDIA cards if ripples are enabled

+    if(position.y > level + m_MaxAmplitude + isAtFarPlane * 100.0){

+        gl_FragColor = vec4(color2, 1.0);

+        return;

+    }

+    //#endif

+

+    vec3 eyeVec = position - m_CameraPosition;

+    float diff = level - position.y;

+    float cameraDepth = m_CameraPosition.y - position.y;

+

+    // Find intersection with water surface

+    vec3 eyeVecNorm = normalize(eyeVec);

+    float t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t;

+

+    vec2 texC;

+    int samples = 1;

+    #ifdef ENABLE_HQ_SHORELINE

+        samples = 10;

+    #endif

+    float biasFactor = 1.0/samples;

+    for (int i = 0; i < samples; i++){

+        texC = (surfacePoint.xz + eyeVecNorm.xz * biasFactor) * scale + m_Time * 0.03 * m_WindDirection;

+

+        float bias = texture2D(m_HeightMap, texC).r;

+

+        bias *= biasFactor;

+        level += bias * m_MaxAmplitude;

+        t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+        surfacePoint = m_CameraPosition + eyeVecNorm * t;

+    }

+

+    float depth = length(position - surfacePoint);

+    float depth2 = surfacePoint.y - position.y;

+

+    // XXX: HACK ALERT: Increase water depth to infinity if at far plane

+    // Prevents "foam on horizon" issue

+    // For best results, replace the "100.0" below with the

+    // highest value in the m_ColorExtinction vec3

+    depth  += isAtFarPlane * 100.0;

+    depth2 += isAtFarPlane * 100.0;

+

+    eyeVecNorm = normalize(m_CameraPosition - surfacePoint);

+

+    // Find normal of water surface

+    float normal1 = texture2D(m_HeightMap, (texC + vec2(-1.0, 0.0) / 256.0)).r;

+    float normal2 = texture2D(m_HeightMap, (texC + vec2(1.0, 0.0) / 256.0)).r;

+    float normal3 = texture2D(m_HeightMap, (texC + vec2(0.0, -1.0) / 256.0)).r;

+    float normal4 = texture2D(m_HeightMap, (texC + vec2(0.0, 1.0) / 256.0)).r;

+

+    vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude));

+    vec3 normal = vec3(0.0);

+

+    #ifdef ENABLE_RIPPLES

+        texC = surfacePoint.xz * 0.8 + m_WindDirection * m_Time* 1.6;

+        mat3 tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal0a = normalize(tangentFrame*(2.0 * texture2D(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.4 + m_WindDirection * m_Time* 0.8;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal1a = normalize(tangentFrame*(2.0 * texture2D(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.2 + m_WindDirection * m_Time * 0.4;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal2a = normalize(tangentFrame*(2.0 * texture2D(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.1 + m_WindDirection * m_Time * 0.2;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal3a = normalize(tangentFrame*(2.0 * texture2D(m_NormalMap, texC).xyz - 1.0));

+

+        normal = normalize(normal0a * normalModifier.x + normal1a * normalModifier.y +normal2a * normalModifier.z + normal3a * normalModifier.w);

+        // XXX: Here's another way to fix the terrain edge issue,

+        // But it requires GLSL 1.3 and still looks kinda incorrect

+        // around edges

+        // To make the shader 1.2 compatible we use a trick :

+        // we clamp the x value of the normal and compare it to it's former value instead of using isnan.

+        normal = clamp(normal.x,0.0,1.0)!=normal.x ? myNormal : normal;

+        //if (position.y > level){

+        //    gl_FragColor = vec4(color2 + normal*0.0001, 1.0);

+        //    return;

+        //}

+    #else

+        normal = myNormal;

+    #endif

+    

+    vec3 refraction = color2;

+    #ifdef ENABLE_REFRACTION

+        texC = texCoord.xy;

+        texC += sin(m_Time*1.8  + 3.0 * abs(position.y)) * (refractionScale * min(depth2, 1.0));

+        refraction = texture2D(m_Texture, texC).rgb;

+    #endif

+

+    vec3 waterPosition = surfacePoint.xyz;

+    waterPosition.y -= (level - m_WaterHeight);

+    vec4 texCoordProj = m_TextureProjMatrix * vec4(waterPosition, 1.0);

+

+    texCoordProj.x = texCoordProj.x + m_ReflectionDisplace * normal.x;

+    texCoordProj.z = texCoordProj.z + m_ReflectionDisplace * normal.z;

+    texCoordProj /= texCoordProj.w;

+    texCoordProj.y = 1.0 - texCoordProj.y;

+

+    vec3 reflection = texture2D(m_ReflectionMap, texCoordProj.xy).rgb;

+

+    float fresnel = fresnelTerm(normal, eyeVecNorm);

+

+    float depthN = depth * m_WaterTransparency;

+    float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale);

+    refraction = mix(mix(refraction, m_WaterColor.rgb * waterCol, saturate(depthN / visibility)),

+        m_DeepWaterColor.rgb * waterCol, saturate(depth2 / m_ColorExtinction));

+

+    vec3 foam = vec3(0.0);

+    #ifdef ENABLE_FOAM

+        texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005;

+        vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005;

+

+        if(depth2 < m_FoamExistence.x){

+            foam = (texture2D(m_FoamMap, texC).r + texture2D(m_FoamMap, texCoord2)).rgb * m_FoamIntensity;

+        }else if(depth2 < m_FoamExistence.y){

+            foam = mix((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity, vec4(0.0),

+                (depth2 - m_FoamExistence.x) / (m_FoamExistence.y - m_FoamExistence.x)).rgb;

+        }

+

+        if(m_MaxAmplitude - m_FoamExistence.z > 0.0001){

+           foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity  * m_FoamIntensity * 0.3 *

+               saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb;

+        }

+        foam *= m_LightColor.rgb;

+    #endif

+

+    vec3 specular =vec3(0.0);

+    #ifdef ENABLE_SPECULAR

+        vec3 lightDir=normalize(m_LightDir);

+        vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm);

+        float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5);

+        specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2)));

+        specular += specular * 25.0 * saturate(m_Shininess - 0.05);

+        //foam does not shine

+        specular=specular * m_LightColor.rgb - (5.0 * foam);

+    #endif

+

+    color = mix(refraction, reflection, fresnel);

+    color = mix(refraction, color, saturate(depth * m_ShoreHardness));

+    color = saturate(color + max(specular, foam ));

+    color = mix(refraction, color, saturate(depth* m_FoamHardness));

+        

+

+    // XXX: HACK ALERT:

+    // We trick the GeForces to think they have

+    // to calculate the derivatives for all these pixels by using step()!

+    // That way we won't get pixels around the edges of the terrain,

+    // Where the derivatives are undefined

+    if(position.y > level){

+            color = color2;

+    }

+

+    gl_FragColor = vec4(color,1.0);

+    

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Water.j3md b/engine/src/core-effects/Common/MatDefs/Water/Water.j3md
new file mode 100644
index 0000000..160cadb
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Water.j3md
@@ -0,0 +1,90 @@
+MaterialDef Advanced Water {
+
+    MaterialParameters {
+        Int NumSamples
+        Int NumSamplesDepth
+        Texture2D FoamMap
+        Texture2D CausticsMap
+        Texture2D NormalMap
+        Texture2D ReflectionMap
+        Texture2D HeightMap
+        Texture2D Texture
+        Texture2D DepthTexture        
+        Vector3 CameraPosition
+        Float Time
+        Vector3 frustumCorner
+        Matrix4 TextureProjMatrix
+        Matrix4 ViewProjectionMatrixInverse
+        Float WaterHeight
+        Vector3 LightDir
+        Float WaterTransparency
+        Float NormalScale
+        Float R0
+        Float MaxAmplitude
+        Color LightColor
+        Float ShoreHardness
+        Float FoamHardness
+        Float RefractionStrength
+        Float WaveScale
+        Vector3 FoamExistence
+        Float SunScale
+        Vector3 ColorExtinction
+        Float Shininess
+        Color WaterColor
+        Color DeepWaterColor
+        Vector2 WindDirection
+        Float ReflectionDisplace
+        Float FoamIntensity
+        Float CausticsIntensity
+        Float UnderWaterFogDistance
+
+        Boolean UseRipples
+        Boolean UseHQShoreline
+        Boolean UseSpecular
+        Boolean UseFoam
+        Boolean UseCaustics 
+        Boolean UseRefraction
+
+    }
+
+    Technique {
+        VertexShader   GLSL150 : Common/MatDefs/Post/Post15.vert
+        FragmentShader GLSL150 : Common/MatDefs/Water/Water15.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+
+        Defines {
+          RESOLVE_MS : NumSamples
+            RESOLVE_DEPTH_MS : NumSamplesDepth
+            ENABLE_RIPPLES : UseRipples
+            ENABLE_HQ_SHORELINE : UseHQShoreline
+            ENABLE_SPECULAR : UseSpecular
+            ENABLE_FOAM : UseFoam
+            ENABLE_CAUSTICS : UseCaustics
+            ENABLE_REFRACTION : UseRefraction
+        }
+    }
+
+    Technique {
+        VertexShader   GLSL100 : Common/MatDefs/Post/Post.vert
+        FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+        Defines {
+            ENABLE_RIPPLES : UseRipples
+            ENABLE_HQ_SHORELINE : UseHQShoreline
+            ENABLE_SPECULAR : UseSpecular
+            ENABLE_FOAM : UseFoam
+            ENABLE_CAUSTICS : UseCaustics
+            ENABLE_REFRACTION : UseRefraction
+
+        }
+    }
+
+    Technique FixedFunc {
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Water/Water15.frag b/engine/src/core-effects/Common/MatDefs/Water/Water15.frag
new file mode 100644
index 0000000..64cf280
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/Water15.frag
@@ -0,0 +1,419 @@
+#import "Common/ShaderLib/MultiSample.glsllib"

+

+// Water pixel shader

+// Copyright (C) JMonkeyEngine 3.0

+// by Remy Bouquet (nehon) for JMonkeyEngine 3.0

+// original HLSL version by Wojciech Toman 2009

+

+uniform COLORTEXTURE m_Texture;

+uniform DEPTHTEXTURE m_DepthTexture;

+

+

+uniform sampler2D m_HeightMap;

+uniform sampler2D m_NormalMap;

+uniform sampler2D m_FoamMap;

+uniform sampler2D m_CausticsMap;

+uniform sampler2D m_ReflectionMap;

+

+uniform mat4 m_ViewProjectionMatrixInverse;

+uniform mat4 m_TextureProjMatrix;

+uniform vec3 m_CameraPosition;

+

+uniform float m_WaterHeight;

+uniform float m_Time;

+uniform float m_WaterTransparency;

+uniform float m_NormalScale;

+uniform float m_R0;

+uniform float m_MaxAmplitude;

+uniform vec3 m_LightDir;

+uniform vec4 m_LightColor;

+uniform float m_ShoreHardness;

+uniform float m_FoamHardness;

+uniform float m_RefractionStrength;

+uniform vec3 m_FoamExistence;

+uniform vec3 m_ColorExtinction;

+uniform float m_Shininess;

+uniform vec4 m_WaterColor;

+uniform vec4 m_DeepWaterColor;

+uniform vec2 m_WindDirection;

+uniform float m_SunScale;

+uniform float m_WaveScale;

+uniform float m_UnderWaterFogDistance;

+uniform float m_CausticsIntensity;

+

+

+vec2 scale = vec2(m_WaveScale, m_WaveScale);

+float refractionScale = m_WaveScale;

+

+// Modifies 4 sampled normals. Increase first values to have more

+// smaller "waves" or last to have more bigger "waves"

+const vec4 normalModifier = vec4(3.0, 2.0, 4.0, 10.0);

+// Strength of displacement along normal.

+uniform float m_ReflectionDisplace;

+// Water transparency along eye vector.

+const float visibility = 3.0;

+// foam intensity

+uniform float m_FoamIntensity ;

+

+in vec2 texCoord;

+out vec4 outFragColor;

+

+mat3 MatrixInverse(in mat3 inMatrix){

+    float det = dot(cross(inMatrix[0], inMatrix[1]), inMatrix[2]);

+    mat3 T = transpose(inMatrix);

+    return mat3(cross(T[1], T[2]),

+        cross(T[2], T[0]),

+        cross(T[0], T[1])) / det;

+}

+

+

+mat3 computeTangentFrame(in vec3 N, in vec3 P, in vec2 UV) {

+    vec3 dp1 = dFdx(P);

+    vec3 dp2 = dFdy(P);

+    vec2 duv1 = dFdx(UV);

+    vec2 duv2 = dFdy(UV);

+

+    // solve the linear system

+    vec3 dp1xdp2 = cross(dp1, dp2);

+    mat2x3 inverseM = mat2x3(cross(dp2, dp1xdp2), cross(dp1xdp2, dp1));

+

+    vec3 T = inverseM * vec2(duv1.x, duv2.x);

+    vec3 B = inverseM * vec2(duv1.y, duv2.y);

+

+    // construct tangent frame

+    float maxLength = max(length(T), length(B));

+    T = T / maxLength;

+    B = B / maxLength;

+

+    return mat3(T, B, N);

+}

+

+float saturate(in float val){

+    return clamp(val,0.0,1.0);

+}

+

+vec3 saturate(in vec3 val){

+    return clamp(val,vec3(0.0),vec3(1.0));

+}

+

+vec3 getPosition(in float depth, in vec2 uv){

+    vec4 pos = vec4(uv, depth, 1.0) * 2.0 - 1.0;

+    pos = m_ViewProjectionMatrixInverse * pos;

+    return pos.xyz / pos.w;

+}

+

+// Function calculating fresnel term.

+// - normal - normalized normal vector

+// - eyeVec - normalized eye vector

+float fresnelTerm(in vec3 normal,in vec3 eyeVec){

+    float angle = 1.0 - max(0.0, dot(normal, eyeVec));

+    float fresnel = angle * angle;

+    fresnel = fresnel * fresnel;

+    fresnel = fresnel * angle;

+    return saturate(fresnel * (1.0 - saturate(m_R0)) + m_R0 - m_RefractionStrength);

+}

+

+vec2 m_FrustumNearFar=vec2(1.0,m_UnderWaterFogDistance);

+const float LOG2 = 1.442695;

+

+vec4 underWater(int sampleNum){

+

+

+    float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r;

+    vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb;

+    

+    vec3 position = getPosition(sceneDepth, texCoord);

+    float level = m_WaterHeight;

+

+    vec3 eyeVec = position - m_CameraPosition;    

+ 

+    // Find intersection with water surface

+    vec3 eyeVecNorm = normalize(eyeVec);

+    float t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t;

+

+    vec2 texC = vec2(0.0);

+

+    float cameraDepth = length(m_CameraPosition - surfacePoint);  

+    texC = (surfacePoint.xz + eyeVecNorm.xz) * scale + m_Time * 0.03 * m_WindDirection;

+    float bias = texture(m_HeightMap, texC).r;

+    level += bias * m_MaxAmplitude;

+    t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    surfacePoint = m_CameraPosition + eyeVecNorm * t; 

+    eyeVecNorm = normalize(m_CameraPosition - surfacePoint);

+

+    // Find normal of water surface

+    float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1.0,  0.0)).r;

+    float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1.0,  0.0)).r;

+    float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0.0, -1.0)).r;

+    float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0.0,  1.0)).r;

+

+    vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude));

+    vec3 normal = myNormal*-1.0;

+    float fresnel = fresnelTerm(normal, eyeVecNorm); 

+

+    vec3 refraction = color2;

+    #ifdef ENABLE_REFRACTION

+        texC = texCoord.xy *sin (fresnel+1.0);

+        #ifdef RESOLVE_MS

+            ivec2 iTexC = ivec2(texC * textureSize(m_Texture));

+            refraction = texelFetch(m_Texture, iTexC, sampleNum).rgb;

+        #else

+            ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0));

+            refraction = texelFetch(m_Texture, iTexC, 0).rgb;

+        #endif

+    #endif 

+

+   float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale);

+   refraction = mix(mix(refraction, m_DeepWaterColor.rgb * waterCol, m_WaterTransparency),  m_WaterColor.rgb* waterCol,m_WaterTransparency);

+

+    vec3 foam = vec3(0.0);

+    #ifdef ENABLE_FOAM    

+        texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005;

+        vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005;

+

+        if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){

+            foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity  * m_FoamIntensity * 0.3 *

+               saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb;

+        }

+        foam *= m_LightColor.rgb;    

+    #endif

+

+

+

+    vec3 specular = vec3(0.0);   

+    vec3 color ;

+    float fogFactor;

+

+    if(position.y>level){

+        #ifdef ENABLE_SPECULAR

+            if(step(0.9999,sceneDepth)==1.0){

+                vec3 lightDir=normalize(m_LightDir);

+                vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm);

+                float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5);

+                specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2)));

+                specular += specular * 25.0 * saturate(m_Shininess - 0.05);

+                specular=specular * m_LightColor.rgb * 100.0;

+            }

+        #endif

+        float fogIntensity= 8.0 * m_WaterTransparency;

+        fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 );

+        fogFactor = clamp(fogFactor, 0.0, 1.0);        

+        color =mix(m_DeepWaterColor.rgb,refraction,fogFactor);   

+        specular=specular*fogFactor;    

+        color = saturate(color + max(specular, foam ));

+    }else{

+        vec3 caustics = vec3(0.0);

+        #ifdef ENABLE_CAUSTICS 

+            vec2 windDirection=m_WindDirection;

+            texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time  + position.x) * 0.01;

+            vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time  + position.z) * 0.01;

+            caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb;                  

+            caustics=saturate(mix(m_WaterColor.rgb,caustics,m_CausticsIntensity));            

+            color=mix(color2,caustics,m_CausticsIntensity);

+        #else

+            color=color2;

+        #endif

+                

+        float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x));

+        float fogIntensity= 18 * m_WaterTransparency;

+        fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth *  fogDepth * LOG2 );

+        fogFactor = clamp(fogFactor, 0.0, 1.0);

+        color =mix(m_DeepWaterColor.rgb,color,fogFactor);

+    }

+

+    return vec4(color, 1.0);   

+}

+// NOTE: This will be called even for single-sampling

+vec4 main_multiSample(int sampleNum){

+    // If we are underwater let's call the underwater function

+    if(m_WaterHeight >= m_CameraPosition.y){

+

+        return underWater(sampleNum);

+    }

+

+    float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r;

+    vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb;

+

+    vec3 color = color2;

+    vec3 position = getPosition(sceneDepth, texCoord);

+

+    float level = m_WaterHeight;

+    

+    float isAtFarPlane = step(0.99998, sceneDepth);

+    //#ifndef ENABLE_RIPPLES

+    // This optimization won't work on NVIDIA cards if ripples are enabled

+    if(position.y > level + m_MaxAmplitude + isAtFarPlane * 100.0){

+

+        return vec4(color2, 1.0);

+    }

+    //#endif

+

+    vec3 eyeVec = position - m_CameraPosition;    

+    float cameraDepth = m_CameraPosition.y - position.y;

+

+    // Find intersection with water surface

+    vec3 eyeVecNorm = normalize(eyeVec);

+    float t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+    vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t;

+

+    vec2 texC = vec2(0.0);

+    int samples = 1;

+    #ifdef ENABLE_HQ_SHORELINE

+        samples = 10;

+    #endif

+

+    float biasFactor = 1.0 / samples;

+    for (int i = 0; i < samples; i++){

+        texC = (surfacePoint.xz + eyeVecNorm.xz * biasFactor) * scale + m_Time * 0.03 * m_WindDirection;

+

+        float bias = texture(m_HeightMap, texC).r;

+

+        bias *= biasFactor;

+        level += bias * m_MaxAmplitude;

+        t = (level - m_CameraPosition.y) / eyeVecNorm.y;

+        surfacePoint = m_CameraPosition + eyeVecNorm * t;

+    }

+

+    float depth = length(position - surfacePoint);

+    float depth2 = surfacePoint.y - position.y;

+

+    // XXX: HACK ALERT: Increase water depth to infinity if at far plane

+    // Prevents "foam on horizon" issue

+    // For best results, replace the "100.0" below with the

+    // highest value in the m_ColorExtinction vec3

+    depth  += isAtFarPlane * 100.0;

+    depth2 += isAtFarPlane * 100.0;

+

+    eyeVecNorm = normalize(m_CameraPosition - surfacePoint);

+

+    // Find normal of water surface

+    float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1.0,  0.0)).r;

+    float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1.0,  0.0)).r;

+    float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0.0, -1.0)).r;

+    float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0.0,  1.0)).r;

+

+    vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude));

+    vec3 normal = vec3(0.0);

+

+    #ifdef ENABLE_RIPPLES

+        texC = surfacePoint.xz * 0.8 + m_WindDirection * m_Time* 1.6;

+        mat3 tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal0a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.4 + m_WindDirection * m_Time* 0.8;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal1a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.2 + m_WindDirection * m_Time * 0.4;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal2a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0));

+

+        texC = surfacePoint.xz * 0.1 + m_WindDirection * m_Time * 0.2;

+        tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC);

+        vec3 normal3a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0));

+

+        normal = normalize(normal0a * normalModifier.x + normal1a * normalModifier.y +normal2a * normalModifier.z + normal3a * normalModifier.w);

+        // XXX: Here's another way to fix the terrain edge issue,

+        // But it requires GLSL 1.3 and still looks kinda incorrect

+        // around edges

+        normal = isnan(normal.x) ? myNormal : normal;

+        //if (position.y > level){

+        //    gl_FragColor = vec4(color2 + normal*0.0001, 1.0);

+        //    return;

+        //}

+    #else

+        normal = myNormal;

+    #endif

+

+    vec3 refraction = color2;

+    #ifdef ENABLE_REFRACTION

+       // texC = texCoord.xy+ m_ReflectionDisplace * normal.x;

+        texC = texCoord.xy;

+        texC += sin(m_Time*1.8  + 3.0 * abs(position.y)) * (refractionScale * min(depth2, 1.0));

+        #ifdef RESOLVE_MS

+            ivec2 iTexC = ivec2(texC * textureSize(m_Texture));

+            refraction = texelFetch(m_Texture, iTexC, sampleNum).rgb;

+        #else

+            ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0));

+            refraction = texelFetch(m_Texture, iTexC, 0).rgb;

+        #endif

+    #endif

+

+    vec3 waterPosition = surfacePoint.xyz;

+    waterPosition.y -= (level - m_WaterHeight);

+    vec4 texCoordProj = m_TextureProjMatrix * vec4(waterPosition, 1.0);

+

+    texCoordProj.x = texCoordProj.x + m_ReflectionDisplace * normal.x;

+    texCoordProj.z = texCoordProj.z + m_ReflectionDisplace * normal.z;

+    texCoordProj /= texCoordProj.w;

+    texCoordProj.y = 1.0 - texCoordProj.y;

+

+    vec3 reflection = texture(m_ReflectionMap, texCoordProj.xy).rgb;

+

+    float fresnel = fresnelTerm(normal, eyeVecNorm);

+

+    float depthN = depth * m_WaterTransparency;

+    float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale);

+    refraction = mix(mix(refraction, m_WaterColor.rgb * waterCol, saturate(depthN / visibility)),

+        m_DeepWaterColor.rgb * waterCol, saturate(depth2 / m_ColorExtinction));

+

+

+

+

+    vec3 foam = vec3(0.0);

+    #ifdef ENABLE_FOAM

+        texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005;

+        vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005;

+

+        if(depth2 < m_FoamExistence.x){

+            foam = (texture2D(m_FoamMap, texC).r + texture2D(m_FoamMap, texCoord2)).rgb * vec3(m_FoamIntensity);

+        }else if(depth2 < m_FoamExistence.y){

+            foam = mix((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity , vec4(0.0),

+                (depth2 - m_FoamExistence.x) / (m_FoamExistence.y - m_FoamExistence.x)).rgb;

+        }

+

+        

+        if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){

+            foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity  * m_FoamIntensity * 0.3 *

+               saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb;

+        }

+        foam *= m_LightColor.rgb;

+    #endif

+

+    vec3 specular = vec3(0.0);

+    #ifdef ENABLE_SPECULAR

+        vec3 lightDir=normalize(m_LightDir);

+        vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm);

+        float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5);

+        specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2)));

+        specular += specular * 25.0 * saturate(m_Shininess - 0.05);

+        //foam does not shine

+        specular=specular * m_LightColor.rgb - (5.0 * foam);

+    #endif

+

+    color = mix(refraction, reflection, fresnel);

+    color = mix(refraction, color, saturate(depth * m_ShoreHardness));

+    color = saturate(color + max(specular, foam ));

+    color = mix(refraction, color, saturate(depth* m_FoamHardness));

+

+

+    // XXX: HACK ALERT:

+    // We trick the GeForces to think they have

+    // to calculate the derivatives for all these pixels by using step()!

+    // That way we won't get pixels around the edges of the terrain,

+    // Where the derivatives are undefined

+    return vec4(mix(color, color2, step(level, position.y)), 1.0);

+}

+

+void main(){

+    #ifdef RESOLVE_MS

+        vec4 color = vec4(0.0);

+        for (int i = 0; i < m_NumSamples; i++){

+            color += main_multiSample(i);

+        }

+        outFragColor = color / m_NumSamples;

+    #else

+        outFragColor = main_multiSample(0);

+    #endif

+}
\ No newline at end of file
diff --git a/engine/src/core-effects/Common/MatDefs/Water/simple_water.frag b/engine/src/core-effects/Common/MatDefs/Water/simple_water.frag
new file mode 100644
index 0000000..65a9c99
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/simple_water.frag
@@ -0,0 +1,126 @@
+/*
+GLSL conversion of Michael Horsch water demo
+http://www.bonzaisoftware.com/wfs.html
+Converted by Mars_999
+8/20/2005
+*/
+
+uniform sampler2D m_water_normalmap;
+uniform sampler2D m_water_reflection;
+uniform sampler2D m_water_refraction;
+uniform sampler2D m_water_dudvmap;
+uniform sampler2D m_water_depthmap;
+uniform vec4 m_waterColor;
+uniform float m_waterDepth;
+uniform vec4 m_distortionScale;
+uniform vec4 m_distortionMix;
+uniform vec4 m_texScale;
+uniform vec2 m_FrustumNearFar;
+uniform float m_waterTransparency;
+
+
+
+varying vec4 lightDir; //lightpos
+varying vec4 waterTex1; //moving texcoords
+varying vec4 waterTex2; //moving texcoords
+varying vec4 position; //for projection
+varying vec4 viewDir; //viewts
+varying vec4 viewLightDir;
+varying vec4 viewCamDir;
+
+//unit 0 = m_water_reflection
+//unit 1 = m_water_refraction
+//unit 2 = m_water_normalmap
+//unit 3 = m_water_dudvmap
+//unit 4 = m_water_depthmap
+
+ const vec4 two = vec4(2.0, 2.0, 2.0, 1.0);
+ const vec4 mone = vec4(-1.0, -1.0, -1.0, 1.0);
+
+ const vec4 ofive = vec4(0.5,0.5,0.5,1.0);
+
+ const float exponent = 64.0;
+
+float tangDot(in vec3 v1, in vec3 v2){
+    float d = dot(v1,v2);
+    #ifdef V_TANGENT
+        d = 1.0 - d*d;
+        return step(0.0, d) * sqrt(d);
+    #else
+        return d;
+    #endif
+}
+
+vec4 readDepth(vec2 uv){
+    float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - texture2D(m_water_depthmap, uv).r* (m_FrustumNearFar.y-m_FrustumNearFar.x));
+    return vec4( depth);
+}
+
+void main(void)
+{
+ 
+
+     vec4 lightTS = normalize(lightDir);
+     vec4 viewt = normalize(viewDir);
+     vec4 disdis = texture2D(m_water_dudvmap, vec2(waterTex2 * m_texScale));
+     vec4 fdist = texture2D(m_water_dudvmap, vec2(waterTex1 + disdis*m_distortionMix));
+     fdist =normalize( fdist * 2.0 - 1.0)* m_distortionScale;
+  
+
+     //load normalmap
+     vec4 nmap = texture2D(m_water_normalmap, vec2(waterTex1 + disdis*m_distortionMix));
+     nmap = (nmap-ofive) * two;
+    // nmap = nmap*2.0-1.0;
+     vec4 vNorm = normalize(nmap);
+
+     
+     vec4 projCoord = position / position.w;
+     projCoord =(projCoord+1.0)*0.5 + fdist;
+     projCoord = clamp(projCoord, 0.001, 0.999);
+
+     //load reflection,refraction and depth texture
+     vec4 refl = texture2D(m_water_reflection, vec2(projCoord.x,1.0-projCoord.y));
+     vec4 refr = texture2D(m_water_refraction, vec2(projCoord));
+     vec4 wdepth =readDepth(vec2(projCoord));
+  
+     wdepth = vec4(pow(wdepth.x, m_waterDepth));
+     vec4 invdepth = 1.0 - wdepth;
+
+
+ // Blinn - Phong
+  //    vec4 H = (viewt - lightTS);
+  //   vec4 specular =vec4(pow(max(dot(H, vNorm), 0.0), exponent));
+
+// Standard Phong
+
+  //   vec4 R =reflect(-L, vNorm);
+ //    vec4 specular =vec4( pow(max(dot(R, E), 0.0),exponent));
+
+ 
+     //calculate specular highlight
+     vec4 L=normalize(viewLightDir);  
+    vec4 E=normalize(viewCamDir);
+     vec4 vRef = normalize(reflect(-L,vNorm));
+     float stemp =max(0.0, dot( vRef,E) );
+     //initializing to 0 to avoid artifacts on old intel cards
+     vec4 specular = vec4(0.0,0.0,0.0,0.0);
+    if(stemp>0.0){
+         stemp = pow(stemp, exponent);
+         specular = vec4(stemp);
+    }
+
+
+
+    vec4 fresnelTerm = vec4(0.02+0.97*pow((1.0-dot(normalize(viewt), vNorm)),5.0));
+
+
+
+    fresnelTerm=fresnelTerm*invdepth*m_waterTransparency;
+    fresnelTerm=clamp(fresnelTerm,0.0,1.0);
+
+    refr*=(fresnelTerm);
+    refr *= invdepth;
+    refr= refr+ m_waterColor*wdepth*fresnelTerm;
+
+    gl_FragColor =(refr+ refl*(1.0-fresnelTerm))+specular;
+}
diff --git a/engine/src/core-effects/Common/MatDefs/Water/simple_water.vert b/engine/src/core-effects/Common/MatDefs/Water/simple_water.vert
new file mode 100644
index 0000000..e6052d8
--- /dev/null
+++ b/engine/src/core-effects/Common/MatDefs/Water/simple_water.vert
@@ -0,0 +1,87 @@
+/*
+GLSL conversion of Michael Horsch water demo
+http://www.bonzaisoftware.com/wfs.html
+Converted by Mars_999
+8/20/2005
+*/
+uniform vec3 m_lightPos;
+uniform float m_time;
+
+uniform mat4 g_WorldViewProjectionMatrix;
+uniform mat4 g_WorldViewMatrix;
+uniform mat4 g_ViewMatrix;
+uniform vec3 g_CameraPosition;
+uniform mat3 g_NormalMatrix;
+
+attribute vec4 inPosition;
+attribute vec2 inTexCoord;
+attribute vec3 inTangent;
+attribute vec3 inNormal;
+
+varying vec4 lightDir;
+varying vec4 waterTex1;
+varying vec4 waterTex2;
+varying vec4 position;
+varying vec4 viewDir;
+varying vec4 viewpos;
+varying vec4 viewLightDir;
+varying vec4 viewCamDir;
+
+
+//unit 0 = water_reflection
+//unit 1 = water_refraction
+//unit 2 = water_normalmap
+//unit 3 = water_dudvmap
+//unit 4 = water_depthmap
+
+void main(void)
+{
+    viewpos.x = g_CameraPosition.x;
+    viewpos.y = g_CameraPosition.y;
+    viewpos.z = g_CameraPosition.z;
+    viewpos.w = 1.0;
+
+    vec4  temp;
+    vec4 tangent = vec4(1.0, 0.0, 0.0, 0.0);
+    vec4 norm = vec4(0.0, 1.0, 0.0, 0.0);
+    vec4 binormal = vec4(0.0, 0.0, 1.0, 0.0);
+
+
+    temp = viewpos - inPosition;
+
+    viewDir.x = dot(temp, tangent);
+    viewDir.y = dot(temp, binormal);
+    viewDir.z = dot(temp, norm);
+    viewDir.w = 0.0;
+
+    temp = vec4(m_lightPos,1.0)- inPosition;
+    lightDir.x = dot(temp, tangent);
+    lightDir.y = dot(temp, binormal);
+    lightDir.z = dot(temp, norm);
+    lightDir.w = 0.0;
+
+   vec4 viewSpaceLightPos=g_ViewMatrix*vec4(m_lightPos,1.0);
+   vec4 viewSpacePos=g_WorldViewMatrix*inPosition;
+   vec3 wvNormal  = normalize(g_NormalMatrix * inNormal);
+   vec3 wvTangent = normalize(g_NormalMatrix * inTangent);
+   vec3 wvBinormal = cross(wvNormal, wvTangent);
+   mat3 tbnMat = mat3(wvTangent, wvBinormal, wvNormal);
+
+    temp = viewSpaceLightPos - viewSpacePos;
+    viewLightDir.xyz=temp.xyz*tbnMat;
+    viewLightDir.w = 0.0;
+
+    temp = -viewSpacePos;
+    viewCamDir.xyz =temp.xyz*tbnMat;
+    viewCamDir.w = 0.0;
+
+
+    vec4 t1 = vec4(0.0, -m_time, 0.0,0.0);
+    vec4 t2 = vec4(0.0, m_time, 0.0,0.0);
+
+    waterTex1 =vec4(inTexCoord,0.0,0.0) + t1;
+    waterTex2 =vec4(inTexCoord ,0.0,0.0)+ t2;
+
+    position = g_WorldViewProjectionMatrix * inPosition;
+    gl_Position = position;
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/BloomFilter.java b/engine/src/core-effects/com/jme3/post/filters/BloomFilter.java
new file mode 100644
index 0000000..770e7f4
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/BloomFilter.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.texture.Image.Format;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * BloomFilter is used to make objects in the scene have a glow effect.<br>
+ * There are 2 mode : Scene and Objects.<br>
+ * Scene mode extracts the bright parts of the scene to make them glow<br>
+ * Object mode make objects glow according to their material's glowMap or their GlowColor<br>
+ * @see <a href="http://jmonkeyengine.org/wiki/doku.php/jme3:advanced:bloom_and_glow">advanced:bloom_and_glow</a> for more details
+ * 
+ * @author Rémy Bouquet aka Nehon
+ */
+public class BloomFilter extends Filter {
+
+    /**
+     * GlowMode specifies if the glow will be applied to the whole scene,or to objects that have aglow color or a glow map
+     */
+    public enum GlowMode {
+
+        /**
+         * Apply bloom filter to bright areas in the scene.
+         */
+        Scene,
+        /**
+         * Apply bloom only to objects that have a glow map or a glow color.
+         */
+        Objects,
+        /**
+         * Apply bloom to both bright parts of the scene and objects with glow map.
+         */
+        SceneAndObjects;
+    }
+
+    private GlowMode glowMode = GlowMode.Scene;
+    //Bloom parameters
+    private float blurScale = 1.5f;
+    private float exposurePower = 5.0f;
+    private float exposureCutOff = 0.0f;
+    private float bloomIntensity = 2.0f;
+    private float downSamplingFactor = 1;
+    private Pass preGlowPass;
+    private Pass extractPass;
+    private Pass horizontalBlur = new Pass();
+    private Pass verticalalBlur = new Pass();
+    private Material extractMat;
+    private Material vBlurMat;
+    private Material hBlurMat;
+    private int screenWidth;
+    private int screenHeight;    
+
+    /**
+     * Creates a Bloom filter
+     */
+    public BloomFilter() {
+        super("BloomFilter");
+    }
+
+    /**
+     * Creates the bloom filter with the specific glow mode
+     * @param glowMode
+     */
+    public BloomFilter(GlowMode glowMode) {
+        this();
+        this.glowMode = glowMode;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        screenWidth = (int) Math.max(1, (w / downSamplingFactor));
+        screenHeight = (int) Math.max(1, (h / downSamplingFactor));
+        //    System.out.println(screenWidth + " " + screenHeight);
+        if (glowMode != GlowMode.Scene) {
+            preGlowPass = new Pass();
+            preGlowPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth);
+        }
+
+        postRenderPasses = new ArrayList<Pass>();
+        //configuring extractPass
+        extractMat = new Material(manager, "Common/MatDefs/Post/BloomExtract.j3md");
+        extractPass = new Pass() {
+
+            @Override
+            public boolean requiresSceneAsTexture() {
+                return true;
+            }
+
+            @Override
+            public void beforeRender() {
+                extractMat.setFloat("ExposurePow", exposurePower);
+                extractMat.setFloat("ExposureCutoff", exposureCutOff);
+                if (glowMode != GlowMode.Scene) {
+                    extractMat.setTexture("GlowMap", preGlowPass.getRenderedTexture());
+                }
+                extractMat.setBoolean("Extract", glowMode != GlowMode.Objects);
+            }
+        };
+
+        extractPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, extractMat);
+        postRenderPasses.add(extractPass);
+
+        //configuring horizontal blur pass
+        hBlurMat = new Material(manager, "Common/MatDefs/Blur/HGaussianBlur.j3md");
+        horizontalBlur = new Pass() {
+
+            @Override
+            public void beforeRender() {
+                hBlurMat.setTexture("Texture", extractPass.getRenderedTexture());
+                hBlurMat.setFloat("Size", screenWidth);
+                hBlurMat.setFloat("Scale", blurScale);
+            }
+        };
+
+        horizontalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, hBlurMat);
+        postRenderPasses.add(horizontalBlur);
+
+        //configuring vertical blur pass
+        vBlurMat = new Material(manager, "Common/MatDefs/Blur/VGaussianBlur.j3md");
+        verticalalBlur = new Pass() {
+
+            @Override
+            public void beforeRender() {
+                vBlurMat.setTexture("Texture", horizontalBlur.getRenderedTexture());
+                vBlurMat.setFloat("Size", screenHeight);
+                vBlurMat.setFloat("Scale", blurScale);
+            }
+        };
+
+        verticalalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, vBlurMat);
+        postRenderPasses.add(verticalalBlur);
+
+
+        //final material
+        material = new Material(manager, "Common/MatDefs/Post/BloomFinal.j3md");
+        material.setTexture("BloomTex", verticalalBlur.getRenderedTexture());
+    }
+
+
+    @Override
+    protected Material getMaterial() {
+        material.setFloat("BloomIntensity", bloomIntensity);
+        return material;
+    }
+
+    @Override
+    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+        if (glowMode != GlowMode.Scene) {           
+            renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);            
+            renderManager.getRenderer().setFrameBuffer(preGlowPass.getRenderFrameBuffer());
+            renderManager.getRenderer().clearBuffers(true, true, true);
+            renderManager.setForcedTechnique("Glow");
+            renderManager.renderViewPortQueues(viewPort, false);         
+            renderManager.setForcedTechnique(null);
+            renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+        }
+    }
+
+    /**
+     * returns the bloom intensity
+     * @return 
+     */
+    public float getBloomIntensity() {
+        return bloomIntensity;
+    }
+
+    /**
+     * intensity of the bloom effect default is 2.0
+     * @param bloomIntensity
+     */
+    public void setBloomIntensity(float bloomIntensity) {
+        this.bloomIntensity = bloomIntensity;
+    }
+
+    /**
+     * returns the blur scale
+     * @return 
+     */
+    public float getBlurScale() {
+        return blurScale;
+    }
+
+    /**
+     * sets The spread of the bloom default is 1.5f
+     * @param blurScale
+     */
+    public void setBlurScale(float blurScale) {
+        this.blurScale = blurScale;
+    }
+
+    /**
+     * returns the exposure cutoff<br>
+     * for more details see {@link setExposureCutOff(float exposureCutOff)}
+     * @return 
+     */    
+    public float getExposureCutOff() {
+        return exposureCutOff;
+    }
+
+    /**
+     * Define the color threshold on which the bloom will be applied (0.0 to 1.0)
+     * @param exposureCutOff
+     */
+    public void setExposureCutOff(float exposureCutOff) {
+        this.exposureCutOff = exposureCutOff;
+    }
+
+    /**
+     * returns the exposure power<br>
+     * form more details see {@link setExposurePower(float exposurePower)}
+     * @return 
+     */
+    public float getExposurePower() {
+        return exposurePower;
+    }
+
+    /**
+     * defines how many time the bloom extracted color will be multiplied by itself. default id 5.0<br>
+     * a high value will reduce rough edges in the bloom and somhow the range of the bloom area     * 
+     * @param exposurePower
+     */
+    public void setExposurePower(float exposurePower) {
+        this.exposurePower = exposurePower;
+    }
+
+    /**
+     * returns the downSampling factor<br>
+     * form more details see {@link setDownSamplingFactor(float downSamplingFactor)}
+     * @return
+     */
+    public float getDownSamplingFactor() {
+        return downSamplingFactor;
+    }
+
+    /**
+     * Sets the downSampling factor : the size of the computed texture will be divided by this factor. default is 1 for no downsampling
+     * A 2 value is a good way of widening the blur
+     * @param downSamplingFactor
+     */
+    public void setDownSamplingFactor(float downSamplingFactor) {
+        this.downSamplingFactor = downSamplingFactor;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(glowMode, "glowMode", GlowMode.Scene);
+        oc.write(blurScale, "blurScale", 1.5f);
+        oc.write(exposurePower, "exposurePower", 5.0f);
+        oc.write(exposureCutOff, "exposureCutOff", 0.0f);
+        oc.write(bloomIntensity, "bloomIntensity", 2.0f);
+        oc.write(downSamplingFactor, "downSamplingFactor", 1);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        glowMode = ic.readEnum("glowMode", GlowMode.class, GlowMode.Scene);
+        blurScale = ic.readFloat("blurScale", 1.5f);
+        exposurePower = ic.readFloat("exposurePower", 5.0f);
+        exposureCutOff = ic.readFloat("exposureCutOff", 0.0f);
+        bloomIntensity = ic.readFloat("bloomIntensity", 2.0f);
+        downSamplingFactor = ic.readFloat("downSamplingFactor", 1);
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/CartoonEdgeFilter.java b/engine/src/core-effects/com/jme3/post/filters/CartoonEdgeFilter.java
new file mode 100644
index 0000000..3e9998b
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/CartoonEdgeFilter.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.texture.Image.Format;
+
+/**
+ * Applies a cartoon-style edge detection filter to all objects in the scene.
+ *
+ * @author Kirill Vainer
+ */
+public class CartoonEdgeFilter extends Filter {
+
+    private Pass normalPass;
+    private float edgeWidth = 1.0f;
+    private float edgeIntensity = 1.0f;
+    private float normalThreshold = 0.5f;
+    private float depthThreshold = 0.1f;
+    private float normalSensitivity = 1.0f;
+    private float depthSensitivity = 10.0f;
+    private ColorRGBA edgeColor = new ColorRGBA(0, 0, 0, 1);
+
+    /**
+     * Creates a CartoonEdgeFilter
+     */
+    public CartoonEdgeFilter() {
+        super("CartoonEdgeFilter");
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+        Renderer r = renderManager.getRenderer();
+        r.setFrameBuffer(normalPass.getRenderFrameBuffer());
+        renderManager.getRenderer().clearBuffers(true, true, true);
+        renderManager.setForcedTechnique("PreNormalPass");
+        renderManager.renderViewPortQueues(viewPort, false);
+        renderManager.setForcedTechnique(null);
+        renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+    }
+
+    @Override
+    protected Material getMaterial() {
+        material.setTexture("NormalsTexture", normalPass.getRenderedTexture());
+        return material;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        normalPass = new Pass();
+        normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
+        material = new Material(manager, "Common/MatDefs/Post/CartoonEdge.j3md");
+        material.setFloat("EdgeWidth", edgeWidth);
+        material.setFloat("EdgeIntensity", edgeIntensity);
+        material.setFloat("NormalThreshold", normalThreshold);
+        material.setFloat("DepthThreshold", depthThreshold);
+        material.setFloat("NormalSensitivity", normalSensitivity);
+        material.setFloat("DepthSensitivity", depthSensitivity);
+        material.setColor("EdgeColor", edgeColor);
+    }
+
+    /**
+     * Return the depth sensitivity<br>
+     * for more details see {@link setDepthSensitivity(float depthSensitivity)}
+     * @return 
+     */
+    public float getDepthSensitivity() {
+        return depthSensitivity;
+    }
+
+    /**
+     * sets the depth sensitivity<br>
+     * defines how much depth will influence edges, default is 10
+     * @param depthSensitivity 
+     */
+    public void setDepthSensitivity(float depthSensitivity) {
+        this.depthSensitivity = depthSensitivity;
+        if (material != null) {
+            material.setFloat("DepthSensitivity", depthSensitivity);
+        }
+    }
+
+    /**
+     * returns the depth threshold<br>
+     * for more details see {@link setDepthThreshold(float depthThreshold)}
+     * @return 
+     */
+    public float getDepthThreshold() {
+        return depthThreshold;
+    }
+
+    /**
+     * sets the depth threshold<br>
+     * Defines at what threshold of difference of depth an edge is outlined default is 0.1f
+     * @param depthThreshold 
+     */
+    public void setDepthThreshold(float depthThreshold) {
+        this.depthThreshold = depthThreshold;
+        if (material != null) {
+            material.setFloat("DepthThreshold", depthThreshold);
+        }
+    }
+
+    /**
+     * returns the edge intensity<br>
+     * for more details see {@link setEdgeIntensity(float edgeIntensity) }
+     * @return 
+     */
+    public float getEdgeIntensity() {
+        return edgeIntensity;
+    }
+
+    /**
+     * sets the edge intensity<br>
+     * Defineshow visilble will be the outlined edges
+     * @param edgeIntensity 
+     */
+    public void setEdgeIntensity(float edgeIntensity) {
+        this.edgeIntensity = edgeIntensity;
+        if (material != null) {
+            material.setFloat("EdgeIntensity", edgeIntensity);
+        }
+    }
+
+    /**
+     * returns the width of the edges
+     * @return 
+     */
+    public float getEdgeWidth() {
+        return edgeWidth;
+    }
+
+    /**
+     * sets the witdh of the edge in pixels default is 1
+     * @param edgeWidth 
+     */
+    public void setEdgeWidth(float edgeWidth) {
+        this.edgeWidth = edgeWidth;
+        if (material != null) {
+            material.setFloat("EdgeWidth", edgeWidth);
+        }
+
+    }
+
+    /**
+     * returns the normals sensitivity<br>
+     * form more details see {@link setNormalSensitivity(float normalSensitivity)}
+     * @return 
+     */
+    public float getNormalSensitivity() {
+        return normalSensitivity;
+    }
+
+    /**
+     * sets the normals sensitivity default is 1
+     * @param normalSensitivity 
+     */
+    public void setNormalSensitivity(float normalSensitivity) {
+        this.normalSensitivity = normalSensitivity;
+        if (material != null) {
+            material.setFloat("NormalSensitivity", normalSensitivity);
+        }
+    }
+
+    /**
+     * returns the normal threshold<br>
+     * for more details see {@link setNormalThreshold(float normalThreshold)}
+     * 
+     * @return 
+     */
+    public float getNormalThreshold() {
+        return normalThreshold;
+    }
+
+    /**
+     * sets the normal threshold default is 0.5
+     * @param normalThreshold 
+     */
+    public void setNormalThreshold(float normalThreshold) {
+        this.normalThreshold = normalThreshold;
+        if (material != null) {
+            material.setFloat("NormalThreshold", normalThreshold);
+        }
+    }
+
+    /**
+     * returns the edge color
+     * @return
+     */
+    public ColorRGBA getEdgeColor() {
+        return edgeColor;
+    }
+
+    /**
+     * Sets the edge color, default is black
+     * @param edgeColor
+     */
+    public void setEdgeColor(ColorRGBA edgeColor) {
+        this.edgeColor = edgeColor;
+        if (material != null) {
+            material.setColor("EdgeColor", edgeColor);
+        }
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/ColorOverlayFilter.java b/engine/src/core-effects/com/jme3/post/filters/ColorOverlayFilter.java
new file mode 100644
index 0000000..a7f30f8
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/ColorOverlayFilter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/** 
+ * This filter simply multiply the whole scene by a color
+ * @author Rémy Bouquet aka Nehon
+ */
+public class ColorOverlayFilter extends Filter {
+
+    private ColorRGBA color = ColorRGBA.White;
+
+    /**
+     * creates a colorOverlayFilter with a white coor (transparent)
+     */
+    public ColorOverlayFilter() {
+        super("Color Overlay");
+    }
+
+    /**
+     * creates a colorOverlayFilter with the given color
+     * @param color 
+     */
+    public ColorOverlayFilter(ColorRGBA color) {
+        this();
+        this.color = color;
+    }
+
+    @Override
+    protected Material getMaterial() {
+
+        material.setColor("Color", color);
+        return material;
+    }
+
+    /**
+     * returns the color
+     * @return color
+     */
+    public ColorRGBA getColor() {
+        return color;
+    }
+
+    /**
+     * sets the color 
+     * @param color 
+     */
+    public void setColor(ColorRGBA color) {
+        this.color = color;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md");
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(color, "color", ColorRGBA.White);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        color = (ColorRGBA) ic.readSavable("color", ColorRGBA.White);
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/CrossHatchFilter.java b/engine/src/core-effects/com/jme3/post/filters/CrossHatchFilter.java
new file mode 100644
index 0000000..699a252
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/CrossHatchFilter.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * A Post Processing filter that makes the screen look like it was drawn as
+ * diagonal lines with a pen.
+ * Try combining this with a cartoon edge filter to obtain manga style visuals.
+ *
+ * Based on an article from Geeks3D:
+ *    <a href="http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/" rel="nofollow">http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/</a>
+ *
+ * @author Roy Straver a.k.a. Baal Garnaal
+ */
+public class CrossHatchFilter extends Filter {
+
+    private ColorRGBA lineColor = ColorRGBA.Black.clone();
+    private ColorRGBA paperColor = ColorRGBA.White.clone();
+    private float colorInfluenceLine = 0.8f;
+    private float colorInfluencePaper = 0.1f;
+    private float fillValue = 0.9f;
+    private float luminance1 = 0.9f;
+    private float luminance2 = 0.7f;
+    private float luminance3 = 0.5f;
+    private float luminance4 = 0.3f;
+    private float luminance5 = 0.0f;
+    private float lineThickness = 1.0f;
+    private float lineDistance = 4.0f;
+
+    /**
+     * Creates a crossHatch filter
+     */
+    public CrossHatchFilter() {
+        super("CrossHatchFilter");
+    }
+
+    /**
+     * Creates a crossHatch filter
+     * @param lineColor the colors of the lines
+     * @param paperColor the paper color
+     */
+    public CrossHatchFilter(ColorRGBA lineColor, ColorRGBA paperColor) {
+        this();
+        this.lineColor = lineColor;
+        this.paperColor = paperColor;
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return false;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/CrossHatch.j3md");
+        material.setColor("LineColor", lineColor);
+        material.setColor("PaperColor", paperColor);
+
+        material.setFloat("ColorInfluenceLine", colorInfluenceLine);
+        material.setFloat("ColorInfluencePaper", colorInfluencePaper);
+
+        material.setFloat("FillValue", fillValue);
+
+        material.setFloat("Luminance1", luminance1);
+        material.setFloat("Luminance2", luminance2);
+        material.setFloat("Luminance3", luminance3);
+        material.setFloat("Luminance4", luminance4);
+        material.setFloat("Luminance5", luminance5);
+
+        material.setFloat("LineThickness", lineThickness);
+        material.setFloat("LineDistance", lineDistance);
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    /**
+     * Sets color used to draw lines
+     * @param lineColor 
+     */
+    public void setLineColor(ColorRGBA lineColor) {
+        this.lineColor = lineColor;
+        if (material != null) {
+            material.setColor("LineColor", lineColor);
+        }
+    }
+
+    /**
+     * Sets color used as background
+     * @param paperColor 
+     */
+    public void setPaperColor(ColorRGBA paperColor) {
+        this.paperColor = paperColor;
+        if (material != null) {
+            material.setColor("PaperColor", paperColor);
+        }
+    }
+
+    /**
+     * Sets color influence of original image on lines drawn
+     * @param colorInfluenceLine 
+     */
+    public void setColorInfluenceLine(float colorInfluenceLine) {
+        this.colorInfluenceLine = colorInfluenceLine;
+        if (material != null) {
+            material.setFloat("ColorInfluenceLine", colorInfluenceLine);
+        }
+    }
+
+    /**
+     * Sets color influence of original image on non-line areas
+     * @param colorInfluencePaper 
+     */
+    public void setColorInfluencePaper(float colorInfluencePaper) {
+        this.colorInfluencePaper = colorInfluencePaper;
+        if (material != null) {
+            material.setFloat("ColorInfluencePaper", colorInfluencePaper);
+        }
+    }
+
+    /**
+     * Sets line/paper color ratio for areas with values < luminance5,
+     * really dark areas get no lines but a filled blob instead
+     * @param fillValue 
+     */
+    public void setFillValue(float fillValue) {
+        this.fillValue = fillValue;
+        if (material != null) {
+            material.setFloat("FillValue", fillValue);
+        }
+    }
+
+    /**
+     *
+     * Sets minimum luminance levels for lines drawn
+     * @param luminance1 Top-left to down right 1
+     * @param luminance2 Top-right to bottom left 1
+     * @param luminance3 Top-left to down right 2
+     * @param luminance4 Top-right to bottom left 2
+     * @param luminance5 Blobs
+     */
+    public void setLuminanceLevels(float luminance1, float luminance2, float luminance3, float luminance4, float luminance5) {
+        this.luminance1 = luminance1;
+        this.luminance2 = luminance2;
+        this.luminance3 = luminance3;
+        this.luminance4 = luminance4;
+        this.luminance5 = luminance5;
+
+        if (material != null) {
+            material.setFloat("Luminance1", luminance1);
+            material.setFloat("Luminance2", luminance2);
+            material.setFloat("Luminance3", luminance3);
+            material.setFloat("Luminance4", luminance4);
+            material.setFloat("Luminance5", luminance5);
+        }
+    }
+
+    /**
+     * Sets the thickness of lines drawn
+     * @param lineThickness 
+     */
+    public void setLineThickness(float lineThickness) {
+        this.lineThickness = lineThickness;
+        if (material != null) {
+            material.setFloat("LineThickness", lineThickness);
+        }
+    }
+
+    /**
+     * Sets minimum distance between lines drawn
+     * Primary lines are drawn at 2*lineDistance
+     * Secondary lines are drawn at lineDistance
+     * @param lineDistance 
+     */
+    public void setLineDistance(float lineDistance) {
+        this.lineDistance = lineDistance;
+        if (material != null) {
+            material.setFloat("LineDistance", lineDistance);
+        }
+    }
+
+    /**
+     * Returns line color
+     * @return 
+     */
+    public ColorRGBA getLineColor() {
+        return lineColor;
+    }
+
+    /**
+     * Returns paper background color
+     * @return 
+     */
+    public ColorRGBA getPaperColor() {
+        return paperColor;
+    }
+
+    /**
+     * Returns current influence of image colors on lines
+     */
+    public float getColorInfluenceLine() {
+        return colorInfluenceLine;
+    }
+
+    /**
+     * Returns current influence of image colors on paper background
+     */
+    public float getColorInfluencePaper() {
+        return colorInfluencePaper;
+    }
+
+    /**
+     * Returns line/paper color ratio for blobs
+     */
+    public float getFillValue() {
+        return fillValue;
+    }
+
+    /**
+     * Returns the thickness of the lines drawn
+     */
+    public float getLineThickness() {
+        return lineThickness;
+    }
+
+    /**
+     * Returns minimum distance between lines
+     */
+    public float getLineDistance() {
+        return lineDistance;
+    }
+
+    /**
+     * Returns treshold for lines 1
+     */
+    public float getLuminance1() {
+        return luminance1;
+    }
+
+    /**
+     * Returns treshold for lines 2
+     */
+    public float getLuminance2() {
+        return luminance2;
+    }
+
+    /**
+     * Returns treshold for lines 3
+     */
+    public float getLuminance3() {
+        return luminance3;
+    }
+
+    /**
+     * Returns treshold for lines 4
+     */
+    public float getLuminance4() {
+        return luminance4;
+    }
+
+    /**
+     * Returns treshold for blobs
+     */
+    public float getLuminance5() {
+        return luminance5;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/com/jme3/post/filters/DepthOfFieldFilter.java b/engine/src/core-effects/com/jme3/post/filters/DepthOfFieldFilter.java
new file mode 100644
index 0000000..55591c9
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/DepthOfFieldFilter.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ *  A post-processing filter that performs a depth range
+ *  blur using a scaled convolution filter.
+ *
+ *  @version   $Revision: 779 $
+ *  @author    Paul Speed
+ */
+public class DepthOfFieldFilter extends Filter {
+
+    private float focusDistance = 50f;
+    private float focusRange = 10f;
+    private float blurScale = 1f;
+    // These values are set internally based on the
+    // viewport size.
+    private float xScale;
+    private float yScale;
+
+    /**
+     * Creates a DepthOfField filter
+     */
+    public DepthOfFieldFilter() {
+        super("Depth Of Field");
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected Material getMaterial() {
+
+        return material;
+    }
+
+    @Override
+    protected void initFilter(AssetManager assets, RenderManager renderManager,
+            ViewPort vp, int w, int h) {
+        material = new Material(assets, "Common/MatDefs/Post/DepthOfField.j3md");
+        material.setFloat("FocusDistance", focusDistance);
+        material.setFloat("FocusRange", focusRange);
+
+
+        xScale = 1.0f / w;
+        yScale = 1.0f / h;
+
+        material.setFloat("XScale", blurScale * xScale);
+        material.setFloat("YScale", blurScale * yScale);
+    }
+
+    /**
+     *  Sets the distance at which objects are purely in focus.
+     */
+    public void setFocusDistance(float f) {
+
+        this.focusDistance = f;
+        if (material != null) {
+            material.setFloat("FocusDistance", focusDistance);
+        }
+
+    }
+
+    /**
+     * returns the focus distance
+     * @return 
+     */
+    public float getFocusDistance() {
+        return focusDistance;
+    }
+
+    /**
+     *  Sets the range to either side of focusDistance where the
+     *  objects go gradually out of focus.  Less than focusDistance - focusRange
+     *  and greater than focusDistance + focusRange, objects are maximally "blurred".
+     */
+    public void setFocusRange(float f) {
+        this.focusRange = f;
+        if (material != null) {
+            material.setFloat("FocusRange", focusRange);
+        }
+
+    }
+
+    /**
+     * returns the focus range
+     * @return 
+     */
+    public float getFocusRange() {
+        return focusRange;
+    }
+
+    /**
+     *  Sets the blur amount by scaling the convolution filter up or
+     *  down.  A value of 1 (the default) performs a sparse 5x5 evenly
+     *  distribubted convolution at pixel level accuracy.  Higher values skip
+     *  more pixels, and so on until you are no longer blurring the image
+     *  but simply hashing it.
+     *
+     *  The sparse convolution is as follows:
+     *%MINIFYHTMLc3d0cd9fab65de6875a381fd3f83e1b338%*
+     *  Where 'x' is the texel being modified.  Setting blur scale higher
+     *  than 1 spaces the samples out.
+     */
+    public void setBlurScale(float f) {
+        this.blurScale = f;
+        if (material != null) {
+            material.setFloat("XScale", blurScale * xScale);
+            material.setFloat("YScale", blurScale * yScale);
+        }
+    }
+
+    /**
+     * returns the blur scale
+     * @return 
+     */
+    public float getBlurScale() {
+        return blurScale;
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/FXAAFilter.java b/engine/src/core-effects/com/jme3/post/filters/FXAAFilter.java
new file mode 100644
index 0000000..8ba3c16
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/FXAAFilter.java
@@ -0,0 +1,95 @@
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * <a href="http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/" rel="nofollow">http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-<span class="domtooltips" title="OpenGL (Open Graphics Library) is a standard specification defining a cross-language, cross-platform API for writing applications that produce 2D and 3D computer graphics." id="domtooltipsspan11">opengl</span>-test-radeon-geforce/3/</a>
+ * <a href="http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf" rel="nofollow">http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf</a>
+ *
+ * @author Phate666 (adapted to jme3)
+ *
+ */
+public class FXAAFilter extends Filter {
+
+    private float subPixelShift = 1.0f / 4.0f;
+    private float vxOffset = 0.0f;
+    private float spanMax = 8.0f;
+    private float reduceMul = 1.0f / 8.0f;
+
+    public FXAAFilter() {
+        super("FXAAFilter");
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager,
+            RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/FXAA.j3md");   
+        material.setFloat("SubPixelShift", subPixelShift);
+        material.setFloat("VxOffset", vxOffset);
+        material.setFloat("SpanMax", spanMax);
+        material.setFloat("ReduceMul", reduceMul);
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    public void setSpanMax(float spanMax) {
+        this.spanMax = spanMax;
+        if (material != null) {
+            material.setFloat("SpanMax", this.spanMax);
+        }
+    }
+
+    /**
+     * set to 0.0f for higher quality
+     *
+     * @param subPixelShift
+     */
+    public void setSubPixelShift(float subPixelShift) {
+        this.subPixelShift = subPixelShift;
+        if (material != null) {
+            material.setFloat("SubPixelShif", this.subPixelShift);
+        }
+    }
+
+    /**
+     * set to 0.0f for higher quality
+     *
+     * @param reduceMul
+     */
+    public void setReduceMul(float reduceMul) {
+        this.reduceMul = reduceMul;
+        if (material != null) {
+            material.setFloat("ReduceMul", this.reduceMul);
+        }
+    }
+
+    public void setVxOffset(float vxOffset) {
+        this.vxOffset = vxOffset;
+        if (material != null) {
+            material.setFloat("VxOffset", this.vxOffset);
+        }
+    }
+
+    public float getReduceMul() {
+        return reduceMul;
+    }
+
+    public float getSpanMax() {
+        return spanMax;
+    }
+
+    public float getSubPixelShift() {
+        return subPixelShift;
+    }
+
+    public float getVxOffset() {
+        return vxOffset;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/com/jme3/post/filters/FadeFilter.java b/engine/src/core-effects/com/jme3/post/filters/FadeFilter.java
new file mode 100644
index 0000000..1fb2340
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/FadeFilter.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ *
+ * Fade Filter allows you to make an animated fade effect on a scene.
+ * @author Rémy Bouquet aka Nehon
+ * implemented from boxjar implementation
+ * @see <a href="http://jmonkeyengine.org/groups/graphics/forum/topic/newbie-question-general-fade-inout-effect/#post-105559">http://jmonkeyengine.org/groups/graphics/forum/topic/newbie-question-general-fade-inout-effect/#post-105559</a>
+ */
+public class FadeFilter extends Filter {
+
+    private float value = 1;
+    private boolean playing = false;
+    private float direction = 1;
+    private float duration = 1;
+
+    /**
+     * Creates a FadeFilter
+     */
+    public FadeFilter() {
+        super("Fade In/Out");
+    }
+
+    /**
+     * Creates a FadeFilter with the given duration
+     * @param duration 
+     */
+    public FadeFilter(float duration) {
+        this();
+        this.duration = duration;
+    }
+
+    @Override
+    protected Material getMaterial() {
+        material.setFloat("Value", value);
+        return material;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/Fade.j3md");
+    }
+
+    @Override
+    protected void preFrame(float tpf) {
+        if (playing) {
+            value += tpf * direction / duration;
+
+            if (direction > 0 && value > 1) {
+                value = 1;
+                playing = false;
+                setEnabled(false);
+            }
+            if (direction < 0 && value < 0) {
+                value = 0;
+                playing = false;
+                setEnabled(false);
+            }
+        }
+    }
+
+    /**
+     * returns the duration of the effect 
+     * @return 
+     */
+    public float getDuration() {
+        return duration;
+    }
+
+    /**
+     * Sets the duration of the filter default is 1 second
+     * @param duration 
+     */
+    public void setDuration(float duration) {
+        this.duration = duration;
+    }
+
+    /**
+     * fades the scene in (black to scene)
+     */
+    public void fadeIn() {
+        setEnabled(true);
+        direction = 1;
+        playing = true;
+    }
+
+    /**
+     * fades the scene out (scene to black)
+     */
+    public void fadeOut() {
+        setEnabled(true);
+        direction = -1;
+        playing = true;
+
+    }
+
+    public void pause() {
+        playing = false;
+    }
+    
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(duration, "duration", 1);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        duration = ic.readFloat("duration", 1);
+    }
+
+    /**
+     * return the current value of the fading
+     * can be used to chack if fade is complete (eg value=1)
+     * @return 
+     */
+    public float getValue() {
+        return value;
+    }
+
+    /**
+     * sets the fade value
+     * can be used to force complete black or compete scene
+     * @param value 
+     */
+    public void setValue(float value) {
+        this.value = value;       
+        if (material != null) {
+            material.setFloat("Value", value);
+        }
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/FogFilter.java b/engine/src/core-effects/com/jme3/post/filters/FogFilter.java
new file mode 100644
index 0000000..c1df3b7
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/FogFilter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ * A filter to render a fog effect
+ * @author Rémy Bouquet aka Nehon
+ */
+public class FogFilter extends Filter {
+
+    private ColorRGBA fogColor = ColorRGBA.White.clone();
+    private float fogDensity = 0.7f;
+    private float fogDistance = 1000;
+
+    /**
+     * Creates a FogFilter
+     */
+    public FogFilter() {
+        super("FogFilter");
+    }
+
+    /**
+     * Create a fog filter 
+     * @param fogColor the color of the fog (default is white)
+     * @param fogDensity the density of the fog (default is 0.7)
+     * @param fogDistance the distance of the fog (default is 1000)
+     */
+    public FogFilter(ColorRGBA fogColor, float fogDensity, float fogDistance) {
+        this();
+        this.fogColor = fogColor;
+        this.fogDensity = fogDensity;
+        this.fogDistance = fogDistance;
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/Fog.j3md");
+        material.setColor("FogColor", fogColor);
+        material.setFloat("FogDensity", fogDensity);
+        material.setFloat("FogDistance", fogDistance);
+    }
+
+    @Override
+    protected Material getMaterial() {
+
+        return material;
+    }
+
+
+    /**
+     * returns the fog color
+     * @return
+     */
+    public ColorRGBA getFogColor() {
+        return fogColor;
+    }
+
+    /**
+     * Sets the color of the fog
+     * @param fogColor
+     */
+    public void setFogColor(ColorRGBA fogColor) {
+        if (material != null) {
+            material.setColor("FogColor", fogColor);
+        }
+        this.fogColor = fogColor;
+    }
+
+    /**
+     * returns the fog density
+     * @return
+     */
+    public float getFogDensity() {
+        return fogDensity;
+    }
+
+    /**
+     * Sets the density of the fog, a high value gives a thick fog
+     * @param fogDensity
+     */
+    public void setFogDensity(float fogDensity) {
+        if (material != null) {
+            material.setFloat("FogDensity", fogDensity);
+        }
+        this.fogDensity = fogDensity;
+    }
+
+    /**
+     * returns the fog distance
+     * @return
+     */
+    public float getFogDistance() {
+        return fogDistance;
+    }
+
+    /**
+     * the distance of the fog. the higer the value the distant the fog looks
+     * @param fogDistance
+     */
+    public void setFogDistance(float fogDistance) {
+        if (material != null) {
+            material.setFloat("FogDistance", fogDistance);
+        }
+        this.fogDistance = fogDistance;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(fogColor, "fogColor", ColorRGBA.White.clone());
+        oc.write(fogDensity, "fogDensity", 0.7f);
+        oc.write(fogDistance, "fogDistance", 1000);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        fogColor = (ColorRGBA) ic.readSavable("fogColor", ColorRGBA.White.clone());
+        fogDensity = ic.readFloat("fogDensity", 0.7f);
+        fogDistance = ic.readFloat("fogDistance", 1000);
+    }
+
+
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/GammaCorrectionFilter.java b/engine/src/core-effects/com/jme3/post/filters/GammaCorrectionFilter.java
new file mode 100644
index 0000000..9e283ca
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/GammaCorrectionFilter.java
@@ -0,0 +1,78 @@
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * 
+ * @author Phate666
+ * @version 1.0 initial version
+ * @version 1.1 added luma
+ */
+public class GammaCorrectionFilter extends Filter
+{
+	private float gamma = 2.0f;
+	private boolean computeLuma = false;
+
+	public GammaCorrectionFilter()
+	{
+		super("GammaCorrectionFilter");
+	}
+
+	public GammaCorrectionFilter(float gamma)
+	{
+		this();
+		this.setGamma(gamma);
+	}
+
+	@Override
+	protected Material getMaterial()
+	{
+		return material;
+	}
+
+	@Override
+	protected void initFilter(AssetManager manager,
+			RenderManager renderManager, ViewPort vp, int w, int h)
+	{
+		material = new Material(manager,
+				"Common/MatDefs/Post/GammaCorrection.j3md");
+		material.setFloat("gamma", gamma);
+		material.setBoolean("computeLuma", computeLuma);
+	}
+
+	public float getGamma()
+	{
+		return gamma;
+	}
+
+	/**
+	 * set to 0.0 to disable gamma correction
+	 * @param gamma
+	 */
+	public void setGamma(float gamma)
+	{
+		if (material != null)
+		{
+			material.setFloat("gamma", gamma);
+		}
+		this.gamma = gamma;
+	}
+
+	public boolean isComputeLuma()
+	{
+		return computeLuma;
+	}
+
+	public void setComputeLuma(boolean computeLuma)
+	{
+		if (material != null)
+		{
+			material.setBoolean("computeLuma", computeLuma);
+		}
+		this.computeLuma = computeLuma;
+	}
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/LightScatteringFilter.java b/engine/src/core-effects/com/jme3/post/filters/LightScatteringFilter.java
new file mode 100644
index 0000000..953f10a
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/LightScatteringFilter.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ * LightScattering filters creates rays comming from a light sources 
+ * This is often reffered as god rays.
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class LightScatteringFilter extends Filter {
+
+    private Vector3f lightPosition;
+    private Vector3f screenLightPos = new Vector3f();
+    private int nbSamples = 50;
+    private float blurStart = 0.02f;
+    private float blurWidth = 0.9f;
+    private float lightDensity = 1.4f;
+    private boolean adaptative = true;
+    Vector3f viewLightPos = new Vector3f();
+    private boolean display = true;
+    private float innerLightDensity;
+
+    /**
+     * creates a lightScaterring filter
+     */
+    public LightScatteringFilter() {
+        super("Light Scattering");
+    }
+
+    /**
+     * Creates a lightScatteringFilter
+     * @param lightPosition 
+     */
+    public LightScatteringFilter(Vector3f lightPosition) {
+        this();
+        this.lightPosition = lightPosition;
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected Material getMaterial() {
+        material.setVector3("LightPosition", screenLightPos);
+        material.setInt("NbSamples", nbSamples);
+        material.setFloat("BlurStart", blurStart);
+        material.setFloat("BlurWidth", blurWidth);
+        material.setFloat("LightDensity", innerLightDensity);
+        material.setBoolean("Display", display);
+        return material;
+    }
+
+    @Override
+    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+        getClipCoordinates(lightPosition, screenLightPos, viewPort.getCamera());
+        //  screenLightPos.x = screenLightPos.x / viewPort.getCamera().getWidth();
+        //  screenLightPos.y = screenLightPos.y / viewPort.getCamera().getHeight();
+
+        viewPort.getCamera().getViewMatrix().mult(lightPosition, viewLightPos);
+        //System.err.println("viewLightPos "+viewLightPos);
+        display = screenLightPos.x < 1.6f && screenLightPos.x > -0.6f && screenLightPos.y < 1.6f && screenLightPos.y > -0.6f && viewLightPos.z < 0;
+//System.err.println("camdir "+viewPort.getCamera().getDirection());
+//System.err.println("lightPos "+lightPosition);
+//System.err.println("screenLightPos "+screenLightPos);
+        if (adaptative) {
+            innerLightDensity = Math.max(lightDensity - Math.max(screenLightPos.x, screenLightPos.y), 0.0f);
+        } else {
+            innerLightDensity = lightDensity;
+        }
+    }
+
+    private Vector3f getClipCoordinates(Vector3f worldPosition, Vector3f store, Camera cam) {
+
+        float w = cam.getViewProjectionMatrix().multProj(worldPosition, store);
+        store.divideLocal(w);
+
+        store.x = ((store.x + 1f) * (cam.getViewPortRight() - cam.getViewPortLeft()) / 2f + cam.getViewPortLeft());
+        store.y = ((store.y + 1f) * (cam.getViewPortTop() - cam.getViewPortBottom()) / 2f + cam.getViewPortBottom());
+        store.z = (store.z + 1f) / 2f;
+
+        return store;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/LightScattering.j3md");
+    }
+
+    /**
+     * returns the blur start of the scattering 
+     * see {@link  setBlurStart(float blurStart)}
+     * @return 
+     */
+    public float getBlurStart() {
+        return blurStart;
+    }
+
+    /**
+     * sets the blur start<br>
+     * at which distance from the light source the effect starts default is 0.02
+     * @param blurStart 
+     */
+    public void setBlurStart(float blurStart) {
+        this.blurStart = blurStart;
+    }
+
+    /**
+     * returns the blur width<br>
+     * see {@link setBlurWidth(float blurWidth)}
+     * @return 
+     */
+    public float getBlurWidth() {
+        return blurWidth;
+    }
+
+    /**
+     * sets the blur width default is 0.9
+     * @param blurWidth 
+     */
+    public void setBlurWidth(float blurWidth) {
+        this.blurWidth = blurWidth;
+    }
+
+    /**
+     * retiurns the light density<br>
+     * see {@link setLightDensity(float lightDensity)}
+     * 
+     * @return 
+     */
+    public float getLightDensity() {
+        return lightDensity;
+    }
+
+    /**
+     * sets how much the effect is visible over the rendered scene default is 1.4
+     * @param lightDensity 
+     */
+    public void setLightDensity(float lightDensity) {
+        this.lightDensity = lightDensity;
+    }
+
+    /**
+     * returns the light position
+     * @return 
+     */
+    public Vector3f getLightPosition() {
+        return lightPosition;
+    }
+
+    /**
+     * sets the light position
+     * @param lightPosition 
+     */
+    public void setLightPosition(Vector3f lightPosition) {
+        this.lightPosition = lightPosition;
+    }
+
+    /**
+     * returns the nmber of samples for the radial blur
+     * @return 
+     */
+    public int getNbSamples() {
+        return nbSamples;
+    }
+
+    /**
+     * sets the number of samples for the radial blur default is 50
+     * the higher the value the higher the quality, but the slower the performances.
+     * @param nbSamples 
+     */
+    public void setNbSamples(int nbSamples) {
+        this.nbSamples = nbSamples;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(lightPosition, "lightPosition", Vector3f.ZERO);
+        oc.write(nbSamples, "nbSamples", 50);
+        oc.write(blurStart, "blurStart", 0.02f);
+        oc.write(blurWidth, "blurWidth", 0.9f);
+        oc.write(lightDensity, "lightDensity", 1.4f);
+        oc.write(adaptative, "adaptative", true);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        lightPosition = (Vector3f) ic.readSavable("lightPosition", Vector3f.ZERO);
+        nbSamples = ic.readInt("nbSamples", 50);
+        blurStart = ic.readFloat("blurStart", 0.02f);
+        blurWidth = ic.readFloat("blurWidth", 0.9f);
+        lightDensity = ic.readFloat("lightDensity", 1.4f);
+        adaptative = ic.readBoolean("adaptative", true);
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/PosterizationFilter.java b/engine/src/core-effects/com/jme3/post/filters/PosterizationFilter.java
new file mode 100644
index 0000000..c980eda
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/PosterizationFilter.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2009-2010 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * A Post Processing filter to change colors appear with sharp edges as if the
+ * available amount of colors available was not enough to draw the true image.
+ * Possibly useful in cartoon styled games. Use the strength variable to lessen
+ * influence of this filter on the total result. Values from 0.2 to 0.7 appear
+ * to give nice results.
+ *
+ * Based on an article from Geeks3D:
+ *    <a href="http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/" rel="nofollow">http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/</a>
+ *
+ * @author: Roy Straver a.k.a. Baal Garnaal
+ */
+public class PosterizationFilter extends Filter {
+
+    private int numColors = 8;
+    private float gamma = 0.6f;
+    private float strength = 1.0f;
+
+    /**
+     * Creates a posterization Filter
+     */
+    public PosterizationFilter() {
+        super("PosterizationFilter");
+    }
+
+    /**
+     * Creates a posterization Filter with the given number of colors
+     * @param numColors 
+     */
+    public PosterizationFilter(int numColors) {
+        this();
+        this.numColors = numColors;
+    }
+
+    /**
+     * Creates a posterization Filter with the given number of colors and gamma
+     * @param numColors
+     * @param gamma 
+     */
+    public PosterizationFilter(int numColors, float gamma) {
+        this(numColors);
+        this.gamma = gamma;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Post/Posterization.j3md");
+        material.setInt("NumColors", numColors);
+        material.setFloat("Gamma", gamma);
+        material.setFloat("Strength", strength);
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    /**
+     * Sets number of color levels used to draw the screen
+     */
+    public void setNumColors(int numColors) {
+        this.numColors = numColors;
+        if (material != null) {
+            material.setInt("NumColors", numColors);
+        }
+    }
+
+    /**
+     * Sets gamma level used to enhange visual quality
+     */
+    public void setGamma(float gamma) {
+        this.gamma = gamma;
+        if (material != null) {
+            material.setFloat("Gamma", gamma);
+        }
+    }
+
+    /**
+     * Sets urrent strength value, i.e. influence on final image
+     */
+    public void setStrength(float strength) {
+        this.strength = strength;
+        if (material != null) {
+            material.setFloat("Strength", strength);
+        }
+    }
+
+    /**
+     * Returns number of color levels used
+     */
+    public int getNumColors() {
+        return numColors;
+    }
+
+    /**
+     * Returns current gamma value
+     */
+    public float getGamma() {
+        return gamma;
+    }
+
+    /**
+     * Returns current strength value, i.e. influence on final image
+     */
+    public float getStrength() {
+        return strength;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-effects/com/jme3/post/filters/RadialBlurFilter.java b/engine/src/core-effects/com/jme3/post/filters/RadialBlurFilter.java
new file mode 100644
index 0000000..0bcae5f
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/RadialBlurFilter.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.shader.VarType;
+import java.io.IOException;
+
+/**
+ * Radially blurs the scene from the center of it
+ * @author Rémy Bouquet aka Nehon
+ */
+public class RadialBlurFilter extends Filter {
+
+    private float sampleDist = 1.0f;
+    private float sampleStrength = 2.2f;
+    private float[] samples = {-0.08f, -0.05f, -0.03f, -0.02f, -0.01f, 0.01f, 0.02f, 0.03f, 0.05f, 0.08f};
+
+    /**
+     * Creates a RadialBlurFilter
+     */
+    public RadialBlurFilter() {
+        super("Radial blur");
+    }
+
+    /**
+     * Creates a RadialBlurFilter
+     * @param sampleDist the distance between samples
+     * @param sampleStrength the strenght of each sample
+     */
+    public RadialBlurFilter(float sampleDist, float sampleStrength) {
+        this();
+        this.sampleDist = sampleDist;
+        this.sampleStrength = sampleStrength;
+    }
+
+    @Override
+    protected Material getMaterial() {
+
+        material.setFloat("SampleDist", sampleDist);
+        material.setFloat("SampleStrength", sampleStrength);
+        material.setParam("Samples", VarType.FloatArray, samples);
+
+        return material;
+    }
+
+    /**
+     * return the sample distance
+     * @return 
+     */
+    public float getSampleDistance() {
+        return sampleDist;
+    }
+
+    /**
+     * sets the samples distances default is 1
+     * @param sampleDist 
+     */
+    public void setSampleDistance(float sampleDist) {
+        this.sampleDist = sampleDist;
+    }
+
+    /**
+     * 
+     * @return 
+     * @deprecated use {@link #getSampleDistance()}
+     */
+    @Deprecated
+    public float getSampleDist() {
+        return sampleDist;
+    }
+
+    /**
+     * 
+     * @param sampleDist
+     * @deprecated use {@link #setSampleDistance(float sampleDist)}
+     */
+    @Deprecated
+    public void setSampleDist(float sampleDist) {
+        this.sampleDist = sampleDist;
+    }
+
+    /**
+     * Returns the sample Strength
+     * @return 
+     */
+    public float getSampleStrength() {
+        return sampleStrength;
+    }
+
+    /**
+     * sets the sample streanght default is 2.2
+     * @param sampleStrength 
+     */
+    public void setSampleStrength(float sampleStrength) {
+        this.sampleStrength = sampleStrength;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        material = new Material(manager, "Common/MatDefs/Blur/RadialBlur.j3md");
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(sampleDist, "sampleDist", 1.0f);
+        oc.write(sampleStrength, "sampleStrength", 2.2f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        sampleDist = ic.readFloat("sampleDist", 1.0f);
+        sampleStrength = ic.readFloat("sampleStrength", 2.2f);
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java b/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java
new file mode 100644
index 0000000..47be413
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/filters/TranslucentBucketFilter.java
@@ -0,0 +1,80 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture2D;
+
+/**
+ * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter
+ * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostPorcessor
+ * @author Nehon
+ */
+public final class TranslucentBucketFilter extends Filter {
+
+    private RenderManager renderManager;
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager rm, ViewPort vp, int w, int h) {
+        this.renderManager = rm;
+        material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md");
+        material.setColor("Color", ColorRGBA.White);
+        Texture2D tex = processor.getFilterTexture();
+        material.setTexture("Texture", tex);
+        if (tex.getImage().getMultiSamples() > 1) {
+            material.setInt("NumSamples", tex.getImage().getMultiSamples());
+        } else {
+            material.clearParam("NumSamples");
+        }
+        renderManager.setHandleTranslucentBucket(false);
+    }
+
+    /**
+     * Override this method and return false if your Filter does not need the scene texture
+     * @return
+     */
+    @Override
+    protected boolean isRequiresSceneTexture() {
+        return false;
+    }
+
+    @Override
+    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
+        renderManager.setCamera(viewPort.getCamera(), false);
+        if (prevFilterBuffer != sceneBuffer) {
+            renderManager.getRenderer().copyFrameBuffer(prevFilterBuffer, sceneBuffer, false);
+        }
+        renderManager.getRenderer().setFrameBuffer(sceneBuffer);
+        viewPort.getQueue().renderQueue(RenderQueue.Bucket.Translucent, renderManager, viewPort.getCamera());
+    }
+
+    @Override
+    protected void cleanUpFilter(Renderer r) {
+        if (renderManager != null) {
+            renderManager.setHandleTranslucentBucket(true);
+        }
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if (renderManager != null) {
+            renderManager.setHandleTranslucentBucket(!enabled);
+        }
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/post/ssao/SSAOFilter.java b/engine/src/core-effects/com/jme3/post/ssao/SSAOFilter.java
new file mode 100644
index 0000000..cb0c037
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/post/ssao/SSAOFilter.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post.ssao;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * SSAO stands for screen space ambient occlusion
+ * It's a technique that fake ambient lighting by computing shadows that near by objects would casts on each others 
+ * under the effect of an ambient light
+ * more info on this in this blog post <a href="http://jmonkeyengine.org/2010/08/16/screen-space-ambient-occlusion-for-jmonkeyengine-3-0/">http://jmonkeyengine.org/2010/08/16/screen-space-ambient-occlusion-for-jmonkeyengine-3-0/</a>
+ * 
+ * @author Rémy Bouquet aka Nehon
+ */
+public class SSAOFilter extends Filter {
+
+    private Pass normalPass;
+    private Vector3f frustumCorner;
+    private Vector2f frustumNearFar;
+    private Vector2f[] samples = {new Vector2f(1.0f, 0.0f), new Vector2f(-1.0f, 0.0f), new Vector2f(0.0f, 1.0f), new Vector2f(0.0f, -1.0f)};
+    private float sampleRadius = 5.1f;
+    private float intensity = 1.5f;
+    private float scale = 0.2f;
+    private float bias = 0.1f;
+    private boolean useOnlyAo = false;
+    private boolean useAo = true;
+    private Material ssaoMat;
+    private Pass ssaoPass;
+//    private Material downSampleMat;
+//    private Pass downSamplePass;
+    private float downSampleFactor = 1f;
+
+    /**
+     * Create a Screen Space Ambient Occlusion Filter
+     */
+    public SSAOFilter() {
+        super("SSAOFilter");
+    }
+
+    /**
+     * Create a Screen Space Ambient Occlusion Filter
+     * @param sampleRadius The radius of the area where random samples will be picked. default 5.1f
+     * @param intensity intensity of the resulting AO. default 1.2f
+     * @param scale distance between occluders and occludee. default 0.2f
+     * @param bias the width of the occlusion cone considered by the occludee. default 0.1f
+     */
+    public SSAOFilter(float sampleRadius, float intensity, float scale, float bias) {
+        this();
+        this.sampleRadius = sampleRadius;
+        this.intensity = intensity;
+        this.scale = scale;
+        this.bias = bias;
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+        Renderer r = renderManager.getRenderer();
+        r.setFrameBuffer(normalPass.getRenderFrameBuffer());
+        renderManager.getRenderer().clearBuffers(true, true, true);
+        renderManager.setForcedTechnique("PreNormalPass");
+        renderManager.renderViewPortQueues(viewPort, false);
+        renderManager.setForcedTechnique(null);
+        renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+        int screenWidth = w;
+        int screenHeight = h;
+        postRenderPasses = new ArrayList<Pass>();
+
+        normalPass = new Pass();
+        normalPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth);
+
+
+        frustumNearFar = new Vector2f();
+
+        float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar();
+        float farX = farY * ((float) screenWidth / (float) screenHeight);
+        frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar());
+        frustumNearFar.x = vp.getCamera().getFrustumNear();
+        frustumNearFar.y = vp.getCamera().getFrustumFar();
+
+
+
+
+
+        //ssao Pass
+        ssaoMat = new Material(manager, "Common/MatDefs/SSAO/ssao.j3md");
+        ssaoMat.setTexture("Normals", normalPass.getRenderedTexture());
+        Texture random = manager.loadTexture("Common/MatDefs/SSAO/Textures/random.png");
+        random.setWrap(Texture.WrapMode.Repeat);
+        ssaoMat.setTexture("RandomMap", random);
+
+        ssaoPass = new Pass() {
+
+            @Override
+            public boolean requiresDepthAsTexture() {
+                return true;
+            }
+        };
+
+        ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
+        ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
+        ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
+        postRenderPasses.add(ssaoPass);
+        material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
+        material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());
+
+        ssaoMat.setVector3("FrustumCorner", frustumCorner);
+        ssaoMat.setFloat("SampleRadius", sampleRadius);
+        ssaoMat.setFloat("Intensity", intensity);
+        ssaoMat.setFloat("Scale", scale);
+        ssaoMat.setFloat("Bias", bias);
+        material.setBoolean("UseAo", useAo);
+        material.setBoolean("UseOnlyAo", useOnlyAo);
+        ssaoMat.setVector2("FrustumNearFar", frustumNearFar);
+        material.setVector2("FrustumNearFar", frustumNearFar);
+        ssaoMat.setParam("Samples", VarType.Vector2Array, samples);
+
+        float xScale = 1.0f / w;
+        float yScale = 1.0f / h;
+
+        float blurScale = 2f;
+        material.setFloat("XScale", blurScale * xScale);
+        material.setFloat("YScale", blurScale * yScale);
+
+    }
+
+    /**
+     * Return the bias<br>
+     * see {@link  #setBias(float bias)}
+     * @return 
+     */
+    public float getBias() {
+        return bias;
+    }
+
+    /**
+     * Sets the the width of the occlusion cone considered by the occludee default is 0.1f
+     * @param bias 
+     */
+    public void setBias(float bias) {
+        this.bias = bias;
+        if (ssaoMat != null) {
+            ssaoMat.setFloat("Bias", bias);
+        }
+    }
+
+    /**
+     * returns the ambient occlusion intensity
+     * @return 
+     */
+    public float getIntensity() {
+        return intensity;
+    }
+
+    /**
+     * Sets the Ambient occlusion intensity default is 1.2f
+     * @param intensity 
+     */
+    public void setIntensity(float intensity) {
+        this.intensity = intensity;
+        if (ssaoMat != null) {
+            ssaoMat.setFloat("Intensity", intensity);
+        }
+
+    }
+
+    /**
+     * returns the sample radius<br>
+     * see {link setSampleRadius(float sampleRadius)}
+     * @return 
+     */
+    public float getSampleRadius() {
+        return sampleRadius;
+    }
+
+    /**
+     * Sets the radius of the area where random samples will be picked dafault 5.1f     
+     * @param sampleRadius 
+     */
+    public void setSampleRadius(float sampleRadius) {
+        this.sampleRadius = sampleRadius;
+        if (ssaoMat != null) {
+            ssaoMat.setFloat("SampleRadius", sampleRadius);
+        }
+
+    }
+
+    /**
+     * returns the scale<br>
+     * see {@link #setScale(float scale)}
+     * @return 
+     */
+    public float getScale() {
+        return scale;
+    }
+
+    /**
+     * 
+     * Returns the distance between occluders and occludee. default 0.2f
+     * @param scale 
+     */
+    public void setScale(float scale) {
+        this.scale = scale;
+        if (ssaoMat != null) {
+            ssaoMat.setFloat("Scale", scale);
+        }
+    }
+
+    /**
+     * debugging only , will be removed
+     * @return 
+     */
+    public boolean isUseAo() {
+        return useAo;
+    }
+
+    /**
+     * debugging only , will be removed
+     */
+    public void setUseAo(boolean useAo) {
+        this.useAo = useAo;
+        if (material != null) {
+            material.setBoolean("UseAo", useAo);
+        }
+
+    }
+
+    /**
+     * debugging only , will be removed
+     * @return 
+     */
+    public boolean isUseOnlyAo() {
+        return useOnlyAo;
+    }
+
+    /**
+     * debugging only , will be removed
+     */
+    public void setUseOnlyAo(boolean useOnlyAo) {
+        this.useOnlyAo = useOnlyAo;
+        if (material != null) {
+            material.setBoolean("UseOnlyAo", useOnlyAo);
+        }
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(sampleRadius, "sampleRadius", 5.1f);
+        oc.write(intensity, "intensity", 1.5f);
+        oc.write(scale, "scale", 0.2f);
+        oc.write(bias, "bias", 0.1f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        sampleRadius = ic.readFloat("sampleRadius", 5.1f);
+        intensity = ic.readFloat("intensity", 1.5f);
+        scale = ic.readFloat("scale", 0.2f);
+        bias = ic.readFloat("bias", 0.1f);
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/water/ReflectionProcessor.java b/engine/src/core-effects/com/jme3/water/ReflectionProcessor.java
new file mode 100644
index 0000000..9a14df8
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/water/ReflectionProcessor.java
@@ -0,0 +1,125 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.water;
+
+import com.jme3.math.Plane;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Reflection Processor
+ * Used to render the reflected scene in an off view port
+ */
+public class ReflectionProcessor implements SceneProcessor {
+
+    private RenderManager rm;
+    private ViewPort vp;
+    private Camera reflectionCam;
+    private FrameBuffer reflectionBuffer;
+    private Plane reflectionClipPlane;
+
+    /**
+     * Creates a ReflectionProcessor
+     * @param reflectionCam the cam to use for reflection
+     * @param reflectionBuffer the FrameBuffer to render to
+     * @param reflectionClipPlane the clipping plane
+     */
+    public ReflectionProcessor(Camera reflectionCam, FrameBuffer reflectionBuffer, Plane reflectionClipPlane) {
+        this.reflectionCam = reflectionCam;
+        this.reflectionBuffer = reflectionBuffer;
+        this.reflectionClipPlane = reflectionClipPlane;
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        this.rm = rm;
+        this.vp = vp;
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    public boolean isInitialized() {
+        return rm != null;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+        //we need special treatement for the sky because it must not be clipped
+        rm.getRenderer().setFrameBuffer(reflectionBuffer);
+        reflectionCam.setProjectionMatrix(null);
+        rm.setCamera(reflectionCam, false);
+        rm.getRenderer().clearBuffers(true, true, true);
+        //Rendering the sky whithout clipping
+        rm.getRenderer().setDepthRange(1, 1);
+        vp.getQueue().renderQueue(RenderQueue.Bucket.Sky, rm, reflectionCam, true);
+        rm.getRenderer().setDepthRange(0, 1);
+        //setting the clip plane to the cam
+        reflectionCam.setClipPlane(reflectionClipPlane, Plane.Side.Positive);//,1
+        rm.setCamera(reflectionCam, false);
+
+    }
+
+    public void postFrame(FrameBuffer out) {
+    }
+
+    public void cleanup() {
+    }
+
+    /**
+     * Internal use only<br>
+     * returns the frame buffer
+     * @return 
+     */
+    public FrameBuffer getReflectionBuffer() {
+        return reflectionBuffer;
+    }
+
+    /**
+     * Internal use only<br>
+     * sets the frame buffer
+     * @param reflectionBuffer 
+     */
+    public void setReflectionBuffer(FrameBuffer reflectionBuffer) {
+        this.reflectionBuffer = reflectionBuffer;
+    }
+
+    /**
+     * returns the reflection cam
+     * @return 
+     */
+    public Camera getReflectionCam() {
+        return reflectionCam;
+    }
+
+    /**
+     * sets the reflection cam
+     * @param reflectionCam 
+     */
+    public void setReflectionCam(Camera reflectionCam) {
+        this.reflectionCam = reflectionCam;
+    }
+
+    /**
+     * returns the reflection clip plane
+     * @return 
+     */
+    public Plane getReflectionClipPlane() {
+        return reflectionClipPlane;
+    }
+
+    /**
+     * Sets the reflection clip plane
+     * @param reflectionClipPlane 
+     */
+    public void setReflectionClipPlane(Plane reflectionClipPlane) {
+        this.reflectionClipPlane = reflectionClipPlane;
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/water/SimpleWaterProcessor.java b/engine/src/core-effects/com/jme3/water/SimpleWaterProcessor.java
new file mode 100644
index 0000000..70ccd11
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/water/SimpleWaterProcessor.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2009-2010 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.water;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ *
+ * Simple Water renders a simple plane that use reflection and refraction to look like water.
+ * It's pretty basic, but much faster than the WaterFilter
+ * It's useful if you aim low specs hardware and still want a good looking water.
+ * Usage is : 
+ * <code>
+ *      SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
+ *      //setting the scene to use for reflection
+ *      waterProcessor.setReflectionScene(mainScene);
+ *      //setting the light position
+ *      waterProcessor.setLightPosition(lightPos);
+ *      
+ *      //setting the water plane
+ *      Vector3f waterLocation=new Vector3f(0,-20,0);
+ *      waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
+ *      //setting the water color 
+ *      waterProcessor.setWaterColor(ColorRGBA.Brown);
+ *
+ *      //creating a quad to render water to
+ *      Quad quad = new Quad(400,400);
+ *
+ *      //the texture coordinates define the general size of the waves
+ *      quad.scaleTextureCoordinates(new Vector2f(6f,6f));
+ *
+ *      //creating a geom to attach the water material 
+ *      Geometry water=new Geometry("water", quad);
+ *      water.setLocalTranslation(-200, -20, 250);
+ *      water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+ *      //finally setting the material
+ *      water.setMaterial(waterProcessor.getMaterial());
+ *    
+ *      //attaching the water to the root node
+ *      rootNode.attachChild(water);
+ * </code>
+ * @author Normen Hansen & Rémy Bouquet
+ */
+public class SimpleWaterProcessor implements SceneProcessor {
+
+    protected RenderManager rm;
+    protected ViewPort vp;
+    protected Spatial reflectionScene;
+    protected ViewPort reflectionView;
+    protected ViewPort refractionView;
+    protected FrameBuffer reflectionBuffer;
+    protected FrameBuffer refractionBuffer;
+    protected Camera reflectionCam;
+    protected Camera refractionCam;
+    protected Texture2D reflectionTexture;
+    protected Texture2D refractionTexture;
+    protected Texture2D depthTexture;
+    protected Texture2D normalTexture;
+    protected Texture2D dudvTexture;
+    protected int renderWidth = 512;
+    protected int renderHeight = 512;
+    protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
+    protected float speed = 0.05f;
+    protected Ray ray = new Ray();
+    protected Vector3f targetLocation = new Vector3f();
+    protected AssetManager manager;
+    protected Material material;
+    protected float waterDepth = 1;
+    protected float waterTransparency = 0.4f;
+    protected boolean debug = false;
+    private Picture dispRefraction;
+    private Picture dispReflection;
+    private Picture dispDepth;
+    private Plane reflectionClipPlane;
+    private Plane refractionClipPlane;
+    private float refractionClippingOffset = 0.3f;
+    private float reflectionClippingOffset = -5f;
+    private Vector3f vect1 = new Vector3f();
+    private Vector3f vect2 = new Vector3f();
+    private Vector3f vect3 = new Vector3f();
+
+    /**
+     * Creates a SimpleWaterProcessor
+     * @param manager the asset manager
+     */
+    public SimpleWaterProcessor(AssetManager manager) {
+        this.manager = manager;
+        material = new Material(manager, "Common/MatDefs/Water/SimpleWater.j3md");
+        material.setFloat("waterDepth", waterDepth);
+        material.setFloat("waterTransparency", waterTransparency / 10);
+        material.setColor("waterColor", ColorRGBA.White);
+        material.setVector3("lightPos", new Vector3f(1, -1, 1));
+
+        material.setColor("distortionScale", new ColorRGBA(0.2f, 0.2f, 0.2f, 0.2f));
+        material.setColor("distortionMix", new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
+        material.setColor("texScale", new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
+        updateClipPlanes();
+
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        this.rm = rm;
+        this.vp = vp;
+
+        loadTextures(manager);
+        createTextures();
+        applyTextures(material);
+
+        createPreViews();
+
+        material.setVector2("FrustumNearFar", new Vector2f(vp.getCamera().getFrustumNear(), vp.getCamera().getFrustumFar()));
+
+        if (debug) {
+            dispRefraction = new Picture("dispRefraction");
+            dispRefraction.setTexture(manager, refractionTexture, false);
+            dispReflection = new Picture("dispRefraction");
+            dispReflection.setTexture(manager, reflectionTexture, false);
+            dispDepth = new Picture("depthTexture");
+            dispDepth.setTexture(manager, depthTexture, false);
+        }
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    public boolean isInitialized() {
+        return rm != null;
+    }
+    float time = 0;
+    float savedTpf = 0;
+
+    public void preFrame(float tpf) {
+        time = time + (tpf * speed);
+        if (time > 1f) {
+            time = 0;
+        }
+        material.setFloat("time", time);
+        savedTpf = tpf;
+    }
+
+    public void postQueue(RenderQueue rq) {
+        Camera sceneCam = rm.getCurrentCamera();
+
+        //update ray
+        ray.setOrigin(sceneCam.getLocation());
+        ray.setDirection(sceneCam.getDirection());
+
+        //update refraction cam
+        refractionCam.setLocation(sceneCam.getLocation());
+        refractionCam.setRotation(sceneCam.getRotation());
+        refractionCam.setFrustum(sceneCam.getFrustumNear(),
+                sceneCam.getFrustumFar(),
+                sceneCam.getFrustumLeft(),
+                sceneCam.getFrustumRight(),
+                sceneCam.getFrustumTop(),
+                sceneCam.getFrustumBottom());
+
+        //update reflection cam
+        boolean inv = false;
+        if (!ray.intersectsWherePlane(plane, targetLocation)) {
+            ray.setDirection(ray.getDirection().negateLocal());
+            ray.intersectsWherePlane(plane, targetLocation);
+            inv = true;
+        }
+        Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
+        reflectionCam.setLocation(loc);
+        reflectionCam.setFrustum(sceneCam.getFrustumNear(),
+                sceneCam.getFrustumFar(),
+                sceneCam.getFrustumLeft(),
+                sceneCam.getFrustumRight(),
+                sceneCam.getFrustumTop(),
+                sceneCam.getFrustumBottom());
+        // tempVec and calcVect are just temporary vector3f objects
+        vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
+        float planeDistance = plane.pseudoDistance(vect1);
+        vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
+        vect3.set(vect1.subtractLocal(vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
+        // now set the up vector
+        reflectionCam.lookAt(targetLocation, vect3);
+        if (inv) {
+            reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
+        }
+
+        //Rendering reflection and refraction
+        rm.renderViewPort(reflectionView, savedTpf);
+        rm.renderViewPort(refractionView, savedTpf);
+        rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
+        rm.setCamera(sceneCam, false);
+
+    }
+
+    public void postFrame(FrameBuffer out) {
+        if (debug) {
+            displayMap(rm.getRenderer(), dispRefraction, 64);
+            displayMap(rm.getRenderer(), dispReflection, 256);
+            displayMap(rm.getRenderer(), dispDepth, 448);
+        }
+    }
+
+    public void cleanup() {
+    }
+
+    //debug only : displays maps
+    protected void displayMap(Renderer r, Picture pic, int left) {
+        Camera cam = vp.getCamera();
+        rm.setCamera(cam, true);
+        int h = cam.getHeight();
+
+        pic.setPosition(left, h / 20f);
+
+        pic.setWidth(128);
+        pic.setHeight(128);
+        pic.updateGeometricState();
+        rm.renderGeometry(pic);
+        rm.setCamera(cam, false);
+    }
+
+    protected void loadTextures(AssetManager manager) {
+        normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds");
+        dudvTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/dudv_map.jpg");
+        normalTexture.setWrap(WrapMode.Repeat);
+        dudvTexture.setWrap(WrapMode.Repeat);
+    }
+
+    protected void createTextures() {
+        reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
+        refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
+        depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth);
+    }
+
+    protected void applyTextures(Material mat) {
+        mat.setTexture("water_reflection", reflectionTexture);
+        mat.setTexture("water_refraction", refractionTexture);
+        mat.setTexture("water_depthmap", depthTexture);
+        mat.setTexture("water_normalmap", normalTexture);
+        mat.setTexture("water_dudvmap", dudvTexture);
+    }
+
+    protected void createPreViews() {
+        reflectionCam = new Camera(renderWidth, renderHeight);
+        refractionCam = new Camera(renderWidth, renderHeight);
+
+        // create a pre-view. a view that is rendered before the main view
+        reflectionView = new ViewPort("Reflection View", reflectionCam);
+        reflectionView.setClearFlags(true, true, true);
+        reflectionView.setBackgroundColor(ColorRGBA.Black);
+        // create offscreen framebuffer
+        reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
+        //setup framebuffer to use texture
+        reflectionBuffer.setDepthBuffer(Format.Depth);
+        reflectionBuffer.setColorTexture(reflectionTexture);
+
+        //set viewport to render to offscreen framebuffer
+        reflectionView.setOutputFrameBuffer(reflectionBuffer);
+        reflectionView.addProcessor(new ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane));
+        // attach the scene to the viewport to be rendered
+        reflectionView.attachScene(reflectionScene);
+
+        // create a pre-view. a view that is rendered before the main view
+        refractionView = new ViewPort("Refraction View", refractionCam);
+        refractionView.setClearFlags(true, true, true);
+        refractionView.setBackgroundColor(ColorRGBA.Black);
+        // create offscreen framebuffer
+        refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
+        //setup framebuffer to use texture
+        refractionBuffer.setDepthBuffer(Format.Depth);
+        refractionBuffer.setColorTexture(refractionTexture);
+        refractionBuffer.setDepthTexture(depthTexture);
+        //set viewport to render to offscreen framebuffer
+        refractionView.setOutputFrameBuffer(refractionBuffer);
+        refractionView.addProcessor(new RefractionProcessor());
+        // attach the scene to the viewport to be rendered
+        refractionView.attachScene(reflectionScene);
+    }
+
+    protected void destroyViews() {
+        //  rm.removePreView(reflectionView);
+        rm.removePreView(refractionView);
+    }
+
+    /**
+     * Get the water material from this processor, apply this to your water quad.
+     * @return
+     */
+    public Material getMaterial() {
+        return material;
+    }
+
+    /**
+     * Sets the reflected scene, should not include the water quad!
+     * Set before adding processor.
+     * @param spat
+     */
+    public void setReflectionScene(Spatial spat) {
+        reflectionScene = spat;
+    }
+
+    /**
+     * returns the width of the reflection and refraction textures
+     * @return 
+     */
+    public int getRenderWidth() {
+        return renderWidth;
+    }
+
+    /**
+     * returns the height of the reflection and refraction textures
+     * @return 
+     */
+    public int getRenderHeight() {
+        return renderHeight;
+    }
+
+    /**
+     * Set the reflection Texture render size,
+     * set before adding the processor!
+     * @param with
+     * @param height
+     */
+    public void setRenderSize(int width, int height) {
+        renderWidth = width;
+        renderHeight = height;
+    }
+
+    /**
+     * returns the water plane
+     * @return 
+     */
+    public Plane getPlane() {
+        return plane;
+    }
+
+    /**
+     * Set the water plane for this processor.
+     * @param plane
+     */
+    public void setPlane(Plane plane) {
+        this.plane.setConstant(plane.getConstant());
+        this.plane.setNormal(plane.getNormal());
+        updateClipPlanes();
+    }
+
+    /**
+     * Set the water plane using an origin (location) and a normal (reflection direction).
+     * @param origin Set to 0,-6,0 if your water quad is at that location for correct reflection
+     * @param normal Set to 0,1,0 (Vector3f.UNIT_Y) for normal planar water
+     */
+    public void setPlane(Vector3f origin, Vector3f normal) {
+        this.plane.setOriginNormal(origin, normal);
+        updateClipPlanes();
+    }
+
+    private void updateClipPlanes() {
+        reflectionClipPlane = plane.clone();
+        reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
+        refractionClipPlane = plane.clone();
+        refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset);
+
+    }
+
+    /**
+     * Set the light Position for the processor
+     * @param position
+     */
+    //TODO maybe we should provide a convenient method to compute position from direction
+    public void setLightPosition(Vector3f position) {
+        material.setVector3("lightPos", position);
+    }
+
+    /**
+     * Set the color that will be added to the refraction texture.
+     * @param color
+     */
+    public void setWaterColor(ColorRGBA color) {
+        material.setColor("waterColor", color);
+    }
+
+    /**
+     * Higher values make the refraction texture shine through earlier.
+     * Default is 4
+     * @param depth
+     */
+    public void setWaterDepth(float depth) {
+        waterDepth = depth;
+        material.setFloat("waterDepth", depth);
+    }
+
+    /**
+     * return the water depth
+     * @return 
+     */
+    public float getWaterDepth() {
+        return waterDepth;
+    }
+
+    /**
+     * returns water transparency
+     * @return 
+     */
+    public float getWaterTransparency() {
+        return waterTransparency;
+    }
+
+    /**
+     * sets the water transparency default os 0.1f
+     * @param waterTransparency 
+     */
+    public void setWaterTransparency(float waterTransparency) {
+        this.waterTransparency = Math.max(0, waterTransparency);
+        material.setFloat("waterTransparency", waterTransparency / 10);
+    }
+
+    /**
+     * Sets the speed of the wave animation, default = 0.05f.
+     * @param speed
+     */
+    public void setWaveSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * Sets the scale of distortion by the normal map, default = 0.2
+     */
+    public void setDistortionScale(float value) {
+        material.setColor("distortionScale", new ColorRGBA(value, value, value, value));
+    }
+
+    /**
+     * Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5
+     */
+    public void setDistortionMix(float value) {
+        material.setColor("distortionMix", new ColorRGBA(value, value, value, value));
+    }
+
+    /**
+     * Sets the scale of the normal/dudv texture, default = 1.
+     * Note that the waves should be scaled by the texture coordinates of the quad to avoid animation artifacts,
+     * use mesh.scaleTextureCoordinates(Vector2f) for that.
+     */
+    public void setTexScale(float value) {
+        material.setColor("texScale", new ColorRGBA(value, value, value, value));
+    }
+
+    /**
+     * retruns true if the waterprocessor is in debug mode
+     * @return 
+     */
+    public boolean isDebug() {
+        return debug;
+    }
+
+    /**
+     * set to true to display reflection and refraction textures in the GUI for debug purpose
+     * @param debug 
+     */
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    /**
+     * Creates a quad with the water material applied to it.
+     * @param width
+     * @param height
+     * @return
+     */
+    public Geometry createWaterGeometry(float width, float height) {
+        Quad quad = new Quad(width, height);
+        Geometry geom = new Geometry("WaterGeometry", quad);
+        geom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+        geom.setMaterial(material);
+        return geom;
+    }
+
+    /**
+     * returns the reflection clipping plane offset
+     * @return
+     */
+    public float getReflectionClippingOffset() {
+        return reflectionClippingOffset;
+    }
+
+    /**
+     * sets the reflection clipping plane offset
+     * set a nagetive value to lower the clipping plane for relection texture rendering.     
+     * @param reflectionClippingOffset
+     */
+    public void setReflectionClippingOffset(float reflectionClippingOffset) {
+        this.reflectionClippingOffset = reflectionClippingOffset;
+        updateClipPlanes();
+    }
+
+    /**
+     * returns the refraction clipping plane offset
+     * @return
+     */
+    public float getRefractionClippingOffset() {
+        return refractionClippingOffset;
+    }
+
+    /**
+     * Sets the refraction clipping plane offset
+     * set a positive value to raise the clipping plane for refraction texture rendering
+     * @param refractionClippingOffset
+     */
+    public void setRefractionClippingOffset(float refractionClippingOffset) {
+        this.refractionClippingOffset = refractionClippingOffset;
+        updateClipPlanes();
+    }
+
+    /**
+     * Refraction Processor
+     */
+    public class RefractionProcessor implements SceneProcessor {
+
+        RenderManager rm;
+        ViewPort vp;
+
+        public void initialize(RenderManager rm, ViewPort vp) {
+            this.rm = rm;
+            this.vp = vp;
+        }
+
+        public void reshape(ViewPort vp, int w, int h) {
+        }
+
+        public boolean isInitialized() {
+            return rm != null;
+        }
+
+        public void preFrame(float tpf) {
+            refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1
+
+        }
+
+        public void postQueue(RenderQueue rq) {
+        }
+
+        public void postFrame(FrameBuffer out) {
+        }
+
+        public void cleanup() {
+        }
+    }
+}
diff --git a/engine/src/core-effects/com/jme3/water/WaterFilter.java b/engine/src/core-effects/com/jme3/water/WaterFilter.java
new file mode 100644
index 0000000..2943dce
--- /dev/null
+++ b/engine/src/core-effects/com/jme3/water/WaterFilter.java
@@ -0,0 +1,1050 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.water;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * The WaterFilter is a 2D post process that simulate water.
+ * It renders water above and under water.
+ * See this blog post for more info <a href="http://jmonkeyengine.org/2011/01/15/new-advanced-water-effect-for-jmonkeyengine-3/">http://jmonkeyengine.org/2011/01/15/new-advanced-water-effect-for-jmonkeyengine-3/</a>
+ * 
+ * 
+ * @author Rémy Bouquet aka Nehon
+ */
+public class WaterFilter extends Filter {
+
+    private Pass reflectionPass;
+    protected Spatial reflectionScene;
+    protected ViewPort reflectionView;
+    private Texture2D normalTexture;
+    private Texture2D foamTexture;
+    private Texture2D causticsTexture;
+    private Texture2D heightTexture;
+    private Plane plane;
+    private Camera reflectionCam;
+    protected Ray ray = new Ray();
+    private Vector3f targetLocation = new Vector3f();
+    private ReflectionProcessor reflectionProcessor;
+    private Matrix4f biasMatrix = new Matrix4f(0.5f, 0.0f, 0.0f, 0.5f,
+            0.0f, 0.5f, 0.0f, 0.5f,
+            0.0f, 0.0f, 0.0f, 0.5f,
+            0.0f, 0.0f, 0.0f, 1.0f);
+    private Matrix4f textureProjMatrix = new Matrix4f();
+    private boolean underWater;
+    private RenderManager renderManager;
+    private ViewPort viewPort;
+    private float time = 0;
+    //properties
+    private float speed = 1;
+    private Vector3f lightDirection = new Vector3f(0, -1, 0);
+    private ColorRGBA lightColor = ColorRGBA.White;
+    private float waterHeight = 0.0f;
+    private ColorRGBA waterColor = new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f);
+    private ColorRGBA deepWaterColor = new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f);
+    private Vector3f colorExtinction = new Vector3f(5.0f, 20.0f, 30.0f);
+    private float waterTransparency = 0.1f;
+    private float maxAmplitude = 1.5f;
+    private float shoreHardness = 0.1f;
+    private boolean useFoam = true;
+    private float foamIntensity = 0.5f;
+    private float foamHardness = 1.0f;
+    private Vector3f foamExistence = new Vector3f(0.45f, 4.35f, 1.5f);
+    private float waveScale = 0.005f;
+    private float sunScale = 3.0f;
+    private float shininess = 0.7f;
+    private Vector2f windDirection = new Vector2f(0.0f, -1.0f);
+    private int reflectionMapSize = 512;
+    private boolean useRipples = true;
+    private float normalScale = 3.0f;
+    private boolean useHQShoreline = true;
+    private boolean useSpecular = true;
+    private boolean useRefraction = true;
+    private float refractionStrength = 0.0f;
+    private float refractionConstant = 0.5f;
+    private float reflectionDisplace = 30;
+    private float underWaterFogDistance = 120;
+    private boolean useCaustics = true;
+    private float causticsIntensity = 0.5f;
+
+    /**
+     * Create a Water Filter
+     */
+    public WaterFilter() {
+        super("WaterFilter");
+    }
+
+    public WaterFilter(Node reflectionScene, Vector3f lightDirection) {
+        super("WaterFilter");
+        this.reflectionScene = reflectionScene;
+        this.lightDirection = lightDirection;
+    }
+
+    @Override
+    protected boolean isRequiresDepthTexture() {
+        return true;
+    }
+
+    @Override
+    protected void preFrame(float tpf) {
+        time = time + (tpf * speed);
+        material.setFloat("Time", time);
+        Camera sceneCam = viewPort.getCamera();
+        biasMatrix.mult(sceneCam.getViewProjectionMatrix(), textureProjMatrix);
+        material.setMatrix4("TextureProjMatrix", textureProjMatrix);
+        material.setVector3("CameraPosition", sceneCam.getLocation());
+        material.setMatrix4("ViewProjectionMatrixInverse", sceneCam.getViewProjectionMatrix().invert());
+
+        material.setFloat("WaterHeight", waterHeight);
+
+        //update reflection cam
+        ray.setOrigin(sceneCam.getLocation());
+        ray.setDirection(sceneCam.getDirection());
+        plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y));
+        reflectionProcessor.setReflectionClipPlane(plane);
+        boolean inv = false;
+        if (!ray.intersectsWherePlane(plane, targetLocation)) {
+            ray.setDirection(ray.getDirection().negateLocal());
+            ray.intersectsWherePlane(plane, targetLocation);
+            inv = true;
+        }
+        Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
+        reflectionCam.setLocation(loc);
+        reflectionCam.setFrustum(sceneCam.getFrustumNear(),
+                sceneCam.getFrustumFar(),
+                sceneCam.getFrustumLeft(),
+                sceneCam.getFrustumRight(),
+                sceneCam.getFrustumTop(),
+                sceneCam.getFrustumBottom());
+        TempVars vars = TempVars.get();
+
+
+        vars.vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
+        float planeDistance = plane.pseudoDistance(vars.vect1);
+        vars.vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
+        vars.vect3.set(vars.vect1.subtractLocal(vars.vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
+
+        reflectionCam.lookAt(targetLocation, vars.vect3);
+        vars.release();
+
+        if (inv) {
+            reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
+        }
+
+        //if we're under water no need to compute reflection
+        if (sceneCam.getLocation().y >= waterHeight) {
+            boolean rtb = true;
+            if (!renderManager.isHandleTranslucentBucket()) {
+                renderManager.setHandleTranslucentBucket(true);
+                rtb = false;
+            }
+            renderManager.renderViewPort(reflectionView, tpf);
+            if (!rtb) {
+                renderManager.setHandleTranslucentBucket(false);
+            }
+            renderManager.setCamera(sceneCam, false);
+            renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+
+
+            underWater = false;
+        } else {
+            underWater = true;
+        }
+    }
+
+    @Override
+    protected Material getMaterial() {
+        return material;
+    }
+
+    private DirectionalLight findLight(Node node) {
+        for (Light light : node.getWorldLightList()) {    
+            if (light instanceof DirectionalLight) {
+                return (DirectionalLight) light;
+            }
+        }
+        for (Spatial child : node.getChildren()) {
+            if (child instanceof Node) {
+                return findLight((Node) child);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+
+        if (reflectionScene == null) {
+            reflectionScene = vp.getScenes().get(0);
+            DirectionalLight l = findLight((Node) reflectionScene);
+            if (l != null) {
+                lightDirection = l.getDirection();
+            }
+
+        }
+
+        this.renderManager = renderManager;
+        this.viewPort = vp;
+        reflectionPass = new Pass();
+        reflectionPass.init(renderManager.getRenderer(), reflectionMapSize, reflectionMapSize, Format.RGBA8, Format.Depth);
+        reflectionCam = new Camera(reflectionMapSize, reflectionMapSize);
+        reflectionView = new ViewPort("reflectionView", reflectionCam);
+        reflectionView.setClearFlags(true, true, true);
+        reflectionView.attachScene(reflectionScene);
+        reflectionView.setOutputFrameBuffer(reflectionPass.getRenderFrameBuffer());
+        plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y));
+        reflectionProcessor = new ReflectionProcessor(reflectionCam, reflectionPass.getRenderFrameBuffer(), plane);
+        reflectionView.addProcessor(reflectionProcessor);
+
+        normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds");
+        if (foamTexture == null) {
+            foamTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg");
+        }
+        if (causticsTexture == null) {
+            causticsTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/caustics.jpg");
+        }
+        heightTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/heightmap.jpg");
+
+        normalTexture.setWrap(WrapMode.Repeat);
+        foamTexture.setWrap(WrapMode.Repeat);
+        causticsTexture.setWrap(WrapMode.Repeat);
+        heightTexture.setWrap(WrapMode.Repeat);
+
+        material = new Material(manager, "Common/MatDefs/Water/Water.j3md");
+        material.setTexture("HeightMap", heightTexture);
+        material.setTexture("CausticsMap", causticsTexture);
+        material.setTexture("FoamMap", foamTexture);
+        material.setTexture("NormalMap", normalTexture);
+        material.setTexture("ReflectionMap", reflectionPass.getRenderedTexture());
+
+        material.setFloat("WaterTransparency", waterTransparency);
+        material.setFloat("NormalScale", normalScale);
+        material.setFloat("R0", refractionConstant);
+        material.setFloat("MaxAmplitude", maxAmplitude);
+        material.setVector3("LightDir", lightDirection);
+        material.setColor("LightColor", lightColor);
+        material.setFloat("ShoreHardness", shoreHardness);
+        material.setFloat("RefractionStrength", refractionStrength);
+        material.setFloat("WaveScale", waveScale);
+        material.setVector3("FoamExistence", foamExistence);
+        material.setFloat("SunScale", sunScale);
+        material.setVector3("ColorExtinction", colorExtinction);
+        material.setFloat("Shininess", shininess);
+        material.setColor("WaterColor", waterColor);
+        material.setColor("DeepWaterColor", deepWaterColor);
+        material.setVector2("WindDirection", windDirection);
+        material.setFloat("FoamHardness", foamHardness);
+        material.setBoolean("UseRipples", useRipples);
+        material.setBoolean("UseHQShoreline", useHQShoreline);
+        material.setBoolean("UseSpecular", useSpecular);
+        material.setBoolean("UseFoam", useFoam);
+        material.setBoolean("UseCaustics", useCaustics);
+        material.setBoolean("UseRefraction", useRefraction);
+        material.setFloat("ReflectionDisplace", reflectionDisplace);
+        material.setFloat("FoamIntensity", foamIntensity);
+        material.setFloat("UnderWaterFogDistance", underWaterFogDistance);
+        material.setFloat("CausticsIntensity", causticsIntensity);
+
+
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+
+        oc.write(speed, "speed", 1f);
+        oc.write(lightDirection, "lightDirection", new Vector3f(0, -1, 0));
+        oc.write(lightColor, "lightColor", ColorRGBA.White);
+        oc.write(waterHeight, "waterHeight", 0.0f);
+        oc.write(waterColor, "waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f));
+        oc.write(deepWaterColor, "deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f));
+
+        oc.write(colorExtinction, "colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f));
+        oc.write(waterTransparency, "waterTransparency", 0.1f);
+        oc.write(maxAmplitude, "maxAmplitude", 1.5f);
+        oc.write(shoreHardness, "shoreHardness", 0.1f);
+        oc.write(useFoam, "useFoam", true);
+
+        oc.write(foamIntensity, "foamIntensity", 0.5f);
+        oc.write(foamHardness, "foamHardness", 1.0f);
+
+        oc.write(foamExistence, "foamExistence", new Vector3f(0.45f, 4.35f, 1.5f));
+        oc.write(waveScale, "waveScale", 0.005f);
+
+        oc.write(sunScale, "sunScale", 3.0f);
+        oc.write(shininess, "shininess", 0.7f);
+        oc.write(windDirection, "windDirection", new Vector2f(0.0f, -1.0f));
+        oc.write(reflectionMapSize, "reflectionMapSize", 512);
+        oc.write(useRipples, "useRipples", true);
+
+        oc.write(normalScale, "normalScale", 3.0f);
+        oc.write(useHQShoreline, "useHQShoreline", true);
+
+        oc.write(useSpecular, "useSpecular", true);
+
+        oc.write(useRefraction, "useRefraction", true);
+        oc.write(refractionStrength, "refractionStrength", 0.0f);
+        oc.write(refractionConstant, "refractionConstant", 0.5f);
+        oc.write(reflectionDisplace, "reflectionDisplace", 30f);
+        oc.write(underWaterFogDistance, "underWaterFogDistance", 120f);
+        oc.write(causticsIntensity, "causticsIntensity", 0.5f);
+
+        oc.write(useCaustics, "useCaustics", true);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        speed = ic.readFloat("speed", 1f);
+        lightDirection = (Vector3f) ic.readSavable("lightDirection", new Vector3f(0, -1, 0));
+        lightColor = (ColorRGBA) ic.readSavable("lightColor", ColorRGBA.White);
+        waterHeight = ic.readFloat("waterHeight", 0.0f);
+        waterColor = (ColorRGBA) ic.readSavable("waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f));
+        deepWaterColor = (ColorRGBA) ic.readSavable("deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f));
+
+        colorExtinction = (Vector3f) ic.readSavable("colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f));
+        waterTransparency = ic.readFloat("waterTransparency", 0.1f);
+        maxAmplitude = ic.readFloat("maxAmplitude", 1.5f);
+        shoreHardness = ic.readFloat("shoreHardness", 0.1f);
+        useFoam = ic.readBoolean("useFoam", true);
+
+        foamIntensity = ic.readFloat("foamIntensity", 0.5f);
+        foamHardness = ic.readFloat("foamHardness", 1.0f);
+
+        foamExistence = (Vector3f) ic.readSavable("foamExistence", new Vector3f(0.45f, 4.35f, 1.5f));
+        waveScale = ic.readFloat("waveScale", 0.005f);
+
+        sunScale = ic.readFloat("sunScale", 3.0f);
+        shininess = ic.readFloat("shininess", 0.7f);
+        windDirection = (Vector2f) ic.readSavable("windDirection", new Vector2f(0.0f, -1.0f));
+        reflectionMapSize = ic.readInt("reflectionMapSize", 512);
+        useRipples = ic.readBoolean("useRipples", true);
+
+        normalScale = ic.readFloat("normalScale", 3.0f);
+        useHQShoreline = ic.readBoolean("useHQShoreline", true);
+
+        useSpecular = ic.readBoolean("useSpecular", true);
+
+        useRefraction = ic.readBoolean("useRefraction", true);
+        refractionStrength = ic.readFloat("refractionStrength", 0.0f);
+        refractionConstant = ic.readFloat("refractionConstant", 0.5f);
+        reflectionDisplace = ic.readFloat("reflectionDisplace", 30f);
+        underWaterFogDistance = ic.readFloat("underWaterFogDistance", 120f);
+        causticsIntensity = ic.readFloat("causticsIntensity", 0.5f);
+
+        useCaustics = ic.readBoolean("useCaustics", true);
+
+    }
+
+    /**
+     * gets the height of the water plane
+     * @return
+     */
+    public float getWaterHeight() {
+        return waterHeight;
+    }
+
+    /**
+     * Sets the height of the water plane
+     * default is 0.0
+     * @param waterHeight
+     */
+    public void setWaterHeight(float waterHeight) {
+        this.waterHeight = waterHeight;
+    }
+
+    /**
+     * sets the scene to render in the reflection map
+     * @param reflectionScene 
+     */
+    public void setReflectionScene(Spatial reflectionScene) {
+        this.reflectionScene = reflectionScene;
+    }
+
+    /**
+     * returns the waterTransparency value
+     * @return
+     */
+    public float getWaterTransparency() {
+        return waterTransparency;
+    }
+
+    /**
+     * Sets how fast will colours fade out. You can also think about this
+     * values as how clear water is. Therefore use smaller values (eg. 0.05)
+     * to have crystal clear water and bigger to achieve "muddy" water.
+     * default is 0.1f
+     * @param waterTransparency
+     */
+    public void setWaterTransparency(float waterTransparency) {
+        this.waterTransparency = waterTransparency;
+        if (material != null) {
+            material.setFloat("WaterTransparency", waterTransparency);
+        }
+    }
+
+    /**
+     * Returns the normal scales applied to the normal map
+     * @return
+     */
+    public float getNormalScale() {
+        return normalScale;
+    }
+
+    /**
+     * Sets the normal scaling factors to apply to the normal map.
+     * the higher the value the more small ripples will be visible on the waves.
+     * default is 1.0
+     * @param normalScale
+     */
+    public void setNormalScale(float normalScale) {
+        this.normalScale = normalScale;
+        if (material != null) {
+            material.setFloat("NormalScale", normalScale);
+        }
+    }
+
+    /**
+     * returns the refractoin constant
+     * @return 
+     */
+    public float getRefractionConstant() {
+        return refractionConstant;
+    }
+
+    /**
+     * This is a constant related to the index of refraction (IOR) used to compute the fresnel term.
+     * F = R0 + (1-R0)( 1 - N.V)^5
+     * where F is the fresnel term, R0 the constant, N the normal vector and V tne view vector.
+     * It usually depend on the material you are lookinh through (here water).
+     * Default value is 0.3f
+     * In practice, the lowest the value and the less the reflection can be seen on water
+     * @param refractionConstant
+     */
+    public void setRefractionConstant(float refractionConstant) {
+        this.refractionConstant = refractionConstant;
+        if (material != null) {
+            material.setFloat("R0", refractionConstant);
+        }
+    }
+
+    /**
+     * return the maximum wave amplitude
+     * @return 
+     */
+    public float getMaxAmplitude() {
+        return maxAmplitude;
+    }
+
+    /**
+     * Sets the maximum waves amplitude
+     * default is 1.0
+     * @param maxAmplitude
+     */
+    public void setMaxAmplitude(float maxAmplitude) {
+        this.maxAmplitude = maxAmplitude;
+        if (material != null) {
+            material.setFloat("MaxAmplitude", maxAmplitude);
+        }
+    }
+
+    /**
+     * gets the light direction
+     * @return
+     */
+    public Vector3f getLightDirection() {
+        return lightDirection;
+    }
+
+    /**
+     * Sets the light direction
+     * @param lightDirection
+     */
+    public void setLightDirection(Vector3f lightDirection) {
+        this.lightDirection = lightDirection;
+        if (material != null) {
+            material.setVector3("LightDir", lightDirection);
+        }
+    }
+
+    /**
+     * returns the light color
+     * @return
+     */
+    public ColorRGBA getLightColor() {
+        return lightColor;
+    }
+
+    /**
+     * Sets the light color to use
+     * default is white
+     * @param lightColor
+     */
+    public void setLightColor(ColorRGBA lightColor) {
+        this.lightColor = lightColor;
+        if (material != null) {
+            material.setColor("LightColor", lightColor);
+        }
+    }
+
+    /**
+     * Return the shoreHardeness
+     * @return
+     */
+    public float getShoreHardness() {
+        return shoreHardness;
+    }
+
+    /**
+     * The smaller this value is, the softer the transition between
+     * shore and water. If you want hard edges use very big value.
+     * Default is 0.1f.
+     * @param shoreHardness
+     */
+    public void setShoreHardness(float shoreHardness) {
+        this.shoreHardness = shoreHardness;
+        if (material != null) {
+            material.setFloat("ShoreHardness", shoreHardness);
+        }
+    }
+
+    /**
+     * returns the foam hardness
+     * @return
+     */
+    public float getFoamHardness() {
+        return foamHardness;
+    }
+
+    /**
+     * Sets the foam hardness : How much the foam will blend with the shore to avoid hard edged water plane.
+     * Default is 1.0
+     * @param foamHardness
+     */
+    public void setFoamHardness(float foamHardness) {
+        this.foamHardness = foamHardness;
+        if (material != null) {
+            material.setFloat("FoamHardness", foamHardness);
+        }
+    }
+
+    /**
+     * returns the refractionStrenght
+     * @return
+     */
+    public float getRefractionStrength() {
+        return refractionStrength;
+    }
+
+    /**
+     * This value modifies current fresnel term. If you want to weaken
+     * reflections use bigger value. If you want to empasize them use
+     * value smaller then 0. Default is 0.0f.
+     * @param refractionStrength
+     */
+    public void setRefractionStrength(float refractionStrength) {
+        this.refractionStrength = refractionStrength;
+        if (material != null) {
+            material.setFloat("RefractionStrength", refractionStrength);
+        }
+    }
+
+    /**
+     * returns the scale factor of the waves height map
+     * @return
+     */
+    public float getWaveScale() {
+        return waveScale;
+    }
+
+    /**
+     * Sets the scale factor of the waves height map
+     * the smaller the value the bigger the waves
+     * default is 0.005f
+     * @param waveScale
+     */
+    public void setWaveScale(float waveScale) {
+        this.waveScale = waveScale;
+        if (material != null) {
+            material.setFloat("WaveScale", waveScale);
+        }
+    }
+
+    /**
+     * returns the foam existance vector
+     * @return
+     */
+    public Vector3f getFoamExistence() {
+        return foamExistence;
+    }
+
+    /**
+     * Describes at what depth foam starts to fade out and
+     * at what it is completely invisible. The third value is at
+     * what height foam for waves appear (+ waterHeight).
+     * default is (0.45, 4.35, 1.0);
+     * @param foamExistence
+     */
+    public void setFoamExistence(Vector3f foamExistence) {
+        this.foamExistence = foamExistence;
+        if (material != null) {
+            material.setVector3("FoamExistence", foamExistence);
+        }
+    }
+
+    /**
+     * gets the scale of the sun
+     * @return
+     */
+    public float getSunScale() {
+        return sunScale;
+    }
+
+    /**
+     * Sets the scale of the sun for specular effect
+     * @param sunScale
+     */
+    public void setSunScale(float sunScale) {
+        this.sunScale = sunScale;
+        if (material != null) {
+            material.setFloat("SunScale", sunScale);
+        }
+    }
+
+    /**
+     * Returns the color exctinction vector of the water
+     * @return
+     */
+    public Vector3f getColorExtinction() {
+        return colorExtinction;
+    }
+
+    /**
+     * Return at what depth the refraction color extinct
+     * the first value is for red
+     * the second is for green
+     * the third is for blue
+     * Play with thos parameters to "trouble" the water
+     * default is (5.0, 20.0, 30.0f);
+     * @param colorExtinction
+     */
+    public void setColorExtinction(Vector3f colorExtinction) {
+        this.colorExtinction = colorExtinction;
+        if (material != null) {
+            material.setVector3("ColorExtinction", colorExtinction);
+        }
+    }
+
+    /**
+     * Sets the foam texture
+     * @param foamTexture
+     */
+    public void setFoamTexture(Texture2D foamTexture) {
+        this.foamTexture = foamTexture;
+        foamTexture.setWrap(WrapMode.Repeat);
+        if (material != null) {
+            material.setTexture("FoamMap", foamTexture);
+        }
+    }
+
+    /**
+     * Sets the height texture
+     * @param heightTexture
+     */
+    public void setHeightTexture(Texture2D heightTexture) {
+        this.heightTexture = heightTexture;
+        heightTexture.setWrap(WrapMode.Repeat);
+    }
+
+    /**
+     * Sets the normal Texture
+     * @param normalTexture
+     */
+    public void setNormalTexture(Texture2D normalTexture) {
+        this.normalTexture = normalTexture;
+        normalTexture.setWrap(WrapMode.Repeat);
+    }
+
+    /**
+     * return the shininess factor of the water
+     * @return
+     */
+    public float getShininess() {
+        return shininess;
+    }
+
+    /**
+     * Sets the shinines factor of the water
+     * default is 0.7f
+     * @param shininess
+     */
+    public void setShininess(float shininess) {
+        this.shininess = shininess;
+        if (material != null) {
+            material.setFloat("Shininess", shininess);
+        }
+    }
+
+    /**
+     * retruns the speed of the waves
+     * @return
+     */
+    public float getSpeed() {
+        return speed;
+    }
+
+    /**
+     * Set the speed of the waves (0.0 is still) default is 1.0
+     * @param speed
+     */
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * returns the color of the water
+     *
+     * @return
+     */
+    public ColorRGBA getWaterColor() {
+        return waterColor;
+    }
+
+    /**
+     * Sets the color of the water
+     * see setDeepWaterColor for deep water color
+     * default is (0.0078f, 0.5176f, 0.5f,1.0f) (greenish blue)
+     * @param waterColor
+     */
+    public void setWaterColor(ColorRGBA waterColor) {
+        this.waterColor = waterColor;
+        if (material != null) {
+            material.setColor("WaterColor", waterColor);
+        }
+    }
+
+    /**
+     * returns the deep water color
+     * @return
+     */
+    public ColorRGBA getDeepWaterColor() {
+        return deepWaterColor;
+    }
+
+    /**
+     * sets the deep water color
+     * see setWaterColor for general color
+     * default is (0.0039f, 0.00196f, 0.145f,1.0f) (very dark blue)
+     * @param deepWaterColor
+     */
+    public void setDeepWaterColor(ColorRGBA deepWaterColor) {
+        this.deepWaterColor = deepWaterColor;
+        if (material != null) {
+            material.setColor("DeepWaterColor", deepWaterColor);
+        }
+    }
+
+    /**
+     * returns the wind direction
+     * @return
+     */
+    public Vector2f getWindDirection() {
+        return windDirection;
+    }
+
+    /**
+     * sets the wind direction
+     * the direction where the waves move
+     * default is (0.0f, -1.0f)
+     * @param windDirection
+     */
+    public void setWindDirection(Vector2f windDirection) {
+        this.windDirection = windDirection;
+        if (material != null) {
+            material.setVector2("WindDirection", windDirection);
+        }
+    }
+
+    /**
+     * returns the size of the reflection map
+     * @return
+     */
+    public int getReflectionMapSize() {
+        return reflectionMapSize;
+    }
+
+    /**
+     * Sets the size of the reflection map
+     * default is 512, the higher, the better quality, but the slower the effect.
+     * @param reflectionMapSize
+     */
+    public void setReflectionMapSize(int reflectionMapSize) {
+        this.reflectionMapSize = reflectionMapSize;
+    }
+
+    /**
+     * returns true if the water uses foam
+     * @return
+     */
+    public boolean isUseFoam() {
+        return useFoam;
+    }
+
+    /**
+     * set to true to use foam with water
+     * default true
+     * @param useFoam
+     */
+    public void setUseFoam(boolean useFoam) {
+        this.useFoam = useFoam;
+        if (material != null) {
+            material.setBoolean("UseFoam", useFoam);
+        }
+
+    }
+
+    /**
+     * sets the texture to use to render caustics on the ground underwater
+     * @param causticsTexture 
+     */
+    public void setCausticsTexture(Texture2D causticsTexture) {
+        this.causticsTexture = causticsTexture;
+        if (material != null) {
+            material.setTexture("causticsMap", causticsTexture);
+        }
+    }
+
+    /**
+     * returns true if caustics are rendered
+     * @return 
+     */
+    public boolean isUseCaustics() {
+        return useCaustics;
+    }
+
+    /**
+     * set to true if you want caustics to be rendered on the ground underwater, false otherwise
+     * @param useCaustics 
+     */
+    public void setUseCaustics(boolean useCaustics) {
+        this.useCaustics = useCaustics;
+        if (material != null) {
+            material.setBoolean("UseCaustics", useCaustics);
+        }
+    }
+
+    /**
+     * return true 
+     * @return
+     */
+    public boolean isUseHQShoreline() {
+        return useHQShoreline;
+    }
+
+    public void setUseHQShoreline(boolean useHQShoreline) {
+        this.useHQShoreline = useHQShoreline;
+        if (material != null) {
+            material.setBoolean("UseHQShoreline", useHQShoreline);
+        }
+
+    }
+
+    /**
+     * returns true if the water use the refraction
+     * @return 
+     */
+    public boolean isUseRefraction() {
+        return useRefraction;
+    }
+
+    /**
+     * set to true to use refraction (default is true)
+     * @param useRefraction 
+     */
+    public void setUseRefraction(boolean useRefraction) {
+        this.useRefraction = useRefraction;
+        if (material != null) {
+            material.setBoolean("UseRefraction", useRefraction);
+        }
+
+    }
+
+    /**
+     * returns true if the ater use ripples
+     * @return 
+     */
+    public boolean isUseRipples() {
+        return useRipples;
+    }
+
+    /**
+     * 
+     * Set to true tu use ripples
+     * @param useRipples 
+     */
+    public void setUseRipples(boolean useRipples) {
+        this.useRipples = useRipples;
+        if (material != null) {
+            material.setBoolean("UseRipples", useRipples);
+        }
+
+    }
+
+    /**
+     * returns true if the water use specular
+     * @return 
+     */
+    public boolean isUseSpecular() {
+        return useSpecular;
+    }
+
+    /**
+     * Set to true to use specular lightings on the water
+     * @param useSpecular 
+     */
+    public void setUseSpecular(boolean useSpecular) {
+        this.useSpecular = useSpecular;
+        if (material != null) {
+            material.setBoolean("UseSpecular", useSpecular);
+        }
+    }
+
+    /**
+     * returns the foam intensity
+     * @return 
+     */
+    public float getFoamIntensity() {
+        return foamIntensity;
+    }
+
+    /**
+     * sets the foam intensity default is 0.5f
+     * @param foamIntensity 
+     */
+    public void setFoamIntensity(float foamIntensity) {
+        this.foamIntensity = foamIntensity;
+        if (material != null) {
+            material.setFloat("FoamIntensity", foamIntensity);
+
+        }
+    }
+
+    /**
+     * returns the reflection displace
+     * see {@link setReflectionDisplace(float reflectionDisplace)}
+     * @return 
+     */
+    public float getReflectionDisplace() {
+        return reflectionDisplace;
+    }
+
+    /**
+     * Sets the reflection displace. define how troubled will look the reflection in the water. default is 30
+     * @param reflectionDisplace 
+     */
+    public void setReflectionDisplace(float reflectionDisplace) {
+        this.reflectionDisplace = reflectionDisplace;
+        if (material != null) {
+            material.setFloat("m_ReflectionDisplace", reflectionDisplace);
+        }
+    }
+
+    /**
+     * returns true if the camera is under the water level
+     * @return 
+     */
+    public boolean isUnderWater() {
+        return underWater;
+    }
+
+    /**
+     * returns the distance of the fog when under water
+     * @return 
+     */
+    public float getUnderWaterFogDistance() {
+        return underWaterFogDistance;
+    }
+
+    /**
+     * sets the distance of the fog when under water.
+     * default is 120 (120 world units) use a high value to raise the view range under water
+     * @param underWaterFogDistance 
+     */
+    public void setUnderWaterFogDistance(float underWaterFogDistance) {
+        this.underWaterFogDistance = underWaterFogDistance;
+        if (material != null) {
+            material.setFloat("UnderWaterFogDistance", underWaterFogDistance);
+        }
+    }
+
+    /**
+     * get the intensity of caustics under water
+     * @return 
+     */
+    public float getCausticsIntensity() {
+        return causticsIntensity;
+    }
+
+    /**
+     * sets the intensity of caustics under water. goes from 0 to 1, default is 0.5f
+     * @param causticsIntensity 
+     */
+    public void setCausticsIntensity(float causticsIntensity) {
+        this.causticsIntensity = causticsIntensity;
+        if (material != null) {
+            material.setFloat("CausticsIntensity", causticsIntensity);
+        }
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/ClasspathLocator.java b/engine/src/core-plugins/com/jme3/asset/plugins/ClasspathLocator.java
new file mode 100644
index 0000000..f3570de
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/ClasspathLocator.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.system.JmeSystem;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.logging.Logger;
+
+/**
+ * The <code>ClasspathLocator</code> looks up an asset in the classpath.
+ * @author Kirill Vainer
+ */
+public class ClasspathLocator implements AssetLocator {
+
+    private static final Logger logger = Logger.getLogger(ClasspathLocator.class.getName());
+    private String root = "";
+
+    public ClasspathLocator(){
+    }
+
+    public void setRootPath(String rootPath) {
+        this.root = rootPath;
+        if (root.equals("/"))
+            root = "";
+        else if (root.length() > 1){
+            if (root.startsWith("/")){
+                root = root.substring(1);
+            }
+            if (!root.endsWith("/"))
+                root += "/";
+        }
+    }
+    
+    public AssetInfo locate(AssetManager manager, AssetKey key) {
+        URL url;
+        String name = key.getName();
+        if (name.startsWith("/"))
+            name = name.substring(1);
+
+        name = root + name;
+//        if (!name.startsWith(root)){
+//            name = root + name;
+//        }
+
+        if (JmeSystem.isLowPermissions()){
+            url = ClasspathLocator.class.getResource("/" + name);
+        }else{
+            url = Thread.currentThread().getContextClassLoader().getResource(name);
+        }
+        if (url == null)
+            return null;
+        
+        if (url.getProtocol().equals("file")){
+            try {
+                String path = new File(url.toURI()).getCanonicalPath();
+                
+                // convert to / for windows
+                if (File.separatorChar == '\\'){
+                    path = path.replace('\\', '/');
+                }
+                
+                // compare path
+                if (!path.endsWith(name)){
+                    throw new AssetNotFoundException("Asset name doesn't match requirements.\n"+
+                                                     "\"" + path + "\" doesn't match \"" + name + "\"");
+                }
+            } catch (URISyntaxException ex) {
+                throw new AssetLoadException("Error converting URL to URI", ex);
+            } catch (IOException ex){
+                throw new AssetLoadException("Failed to get canonical path for " + url, ex);
+            }
+        }
+        
+        try{
+            return UrlAssetInfo.create(manager, key, url);
+        }catch (IOException ex){
+            // This is different handling than URL locator
+            // since classpath locating would return null at the getResource() 
+            // call, otherwise there's a more critical error...
+            throw new AssetLoadException("Failed to read URL " + url, ex);
+        }
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/FileLocator.java b/engine/src/core-plugins/com/jme3/asset/plugins/FileLocator.java
new file mode 100644
index 0000000..f448fa3
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/FileLocator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.*;
+import java.io.*;
+
+/**
+ * <code>FileLocator</code> allows you to specify a folder where to
+ * look for assets. 
+ * @author Kirill Vainer
+ */
+public class FileLocator implements AssetLocator {
+
+    private File root;
+
+    public void setRootPath(String rootPath) {
+        if (rootPath == null)
+            throw new NullPointerException();
+        
+        try {
+            root = new File(rootPath).getCanonicalFile();
+            if (!root.isDirectory()){
+                throw new IllegalArgumentException("Given root path \"" + root + "\" not a directory");
+            }
+        } catch (IOException ex) {
+            throw new AssetLoadException("Root path is invalid", ex);
+        }
+    }
+
+    private static class AssetInfoFile extends AssetInfo {
+
+        private File file;
+
+        public AssetInfoFile(AssetManager manager, AssetKey key, File file){
+            super(manager, key);
+            this.file = file;
+        }
+
+        @Override
+        public InputStream openStream() {
+            try{
+                return new FileInputStream(file);
+            }catch (FileNotFoundException ex){
+                // NOTE: Can still happen even if file.exists() is true, e.g.
+                // permissions issue and similar
+                throw new AssetLoadException("Failed to open file: " + file, ex);
+            }
+        }
+    }
+
+    public AssetInfo locate(AssetManager manager, AssetKey key) {
+        String name = key.getName();
+        File file = new File(root, name);
+        if (file.exists() && file.isFile()){
+            try {
+                // Now, check asset name requirements
+                String canonical = file.getCanonicalPath();
+                String absolute = file.getAbsolutePath();
+                if (!canonical.endsWith(absolute)){
+                    throw new AssetNotFoundException("Asset name doesn't match requirements.\n"+
+                                                     "\"" + canonical + "\" doesn't match \"" + absolute + "\"");
+                }
+            } catch (IOException ex) {
+                throw new AssetLoadException("Failed to get file canonical path " + file, ex);
+            }
+            
+            return new AssetInfoFile(manager, key, file);
+        }else{
+            return null;
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java b/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java
new file mode 100644
index 0000000..0637221
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLocator;
+import com.jme3.asset.AssetManager;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+
+public class HttpZipLocator implements AssetLocator {
+
+    private static final Logger logger = Logger.getLogger(HttpZipLocator.class.getName());
+
+    private URL zipUrl;
+    private String rootPath = "";
+    private int numEntries;
+    private int tableOffset;
+    private int tableLength;
+    private HashMap<String, ZipEntry2> entries;
+    
+    private static final ByteBuffer byteBuf = ByteBuffer.allocate(250);
+    private static final CharBuffer charBuf = CharBuffer.allocate(250);
+    private static final CharsetDecoder utf8Decoder;
+    
+    public static final long LOCSIG = 0x4034b50, EXTSIG = 0x8074b50,
+      CENSIG = 0x2014b50, ENDSIG = 0x6054b50;
+
+    public static final int LOCHDR = 30, EXTHDR = 16, CENHDR = 46, ENDHDR = 22,
+      LOCVER = 4, LOCFLG = 6, LOCHOW = 8, LOCTIM = 10, LOCCRC = 14,
+      LOCSIZ = 18, LOCLEN = 22, LOCNAM = 26, LOCEXT = 28, EXTCRC = 4,
+      EXTSIZ = 8, EXTLEN = 12, CENVEM = 4, CENVER = 6, CENFLG = 8,
+      CENHOW = 10, CENTIM = 12, CENCRC = 16, CENSIZ = 20, CENLEN = 24,
+      CENNAM = 28, CENEXT = 30, CENCOM = 32, CENDSK = 34, CENATT = 36,
+      CENATX = 38, CENOFF = 42, ENDSUB = 8, ENDTOT = 10, ENDSIZ = 12,
+      ENDOFF = 16, ENDCOM = 20;
+
+    static {
+        Charset utf8 = Charset.forName("UTF-8");
+        utf8Decoder = utf8.newDecoder();
+    }
+
+    private static class ZipEntry2 {
+        String name;
+        int length;
+        int offset;
+        int compSize;
+        long crc;
+        boolean deflate;
+
+        @Override
+        public String toString(){
+            return "ZipEntry[name=" + name +
+                         ",  length=" + length +
+                         ",  compSize=" + compSize +
+                         ",  offset=" + offset + "]";
+        }
+    }
+
+    private static int get16(byte[] b, int off) {
+	return  (b[off++] & 0xff) |
+               ((b[off]   & 0xff) << 8);
+    }
+
+    private static int get32(byte[] b, int off) {
+	return  (b[off++] & 0xff) |
+               ((b[off++] & 0xff) << 8) |
+               ((b[off++] & 0xff) << 16) |
+               ((b[off] & 0xff) << 24);
+    }
+
+    private static long getu32(byte[] b, int off) throws IOException{
+        return (b[off++]&0xff) |
+              ((b[off++]&0xff) << 8) |
+              ((b[off++]&0xff) << 16) |
+             (((long)(b[off]&0xff)) << 24);
+    }
+
+    private static String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException {
+        StringBuilder sb = new StringBuilder();
+        
+        int read = 0;
+        while (read < len){
+            // Either read n remaining bytes in b or 250 if n is higher.
+            int toRead = Math.min(len - read, byteBuf.capacity());
+            
+            boolean endOfInput = toRead < byteBuf.capacity();
+            
+            // read 'toRead' bytes into byteBuf
+            byteBuf.put(b, off + read, toRead);
+            
+            // set limit to position and set position to 0
+            // so data can be decoded
+            byteBuf.flip();
+            
+            // decode data in byteBuf
+            CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); 
+            
+            // if the result is not an underflow its an error
+            // that cannot be handled.
+            // if the error is an underflow and its the end of input
+            // then the decoder expects more bytes but there are no more => error
+            if (!result.isUnderflow() || !endOfInput){
+                result.throwException();
+            }
+            
+            // flip the char buf to get the string just decoded
+            charBuf.flip();
+            
+            // append the decoded data into the StringBuilder
+            sb.append(charBuf.toString());
+            
+            // clear buffers for next use
+            byteBuf.clear();
+            charBuf.clear();
+            
+            read += toRead;
+        }
+        
+        return sb.toString();
+    }
+
+    private InputStream readData(int offset, int length) throws IOException{
+        HttpURLConnection conn = (HttpURLConnection) zipUrl.openConnection();
+        conn.setDoOutput(false);
+        conn.setUseCaches(false);
+        conn.setInstanceFollowRedirects(false);
+        String range = "-";
+        if (offset != Integer.MAX_VALUE){
+            range = offset + range;
+        }
+        if (length != Integer.MAX_VALUE){
+            if (offset != Integer.MAX_VALUE){
+                range = range + (offset + length - 1);
+            }else{
+                range = range + length;
+            }
+        }
+
+        conn.setRequestProperty("Range", "bytes=" + range);
+        conn.connect();
+        if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
+            return conn.getInputStream();
+        }else if (conn.getResponseCode() == HttpURLConnection.HTTP_OK){
+            throw new IOException("Your server does not support HTTP feature Content-Range. Please contact your server administrator.");
+        }else{
+            throw new IOException(conn.getResponseCode() + " " + conn.getResponseMessage());
+        }
+    }
+
+    private int readTableEntry(byte[] table, int offset) throws IOException{
+        if (get32(table, offset) != CENSIG){
+            throw new IOException("Central directory error, expected 'PK12'");
+        }
+
+        int nameLen = get16(table, offset + CENNAM);
+        int extraLen = get16(table, offset + CENEXT);
+        int commentLen = get16(table, offset + CENCOM);
+        int newOffset = offset + CENHDR + nameLen + extraLen + commentLen;
+
+        int flags = get16(table, offset + CENFLG);
+        if ((flags & 1) == 1){
+            // ignore this entry, it uses encryption
+            return newOffset;
+        }
+            
+        int method = get16(table, offset + CENHOW);
+        if (method != ZipEntry.DEFLATED && method != ZipEntry.STORED){
+            // ignore this entry, it uses unknown compression method
+            return newOffset;
+        }
+
+        String name = getUTF8String(table, offset + CENHDR, nameLen);
+        if (name.charAt(name.length()-1) == '/'){
+            // ignore this entry, it is directory node
+            // or it has no name (?)
+            return newOffset;
+        }
+
+        ZipEntry2 entry = new ZipEntry2();
+        entry.name     = name;
+        entry.deflate  = (method == ZipEntry.DEFLATED);
+        entry.crc      = getu32(table, offset + CENCRC);
+        entry.length   = get32(table, offset + CENLEN);
+        entry.compSize = get32(table, offset + CENSIZ);
+        entry.offset   = get32(table, offset + CENOFF);
+
+        // we want offset directly into file data ..
+        // move the offset forward to skip the LOC header
+        entry.offset += LOCHDR + nameLen + extraLen;
+
+        entries.put(entry.name, entry);
+        
+        return newOffset;
+    }
+
+    private void fillByteArray(byte[] array, InputStream source) throws IOException{
+        int total = 0;
+        int length = array.length;
+	while (total < length) {
+	    int read = source.read(array, total, length - total);
+            if (read < 0)
+                throw new IOException("Failed to read entire array");
+
+	    total += read;
+	}
+    }
+
+    private void readCentralDirectory() throws IOException{
+        InputStream in = readData(tableOffset, tableLength);
+        byte[] header = new byte[tableLength];
+
+        // Fix for "PK12 bug in town.zip": sometimes
+        // not entire byte array will be read with InputStream.read()
+        // (especially for big headers)
+        fillByteArray(header, in);
+
+//        in.read(header);
+        in.close();
+
+        entries = new HashMap<String, ZipEntry2>(numEntries);
+        int offset = 0;
+        for (int i = 0; i < numEntries; i++){
+            offset = readTableEntry(header, offset);
+        }
+    }
+
+    private void readEndHeader() throws IOException{
+
+//        InputStream in = readData(Integer.MAX_VALUE, ENDHDR);
+//        byte[] header = new byte[ENDHDR];
+//        fillByteArray(header, in);
+//        in.close();
+//
+//        if (get32(header, 0) != ENDSIG){
+//            throw new IOException("End header error, expected 'PK56'");
+//        }
+
+        // Fix for "PK56 bug in town.zip":
+        // If there's a zip comment inside the end header,
+        // PK56 won't appear in the -22 position relative to the end of the
+        // file!
+        // In that case, we have to search for it.
+        // Increase search space to 200 bytes
+
+        InputStream in = readData(Integer.MAX_VALUE, 200);
+        byte[] header = new byte[200];
+        fillByteArray(header, in);
+        in.close();
+
+        int offset = -1;
+        for (int i = 200 - 22; i >= 0; i--){
+            if (header[i] == (byte) (ENDSIG & 0xff)
+              && get32(header, i) == ENDSIG){
+                // found location
+                offset = i;
+                break;
+            }
+        }
+        if (offset == -1)
+            throw new IOException("Cannot find Zip End Header in file!");
+
+        numEntries  = get16(header, offset + ENDTOT);
+        tableLength = get32(header, offset + ENDSIZ);
+        tableOffset = get32(header, offset + ENDOFF);
+    }
+
+    public void load(URL url) throws IOException {
+        if (!url.getProtocol().equals("http"))
+            throw new UnsupportedOperationException();
+
+        zipUrl = url;
+        readEndHeader();
+        readCentralDirectory();
+    }
+
+    private InputStream openStream(ZipEntry2 entry) throws IOException{
+        InputStream in = readData(entry.offset, entry.compSize);
+        if (entry.deflate){
+            return new InflaterInputStream(in, new Inflater(true));
+        }
+        return in;
+    }
+
+    public InputStream openStream(String name) throws IOException{
+        ZipEntry2 entry = entries.get(name);
+        if (entry == null)
+            throw new RuntimeException("Entry not found: "+name);
+
+        return openStream(entry);
+    }
+
+    public void setRootPath(String path){
+        if (!rootPath.equals(path)){
+            rootPath = path;
+            try {
+                load(new URL(path));
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Failed to set root path "+path, ex);
+            }
+        }
+    }
+
+    public AssetInfo locate(AssetManager manager, AssetKey key){
+        final ZipEntry2 entry = entries.get(key.getName());
+        if (entry == null)
+            return null;
+
+        return new AssetInfo(manager, key){
+            @Override
+            public InputStream openStream() {
+                try {
+                    return HttpZipLocator.this.openStream(entry);
+                } catch (IOException ex) {
+                    logger.log(Level.WARNING, "Error retrieving "+entry.name, ex);
+                    return null;
+                }
+            }
+        };
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java~ b/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java~
new file mode 100644
index 0000000..f5fbfd1
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/HttpZipLocator.java~
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLocator;
+import com.jme3.asset.AssetManager;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+
+public class HttpZipLocator implements AssetLocator {
+
+    private static final Logger logger = Logger.getLogger(HttpZipLocator.class.getName());
+
+    private URL zipUrl;
+    private String rootPath = "";
+    private int numEntries;
+    private int tableOffset;
+    private int tableLength;
+    private HashMap<String, ZipEntry2> entries;
+    
+    private static final ByteBuffer byteBuf = ByteBuffer.allocate(250);
+    private static final CharBuffer charBuf = CharBuffer.allocate(250);
+    private static final CharsetDecoder utf8Decoder;
+    
+    static {
+        Charset utf8 = Charset.forName("UTF-8");
+        utf8Decoder = utf8.newDecoder();
+    }
+
+    private static class ZipEntry2 {
+        String name;
+        int length;
+        int offset;
+        int compSize;
+        long crc;
+        boolean deflate;
+
+        @Override
+        public String toString(){
+            return "ZipEntry[name=" + name +
+                         ",  length=" + length +
+                         ",  compSize=" + compSize +
+                         ",  offset=" + offset + "]";
+        }
+    }
+
+    private static int get16(byte[] b, int off) {
+	return  (b[off++] & 0xff) |
+               ((b[off]   & 0xff) << 8);
+    }
+
+    private static int get32(byte[] b, int off) {
+	return  (b[off++] & 0xff) |
+               ((b[off++] & 0xff) << 8) |
+               ((b[off++] & 0xff) << 16) |
+               ((b[off] & 0xff) << 24);
+    }
+
+    private static long getu32(byte[] b, int off) throws IOException{
+        return (b[off++]&0xff) |
+              ((b[off++]&0xff) << 8) |
+              ((b[off++]&0xff) << 16) |
+             (((long)(b[off]&0xff)) << 24);
+    }
+
+    private static String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException {
+        StringBuilder sb = new StringBuilder();
+        
+        int read = 0;
+        while (read < len){
+            // Either read n remaining bytes in b or 250 if n is higher.
+            int toRead = Math.min(len - read, byteBuf.capacity());
+            
+            boolean endOfInput = toRead < byteBuf.capacity();
+            
+            // read 'toRead' bytes into byteBuf
+            byteBuf.put(b, off + read, toRead);
+            
+            // set limit to position and set position to 0
+            // so data can be decoded
+            byteBuf.flip();
+            
+            // decode data in byteBuf
+            CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); 
+            
+            // if the result is not an underflow its an error
+            // that cannot be handled.
+            // if the error is an underflow and its the end of input
+            // then the decoder expects more bytes but there are no more => error
+            if (!result.isUnderflow() || !endOfInput){
+                result.throwException();
+            }
+            
+            // flip the char buf to get the string just decoded
+            charBuf.flip();
+            
+            // append the decoded data into the StringBuilder
+            sb.append(charBuf.toString());
+            
+            // clear buffers for next use
+            byteBuf.clear();
+            charBuf.clear();
+            
+            read += toRead;
+        }
+        
+        return sb.toString();
+    }
+
+    private InputStream readData(int offset, int length) throws IOException{
+        HttpURLConnection conn = (HttpURLConnection) zipUrl.openConnection();
+        conn.setDoOutput(false);
+        conn.setUseCaches(false);
+        conn.setInstanceFollowRedirects(false);
+        String range = "-";
+        if (offset != Integer.MAX_VALUE){
+            range = offset + range;
+        }
+        if (length != Integer.MAX_VALUE){
+            if (offset != Integer.MAX_VALUE){
+                range = range + (offset + length - 1);
+            }else{
+                range = range + length;
+            }
+        }
+
+        conn.setRequestProperty("Range", "bytes=" + range);
+        conn.connect();
+        if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
+            return conn.getInputStream();
+        }else if (conn.getResponseCode() == HttpURLConnection.HTTP_OK){
+            throw new IOException("Your server does not support HTTP feature Content-Range. Please contact your server administrator.");
+        }else{
+            throw new IOException(conn.getResponseCode() + " " + conn.getResponseMessage());
+        }
+    }
+
+    private int readTableEntry(byte[] table, int offset) throws IOException{
+        if (get32(table, offset) != ZipEntry.CENSIG){
+            throw new IOException("Central directory error, expected 'PK12'");
+        }
+
+        int nameLen = get16(table, offset + ZipEntry.CENNAM);
+        int extraLen = get16(table, offset + ZipEntry.CENEXT);
+        int commentLen = get16(table, offset + ZipEntry.CENCOM);
+        int newOffset = offset + ZipEntry.CENHDR + nameLen + extraLen + commentLen;
+
+        int flags = get16(table, offset + ZipEntry.CENFLG);
+        if ((flags & 1) == 1){
+            // ignore this entry, it uses encryption
+            return newOffset;
+        }
+            
+        int method = get16(table, offset + ZipEntry.CENHOW);
+        if (method != ZipEntry.DEFLATED && method != ZipEntry.STORED){
+            // ignore this entry, it uses unknown compression method
+            return newOffset;
+        }
+
+        String name = getUTF8String(table, offset + ZipEntry.CENHDR, nameLen);
+        if (name.charAt(name.length()-1) == '/'){
+            // ignore this entry, it is directory node
+            // or it has no name (?)
+            return newOffset;
+        }
+
+        ZipEntry2 entry = new ZipEntry2();
+        entry.name     = name;
+        entry.deflate  = (method == ZipEntry.DEFLATED);
+        entry.crc      = getu32(table, offset + ZipEntry.CENCRC);
+        entry.length   = get32(table, offset + ZipEntry.CENLEN);
+        entry.compSize = get32(table, offset + ZipEntry.CENSIZ);
+        entry.offset   = get32(table, offset + ZipEntry.CENOFF);
+
+        // we want offset directly into file data ..
+        // move the offset forward to skip the LOC header
+        entry.offset += ZipEntry.LOCHDR + nameLen + extraLen;
+
+        entries.put(entry.name, entry);
+        
+        return newOffset;
+    }
+
+    private void fillByteArray(byte[] array, InputStream source) throws IOException{
+        int total = 0;
+        int length = array.length;
+	while (total < length) {
+	    int read = source.read(array, total, length - total);
+            if (read < 0)
+                throw new IOException("Failed to read entire array");
+
+	    total += read;
+	}
+    }
+
+    private void readCentralDirectory() throws IOException{
+        InputStream in = readData(tableOffset, tableLength);
+        byte[] header = new byte[tableLength];
+
+        // Fix for "PK12 bug in town.zip": sometimes
+        // not entire byte array will be read with InputStream.read()
+        // (especially for big headers)
+        fillByteArray(header, in);
+
+//        in.read(header);
+        in.close();
+
+        entries = new HashMap<String, ZipEntry2>(numEntries);
+        int offset = 0;
+        for (int i = 0; i < numEntries; i++){
+            offset = readTableEntry(header, offset);
+        }
+    }
+
+    private void readEndHeader() throws IOException{
+
+//        InputStream in = readData(Integer.MAX_VALUE, ZipEntry.ENDHDR);
+//        byte[] header = new byte[ZipEntry.ENDHDR];
+//        fillByteArray(header, in);
+//        in.close();
+//
+//        if (get32(header, 0) != ZipEntry.ENDSIG){
+//            throw new IOException("End header error, expected 'PK56'");
+//        }
+
+        // Fix for "PK56 bug in town.zip":
+        // If there's a zip comment inside the end header,
+        // PK56 won't appear in the -22 position relative to the end of the
+        // file!
+        // In that case, we have to search for it.
+        // Increase search space to 200 bytes
+
+        InputStream in = readData(Integer.MAX_VALUE, 200);
+        byte[] header = new byte[200];
+        fillByteArray(header, in);
+        in.close();
+
+        int offset = -1;
+        for (int i = 200 - 22; i >= 0; i--){
+            if (header[i] == (byte) (ZipEntry.ENDSIG & 0xff)
+              && get32(header, i) == ZipEntry.ENDSIG){
+                // found location
+                offset = i;
+                break;
+            }
+        }
+        if (offset == -1)
+            throw new IOException("Cannot find Zip End Header in file!");
+
+        numEntries  = get16(header, offset + ZipEntry.ENDTOT);
+        tableLength = get32(header, offset + ZipEntry.ENDSIZ);
+        tableOffset = get32(header, offset + ZipEntry.ENDOFF);
+    }
+
+    public void load(URL url) throws IOException {
+        if (!url.getProtocol().equals("http"))
+            throw new UnsupportedOperationException();
+
+        zipUrl = url;
+        readEndHeader();
+        readCentralDirectory();
+    }
+
+    private InputStream openStream(ZipEntry2 entry) throws IOException{
+        InputStream in = readData(entry.offset, entry.compSize);
+        if (entry.deflate){
+            return new InflaterInputStream(in, new Inflater(true));
+        }
+        return in;
+    }
+
+    public InputStream openStream(String name) throws IOException{
+        ZipEntry2 entry = entries.get(name);
+        if (entry == null)
+            throw new RuntimeException("Entry not found: "+name);
+
+        return openStream(entry);
+    }
+
+    public void setRootPath(String path){
+        if (!rootPath.equals(path)){
+            rootPath = path;
+            try {
+                load(new URL(path));
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Failed to set root path "+path, ex);
+            }
+        }
+    }
+
+    public AssetInfo locate(AssetManager manager, AssetKey key){
+        final ZipEntry2 entry = entries.get(key.getName());
+        if (entry == null)
+            return null;
+
+        return new AssetInfo(manager, key){
+            @Override
+            public InputStream openStream() {
+                try {
+                    return HttpZipLocator.this.openStream(entry);
+                } catch (IOException ex) {
+                    logger.log(Level.WARNING, "Error retrieving "+entry.name, ex);
+                    return null;
+                }
+            }
+        };
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/UrlAssetInfo.java b/engine/src/core-plugins/com/jme3/asset/plugins/UrlAssetInfo.java
new file mode 100644
index 0000000..941764f
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/UrlAssetInfo.java
@@ -0,0 +1,65 @@
+package com.jme3.asset.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLoadException;
+import com.jme3.asset.AssetManager;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Handles loading of assets from a URL
+ * 
+ * @author Kirill Vainer
+ */
+public class UrlAssetInfo extends AssetInfo {
+    
+    private URL url;
+    private InputStream in;
+    
+    public static UrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException {
+        // Check if URL can be reached. This will throw
+        // IOException which calling code will handle.
+        URLConnection conn = url.openConnection();
+        conn.setUseCaches(false);
+        InputStream in = conn.getInputStream();
+        
+        // For some reason url cannot be reached?
+        if (in == null){
+            return null;
+        }else{
+            return new UrlAssetInfo(assetManager, key, url, in);
+        }
+    }
+    
+    private UrlAssetInfo(AssetManager assetManager, AssetKey key, URL url, InputStream in) throws IOException {
+        super(assetManager, key);
+        this.url = url;
+        this.in = in;
+    }
+    
+    public boolean hasInitialConnection(){
+        return in != null;
+    }
+    
+    @Override
+    public InputStream openStream() {
+        if (in != null){
+            // Reuse the already existing stream (only once)
+            InputStream in2 = in;
+            in = null;
+            return in2;
+        }else{
+            // Create a new stream for subsequent invocations.
+            try {
+                URLConnection conn = url.openConnection();
+                conn.setUseCaches(false);
+                return conn.getInputStream();
+            } catch (IOException ex) {
+                throw new AssetLoadException("Failed to read URL " + url, ex);
+            }
+        }
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/UrlLocator.java b/engine/src/core-plugins/com/jme3/asset/plugins/UrlLocator.java
new file mode 100644
index 0000000..e770930
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/UrlLocator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLocator;
+import com.jme3.asset.AssetManager;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>UrlLocator</code> is a locator that combines a root URL
+ * and the given path in the AssetKey to construct a new URL
+ * that allows locating the asset.
+ * @author Kirill Vainer
+ */
+public class UrlLocator implements AssetLocator {
+
+    private static final Logger logger = Logger.getLogger(UrlLocator.class.getName());
+    private URL root;
+
+    public void setRootPath(String rootPath) {
+        try {
+            this.root = new URL(rootPath);
+        } catch (MalformedURLException ex) {
+            throw new IllegalArgumentException("Invalid rootUrl specified", ex);
+        }
+    }
+
+    public AssetInfo locate(AssetManager manager, AssetKey key) {
+        String name = key.getName();
+        try{
+            //TODO: remove workaround for SDK
+//            URL url = new URL(root, name);
+            if(name.startsWith("/")){
+                name = name.substring(1);
+            }
+            URL url = new URL(root.toExternalForm() + name);
+            return UrlAssetInfo.create(manager, key, url);
+        }catch (FileNotFoundException e){
+            return null;
+        }catch (IOException ex){
+            logger.log(Level.WARNING, "Error while locating " + name, ex);
+            return null;
+        }
+    }
+
+
+}
diff --git a/engine/src/core-plugins/com/jme3/asset/plugins/ZipLocator.java b/engine/src/core-plugins/com/jme3/asset/plugins/ZipLocator.java
new file mode 100644
index 0000000..ddfdfe8
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/asset/plugins/ZipLocator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins;
+
+import com.jme3.asset.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * <code>ZipLocator</code> is a locator that looks up resources in a .ZIP file.
+ * @author Kirill Vainer
+ */
+public class ZipLocator implements AssetLocator {
+
+    private ZipFile zipfile;
+    private static final Logger logger = Logger.getLogger(ZipLocator.class.getName());
+
+    private class JarAssetInfo extends AssetInfo {
+
+        private final ZipEntry entry;
+
+        public JarAssetInfo(AssetManager manager, AssetKey key, ZipEntry entry){
+            super(manager, key);
+            this.entry = entry;
+        }
+
+        public InputStream openStream(){
+            try{
+                return zipfile.getInputStream(entry);
+            }catch (IOException ex){
+                throw new AssetLoadException("Failed to load zip entry: "+entry, ex);
+            }
+        }
+    }
+
+    public void setRootPath(String rootPath) {
+        try{
+            zipfile = new ZipFile(new File(rootPath), ZipFile.OPEN_READ);
+        }catch (IOException ex){
+            throw new AssetLoadException("Failed to open zip file: " + rootPath, ex);
+        }
+    }
+
+    public AssetInfo locate(AssetManager manager, AssetKey key) {
+        String name = key.getName();
+        ZipEntry entry = zipfile.getEntry(name);
+        if (entry == null)
+            return null;
+        
+        return new JarAssetInfo(manager, key, entry);
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/audio/plugins/WAVLoader.java b/engine/src/core-plugins/com/jme3/audio/plugins/WAVLoader.java
new file mode 100644
index 0000000..ab6a19b
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/audio/plugins/WAVLoader.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.audio.AudioBuffer;
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioKey;
+import com.jme3.audio.AudioStream;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.LittleEndien;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class WAVLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(WAVLoader.class.getName());
+
+    // all these are in big endian
+    private static final int i_RIFF = 0x46464952;
+    private static final int i_WAVE = 0x45564157;
+    private static final int i_fmt  = 0x20746D66;
+    private static final int i_data = 0x61746164;
+
+    private boolean readStream = false;
+
+    private AudioBuffer audioBuffer;
+    private AudioStream audioStream;
+    private AudioData audioData;
+    private int bytesPerSec;
+    private float duration;
+
+    private LittleEndien in;
+
+    private void readFormatChunk(int size) throws IOException{
+        // if other compressions are supported, size doesn't have to be 16
+//        if (size != 16)
+//            logger.warning("Expected size of format chunk to be 16");
+
+        int compression = in.readShort();
+        if (compression != 1){
+            throw new IOException("WAV Loader only supports PCM wave files");
+        }
+
+        int channels = in.readShort();
+        int sampleRate = in.readInt();
+
+        bytesPerSec = in.readInt(); // used to calculate duration
+
+        int bytesPerSample = in.readShort();
+        int bitsPerSample = in.readShort();
+
+        int expectedBytesPerSec = (bitsPerSample * channels * sampleRate) / 8;
+        if (expectedBytesPerSec != bytesPerSec){
+            logger.log(Level.WARNING, "Expected {0} bytes per second, got {1}",
+                    new Object[]{expectedBytesPerSec, bytesPerSec});
+        }
+        
+        if (bitsPerSample != 8 && bitsPerSample != 16)
+            throw new IOException("Only 8 and 16 bits per sample are supported!");
+
+        if ( (bitsPerSample / 8) * channels != bytesPerSample)
+            throw new IOException("Invalid bytes per sample value");
+
+        if (bytesPerSample * sampleRate != bytesPerSec)
+            throw new IOException("Invalid bytes per second value");
+
+        audioData.setupFormat(channels, bitsPerSample, sampleRate);
+
+        int remaining = size - 16;
+        if (remaining > 0){
+            in.skipBytes(remaining);
+        }
+    }
+
+    private void readDataChunkForBuffer(int len) throws IOException {
+        ByteBuffer data = BufferUtils.createByteBuffer(len);
+        byte[] buf = new byte[512];
+        int read = 0;
+        while ( (read = in.read(buf)) > 0){
+            data.put(buf, 0, Math.min(read, data.remaining()) );
+        }
+        data.flip();
+        audioBuffer.updateData(data);
+        in.close();
+    }
+
+    private void readDataChunkForStream(int len) throws IOException {
+        audioStream.updateData(in, duration);
+    }
+
+    private AudioData load(InputStream inputStream, boolean stream) throws IOException{
+        this.in = new LittleEndien(inputStream);
+        
+        int sig = in.readInt();
+        if (sig != i_RIFF)
+            throw new IOException("File is not a WAVE file");
+        
+        // skip size
+        in.readInt();
+        if (in.readInt() != i_WAVE)
+            throw new IOException("WAVE File does not contain audio");
+
+        readStream = stream;
+        if (readStream){
+            audioStream = new AudioStream();
+            audioData = audioStream;
+        }else{
+            audioBuffer = new AudioBuffer();
+            audioData = audioBuffer;
+        }
+
+        while (true) {
+            int type = in.readInt();
+            int len = in.readInt();
+
+            switch (type) {
+                case i_fmt:
+                    readFormatChunk(len);
+                    break;
+                case i_data:
+                    // Compute duration based on data chunk size
+                    duration = len / bytesPerSec;
+
+                    if (readStream) {
+                        readDataChunkForStream(len);
+                    } else {
+                        readDataChunkForBuffer(len);
+                    }
+                    return audioData;
+                default:
+                    int skipped = in.skipBytes(len);
+                    if (skipped <= 0) {
+                        return null;
+                    }
+                    break;
+            }
+        }
+    }
+    
+    public Object load(AssetInfo info) throws IOException {
+        AudioData data;
+        InputStream inputStream = null;
+        try {
+            inputStream = info.openStream();
+            data = load(inputStream, ((AudioKey)info.getKey()).isStream());
+            if (data instanceof AudioStream){
+                inputStream = null;
+            }
+            return data;
+        } finally {
+            if (inputStream != null){
+                inputStream.close();
+            }
+        }
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryClassField.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassField.java
new file mode 100644
index 0000000..20da0e7
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassField.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+class BinaryClassField {
+
+    public static final byte BYTE = 0;
+    public static final byte BYTE_1D = 1;
+    public static final byte BYTE_2D = 2;
+
+    public static final byte INT = 10;
+    public static final byte INT_1D = 11;
+    public static final byte INT_2D = 12;
+
+    public static final byte FLOAT = 20;
+    public static final byte FLOAT_1D = 21;
+    public static final byte FLOAT_2D = 22;
+
+    public static final byte DOUBLE = 30;
+    public static final byte DOUBLE_1D = 31;
+    public static final byte DOUBLE_2D = 32;
+
+    public static final byte LONG = 40;
+    public static final byte LONG_1D = 41;
+    public static final byte LONG_2D = 42;
+
+    public static final byte SHORT = 50;
+    public static final byte SHORT_1D = 51;
+    public static final byte SHORT_2D = 52;
+
+    public static final byte BOOLEAN = 60;
+    public static final byte BOOLEAN_1D = 61;
+    public static final byte BOOLEAN_2D = 62;
+
+    public static final byte STRING = 70;
+    public static final byte STRING_1D = 71;
+    public static final byte STRING_2D = 72;
+
+    public static final byte BITSET = 80;
+
+    public static final byte SAVABLE = 90;
+    public static final byte SAVABLE_1D = 91;
+    public static final byte SAVABLE_2D = 92;
+
+    public static final byte SAVABLE_ARRAYLIST = 100;
+    public static final byte SAVABLE_ARRAYLIST_1D = 101;
+    public static final byte SAVABLE_ARRAYLIST_2D = 102;
+    
+    public static final byte SAVABLE_MAP = 105;
+    public static final byte STRING_SAVABLE_MAP = 106;
+    public static final byte INT_SAVABLE_MAP = 107;
+    
+    public static final byte FLOATBUFFER_ARRAYLIST = 110;
+    public static final byte BYTEBUFFER_ARRAYLIST = 111;
+
+    public static final byte FLOATBUFFER = 120;
+    public static final byte INTBUFFER = 121;
+    public static final byte BYTEBUFFER = 122;
+    public static final byte SHORTBUFFER = 123;
+
+    
+    byte type;
+    String name;
+    byte alias;
+
+    BinaryClassField(String name, byte alias, byte type) {
+        this.name = name;
+        this.alias = alias;
+        this.type = type;
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java
new file mode 100644
index 0000000..e66a945
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import java.util.HashMap;
+
+class BinaryClassObject {
+
+    // When exporting, use nameFields field, importing use aliasFields.
+    HashMap<String, BinaryClassField> nameFields;
+    HashMap<Byte, BinaryClassField> aliasFields;
+    
+    byte[] alias;
+    String className;
+    int[] classHierarchyVersions;
+}
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java
new file mode 100644
index 0000000..b9ec78d
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import com.jme3.export.FormatVersion;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.Savable;
+import com.jme3.export.SavableClassUtil;
+import com.jme3.math.FastMath;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Exports to the jME Binary Format. Format descriptor: (each numbered item
+ * denotes a series of bytes that follows sequentially one after the next.)
+ * <p>
+ * 1. "number of classes" - four bytes - int value representing the number of
+ * entries in the class lookup table.
+ * </p>
+ * <p>
+ * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9,
+ * where X = the number read in 1.
+ * </p>
+ * <p>
+ * 2. "class alias" - 1...X bytes, where X = ((int) FastMath.log(aliasCount,
+ * 256) + 1) - an alias used when writing object data to match an object to its
+ * appropriate object class type.
+ * </p>
+ * <p>
+ * 3. "full class name size" - four bytes - int value representing number of
+ * bytes to read in for next field.
+ * </p>
+ * <p>
+ * 4. "full class name" - 1...X bytes representing a String value, where X = the
+ * number read in 3. The String is the fully qualified class name of the Savable
+ * class, eg "<code>com.jme.math.Vector3f</code>"
+ * </p>
+ * <p>
+ * 5. "number of fields" - four bytes - int value representing number of blocks
+ * to read in next (numbers 6 - 9), where each block represents a field in this
+ * class.
+ * </p>
+ * <p>
+ * 6. "field alias" - 1 byte - the alias used when writing out fields in a
+ * class. Because it is a single byte, a single class can not save out more than
+ * a total of 256 fields.
+ * </p>
+ * <p>
+ * 7. "field type" - 1 byte - a value representing the type of data a field
+ * contains. This value is taken from the static fields of
+ * <code>com.jme.util.export.binary.BinaryClassField</code>.
+ * </p>
+ * <p>
+ * 8. "field name size" - 4 bytes - int value representing the size of the next
+ * field.
+ * </p>
+ * <p>
+ * 9. "field name" - 1...X bytes representing a String value, where X = the
+ * number read in 8. The String is the full String value used when writing the
+ * current field.
+ * </p>
+ * <p>
+ * 10. "number of unique objects" - four bytes - int value representing the
+ * number of data entries in this file.
+ * </p>
+ * <p>
+ * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and
+ * 12, where X = the number read in 10.
+ * </p>
+ * <p>
+ * 11. "data id" - four bytes - int value identifying a single unique object
+ * that was saved in this data file.
+ * </p>
+ * <p>
+ * 12. "data location" - four bytes - int value representing the offset in the
+ * object data portion of this file where the object identified in 11 is
+ * located.
+ * </p>
+ * <p>
+ * 13. "future use" - four bytes - hardcoded int value 1.
+ * </p>
+ * <p>
+ * 14. "root id" - four bytes - int value identifying the top level object.
+ * </p>
+ * <p>
+ * OBJECT DATA SECTION: There will be X blocks each consisting of numbers 15
+ * thru 19, where X = the number of unique location values named in 12.
+ * <p>
+ * 15. "class alias" - see 2.
+ * </p>
+ * <p>
+ * 16. "data length" - four bytes - int value representing the length in bytes
+ * of data stored in fields 17 and 18 for this object.
+ * </p>
+ * <p>
+ * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19
+ * </p>
+ * <p>
+ * 17. "field alias" - see 6.
+ * </p>
+ * <p>
+ * 18. "field data" - 1...X bytes representing the field data. The data length
+ * is dependent on the field type and contents.
+ * </p>
+ *
+ * @author Joshua Slack
+ */
+
+public class BinaryExporter implements JmeExporter {
+    private static final Logger logger = Logger.getLogger(BinaryExporter.class
+            .getName());
+
+    protected int aliasCount = 1;
+    protected int idCount = 1;
+
+    protected IdentityHashMap<Savable, BinaryIdContentPair> contentTable
+             = new IdentityHashMap<Savable, BinaryIdContentPair>();
+
+    protected HashMap<Integer, Integer> locationTable
+             = new HashMap<Integer, Integer>();
+
+    // key - class name, value = bco
+    private HashMap<String, BinaryClassObject> classes
+             = new HashMap<String, BinaryClassObject>();
+
+    private ArrayList<Savable> contentKeys = new ArrayList<Savable>();
+
+    public static boolean debug = false;
+    public static boolean useFastBufs = true;
+      
+    public BinaryExporter() {
+    }
+
+    public static BinaryExporter getInstance() {
+        return new BinaryExporter();
+    }
+
+    public boolean save(Savable object, OutputStream os) throws IOException {
+        // reset some vars
+        aliasCount = 1;
+        idCount = 1;
+        classes.clear();
+        contentTable.clear();
+        locationTable.clear();
+        contentKeys.clear();
+        
+        // write signature and version
+        os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE));
+        os.write(ByteUtils.convertToBytes(FormatVersion.VERSION));
+        
+        int id = processBinarySavable(object);
+
+        // write out tag table
+        int classTableSize = 0;
+        int classNum = classes.keySet().size();
+        int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all
+                                                                  // aliases a
+                                                                  // fixed width
+        
+        os.write(ByteUtils.convertToBytes(classNum));
+        for (String key : classes.keySet()) {
+            BinaryClassObject bco = classes.get(key);
+
+            // write alias
+            byte[] aliasBytes = fixClassAlias(bco.alias,
+                    aliasSize);
+            os.write(aliasBytes);
+            classTableSize += aliasSize;
+            
+            // jME3 NEW: Write class hierarchy version numbers
+            os.write( bco.classHierarchyVersions.length );
+            for (int version : bco.classHierarchyVersions){
+                os.write(ByteUtils.convertToBytes(version));
+            }
+            classTableSize += 1 + bco.classHierarchyVersions.length * 4;
+            
+            // write classname size & classname
+            byte[] classBytes = key.getBytes();
+            os.write(ByteUtils.convertToBytes(classBytes.length));
+            os.write(classBytes);
+            classTableSize += 4 + classBytes.length;
+            
+            // for each field, write alias, type, and name
+            os.write(ByteUtils.convertToBytes(bco.nameFields.size()));
+            for (String fieldName : bco.nameFields.keySet()) {
+                BinaryClassField bcf = bco.nameFields.get(fieldName);
+                os.write(bcf.alias);
+                os.write(bcf.type);
+
+                // write classname size & classname
+                byte[] fNameBytes = fieldName.getBytes();
+                os.write(ByteUtils.convertToBytes(fNameBytes.length));
+                os.write(fNameBytes);
+                classTableSize += 2 + 4 + fNameBytes.length;
+            }
+        }
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        // write out data to a seperate stream
+        int location = 0;
+        // keep track of location for each piece
+        HashMap<String, ArrayList<BinaryIdContentPair>> alreadySaved = new HashMap<String, ArrayList<BinaryIdContentPair>>(
+                contentTable.size());
+        for (Savable savable : contentKeys) {
+            // look back at previous written data for matches
+            String savableName = savable.getClass().getName();
+            BinaryIdContentPair pair = contentTable.get(savable);
+            ArrayList<BinaryIdContentPair> bucket = alreadySaved
+                    .get(savableName + getChunk(pair));
+            int prevLoc = findPrevMatch(pair, bucket);
+            if (prevLoc != -1) {
+                locationTable.put(pair.getId(), prevLoc);
+                continue;
+            }
+
+            locationTable.put(pair.getId(), location);
+            if (bucket == null) {
+                bucket = new ArrayList<BinaryIdContentPair>();
+                alreadySaved.put(savableName + getChunk(pair), bucket);
+            }
+            bucket.add(pair);
+            byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasSize);
+            out.write(aliasBytes);
+            location += aliasSize;
+            BinaryOutputCapsule cap = contentTable.get(savable).getContent();
+            out.write(ByteUtils.convertToBytes(cap.bytes.length));
+            location += 4; // length of bytes
+            out.write(cap.bytes);
+            location += cap.bytes.length;
+        }
+
+        // write out location table
+        // tag/location
+        int numLocations = locationTable.keySet().size();
+        os.write(ByteUtils.convertToBytes(numLocations));
+        int locationTableSize = 0;
+        for (Integer key : locationTable.keySet()) {
+            os.write(ByteUtils.convertToBytes(key));
+            os.write(ByteUtils.convertToBytes(locationTable.get(key)));
+            locationTableSize += 8;
+        }
+
+        // write out number of root ids - hardcoded 1 for now
+        os.write(ByteUtils.convertToBytes(1));
+
+        // write out root id
+        os.write(ByteUtils.convertToBytes(id));
+
+        // append stream to the output stream
+        out.writeTo(os);
+
+
+        out = null;
+        os = null;
+
+        if (debug ) {
+            logger.info("Stats:");
+            logger.log(Level.INFO, "classes: {0}", classNum);
+            logger.log(Level.INFO, "class table: {0} bytes", classTableSize);
+            logger.log(Level.INFO, "objects: {0}", numLocations);
+            logger.log(Level.INFO, "location table: {0} bytes", locationTableSize);
+            logger.log(Level.INFO, "data: {0} bytes", location);
+        }
+
+        return true;
+    }
+
+    protected String getChunk(BinaryIdContentPair pair) {
+        return new String(pair.getContent().bytes, 0, Math.min(64, pair
+                .getContent().bytes.length));
+    }
+
+    protected int findPrevMatch(BinaryIdContentPair oldPair,
+            ArrayList<BinaryIdContentPair> bucket) {
+        if (bucket == null)
+            return -1;
+        for (int x = bucket.size(); --x >= 0;) {
+            BinaryIdContentPair pair = bucket.get(x);
+            if (pair.getContent().equals(oldPair.getContent()))
+                return locationTable.get(pair.getId());
+        }
+        return -1;
+    }
+
+    protected byte[] fixClassAlias(byte[] bytes, int width) {
+        if (bytes.length != width) {
+            byte[] newAlias = new byte[width];
+            for (int x = width - bytes.length; x < width; x++)
+                newAlias[x] = bytes[x - bytes.length];
+            return newAlias;
+        }
+        return bytes;
+    }
+
+    public boolean save(Savable object, File f) throws IOException {
+        File parentDirectory = f.getParentFile();
+        if(parentDirectory != null && !parentDirectory.exists()) {
+            parentDirectory.mkdirs();
+        }
+
+        FileOutputStream fos = new FileOutputStream(f);
+        boolean rVal = save(object, fos);
+        fos.close();
+        return rVal;
+    }
+
+    public BinaryOutputCapsule getCapsule(Savable object) {
+        return contentTable.get(object).getContent();
+    }
+
+    private BinaryClassObject createClassObject(Class clazz) throws IOException{
+        BinaryClassObject bco = new BinaryClassObject();
+        bco.alias = generateTag();
+        bco.nameFields = new HashMap<String, BinaryClassField>();
+        bco.classHierarchyVersions = SavableClassUtil.getSavableVersions(clazz);
+        
+        classes.put(clazz.getName(), bco);
+            
+        return bco;
+    }
+    
+    public int processBinarySavable(Savable object) throws IOException {
+        if (object == null) {
+            return -1;
+        }
+        Class<? extends Savable> clazz = object.getClass();
+        BinaryClassObject bco = classes.get(object.getClass().getName());
+        // is this class been looked at before? in tagTable?
+        if (bco == null) {
+            bco = createClassObject(object.getClass());
+        }
+
+        // is object in contentTable?
+        if (contentTable.get(object) != null) {
+            return (contentTable.get(object).getId());
+        }
+        BinaryIdContentPair newPair = generateIdContentPair(bco);
+        BinaryIdContentPair old = contentTable.put(object, newPair);
+        if (old == null) {
+            contentKeys.add(object);
+        }
+        object.write(this);
+        newPair.getContent().finish();
+        return newPair.getId();
+
+    }
+
+    protected byte[] generateTag() {
+        int width = ((int) FastMath.log(aliasCount, 256) + 1);
+        int count = aliasCount;
+        aliasCount++;
+        byte[] bytes = new byte[width];
+        for (int x = width - 1; x >= 0; x--) {
+            int pow = (int) FastMath.pow(256, x);
+            int factor = count / pow;
+            bytes[width - x - 1] = (byte) factor;
+            count %= pow;
+        }
+        return bytes;
+    }
+
+    protected BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) {
+        BinaryIdContentPair pair = new BinaryIdContentPair(idCount++,
+                new BinaryOutputCapsule(this, bco));
+        return pair;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryIdContentPair.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryIdContentPair.java
new file mode 100644
index 0000000..b999a3c
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryIdContentPair.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+class BinaryIdContentPair {
+
+    private int id;
+    private BinaryOutputCapsule content;
+    
+    BinaryIdContentPair(int id, BinaryOutputCapsule content) {
+        this.id = id;
+        this.content = content;
+    }
+
+    BinaryOutputCapsule getContent() {
+        return content;
+    }
+
+    void setContent(BinaryOutputCapsule content) {
+        this.content = content;
+    }
+
+    int getId() {
+        return id;
+    }
+
+    void setId(int id) {
+        this.id = id;
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java
new file mode 100644
index 0000000..7f94ba4
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import java.io.*;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Joshua Slack
+ * @author Kirill Vainer - Version number, Fast buffer reading
+ */
+public final class BinaryImporter implements JmeImporter {
+    private static final Logger logger = Logger.getLogger(BinaryImporter.class
+            .getName());
+
+    private AssetManager assetManager;
+
+    //Key - alias, object - bco
+    private HashMap<String, BinaryClassObject> classes
+             = new HashMap<String, BinaryClassObject>();
+    //Key - id, object - the savable
+    private HashMap<Integer, Savable> contentTable
+            = new HashMap<Integer, Savable>();
+    //Key - savable, object - capsule
+    private IdentityHashMap<Savable, BinaryInputCapsule> capsuleTable
+             = new IdentityHashMap<Savable, BinaryInputCapsule>();
+    //Key - id, opject - location in the file
+    private HashMap<Integer, Integer> locationTable
+             = new HashMap<Integer, Integer>();
+
+    public static boolean debug = false;
+
+    private byte[] dataArray;
+    private int aliasWidth;
+    private int formatVersion;
+
+    private static final boolean fastRead = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
+    
+    public BinaryImporter() {
+    }
+    
+    public int getFormatVersion(){
+        return formatVersion;
+    }
+    
+    public static boolean canUseFastBuffers(){
+        return fastRead;
+    }
+
+    public static BinaryImporter getInstance() {
+        return new BinaryImporter();
+    }
+
+    public void setAssetManager(AssetManager manager){
+        this.assetManager = manager;
+    }
+
+    public AssetManager getAssetManager(){
+        return assetManager;
+    }
+
+    public Object load(AssetInfo info){
+//        if (!(info.getKey() instanceof ModelKey))
+//            throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
+
+        assetManager = info.getManager();
+
+        InputStream is = null;
+        try {
+            is = info.openStream();
+            Savable s = load(is);
+            
+            return s;
+        } catch (IOException ex) {
+            logger.log(Level.SEVERE, "An error occured while loading jME binary object", ex);
+        } finally {
+            if (is != null){
+                try {
+                    is.close();
+                } catch (IOException ex) {}
+            }
+        }
+        return null;
+    }
+
+    public Savable load(InputStream is) throws IOException {
+        return load(is, null, null);
+    }
+
+    public Savable load(InputStream is, ReadListener listener) throws IOException {
+        return load(is, listener, null);
+    }
+
+    public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream baos) throws IOException {
+        contentTable.clear();
+        BufferedInputStream bis = new BufferedInputStream(is);
+        
+        int numClasses;
+        
+        // Try to read signature
+        int maybeSignature = ByteUtils.readInt(bis);
+        if (maybeSignature == FormatVersion.SIGNATURE){
+            // this is a new version J3O file
+            formatVersion = ByteUtils.readInt(bis);
+            numClasses = ByteUtils.readInt(bis);
+            
+            // check if this binary is from the future
+            if (formatVersion > FormatVersion.VERSION){
+                throw new IOException("The binary file is of newer version than expected! " + 
+                                      formatVersion + " > " + FormatVersion.VERSION);
+            }
+        }else{
+            // this is an old version J3O file
+            // the signature was actually the class count
+            numClasses = maybeSignature;
+            
+            // 0 indicates version before we started adding
+            // version numbers
+            formatVersion = 0; 
+        }
+        
+        int bytes = 4;
+        aliasWidth = ((int)FastMath.log(numClasses, 256) + 1);
+
+        classes.clear();
+        for(int i = 0; i < numClasses; i++) {
+            String alias = readString(bis, aliasWidth);
+            
+            // jME3 NEW: Read class version number
+            int[] classHierarchyVersions;
+            if (formatVersion >= 1){
+                int classHierarchySize = bis.read();
+                classHierarchyVersions = new int[classHierarchySize];
+                for (int j = 0; j < classHierarchySize; j++){
+                    classHierarchyVersions[j] = ByteUtils.readInt(bis);
+                }
+            }else{
+                classHierarchyVersions = new int[]{ 0 };
+            }
+            
+            // read classname and classname size
+            int classLength = ByteUtils.readInt(bis);
+            String className = readString(bis, classLength);
+            
+            BinaryClassObject bco = new BinaryClassObject();
+            bco.alias = alias.getBytes();
+            bco.className = className;
+            bco.classHierarchyVersions = classHierarchyVersions;
+            
+            int fields = ByteUtils.readInt(bis);
+            bytes += (8 + aliasWidth + classLength);
+
+            bco.nameFields = new HashMap<String, BinaryClassField>(fields);
+            bco.aliasFields = new HashMap<Byte, BinaryClassField>(fields);
+            for (int x = 0; x < fields; x++) {
+                byte fieldAlias = (byte)bis.read();
+                byte fieldType = (byte)bis.read();
+
+                int fieldNameLength = ByteUtils.readInt(bis);
+                String fieldName = readString(bis, fieldNameLength);
+                BinaryClassField bcf = new BinaryClassField(fieldName, fieldAlias, fieldType);
+                bco.nameFields.put(fieldName, bcf);
+                bco.aliasFields.put(fieldAlias, bcf);
+                bytes += (6 + fieldNameLength);
+            }
+            classes.put(alias, bco);
+        }
+        if (listener != null) listener.readBytes(bytes);
+
+        int numLocs = ByteUtils.readInt(bis);
+        bytes = 4;
+
+        capsuleTable.clear();
+        locationTable.clear();
+        for(int i = 0; i < numLocs; i++) {
+            int id = ByteUtils.readInt(bis);
+            int loc = ByteUtils.readInt(bis);
+            locationTable.put(id, loc);
+            bytes += 8;
+        }
+
+        @SuppressWarnings("unused")
+        int numbIDs = ByteUtils.readInt(bis); // XXX: NOT CURRENTLY USED
+        int id = ByteUtils.readInt(bis);
+        bytes += 8;
+        if (listener != null) listener.readBytes(bytes);
+
+        if (baos == null) {
+                baos = new ByteArrayOutputStream(bytes);
+        } else {
+                baos.reset();
+        }
+        int size = -1;
+        byte[] cache = new byte[4096];
+        while((size = bis.read(cache)) != -1) {
+            baos.write(cache, 0, size);
+            if (listener != null) listener.readBytes(size);
+        }
+        bis = null;
+
+        dataArray = baos.toByteArray();
+        baos = null;
+
+        Savable rVal = readObject(id);
+        if (debug) {
+            logger.info("Importer Stats: ");
+            logger.log(Level.INFO, "Tags: {0}", numClasses);
+            logger.log(Level.INFO, "Objects: {0}", numLocs);
+            logger.log(Level.INFO, "Data Size: {0}", dataArray.length);
+        }
+        dataArray = null;
+        return rVal;
+    }
+
+    public Savable load(URL f) throws IOException {
+        return load(f, null);
+    }
+
+    public Savable load(URL f, ReadListener listener) throws IOException {
+        InputStream is = f.openStream();
+        Savable rVal = load(is, listener);
+        is.close();
+        return rVal;
+    }
+
+    public Savable load(File f) throws IOException {
+        return load(f, null);
+    }
+
+    public Savable load(File f, ReadListener listener) throws IOException {
+        FileInputStream fis = new FileInputStream(f);
+        Savable rVal = load(fis, listener);
+        fis.close();
+        return rVal;
+    }
+
+    public Savable load(byte[] data) throws IOException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(data);
+        Savable rVal = load(bais);
+        bais.close();
+        return rVal;
+    }
+
+    @Override
+    public InputCapsule getCapsule(Savable id) {
+        return capsuleTable.get(id);
+    }
+
+    protected String readString(InputStream f, int length) throws IOException {
+        byte[] data = new byte[length];
+        for(int j = 0; j < length; j++) {
+            data[j] = (byte)f.read();
+        }
+
+        return new String(data);
+    }
+
+    protected String readString(int length, int offset) throws IOException {
+        byte[] data = new byte[length];
+        for(int j = 0; j < length; j++) {
+            data[j] = dataArray[j+offset];
+        }
+
+        return new String(data);
+    }
+
+    public Savable readObject(int id) {
+
+        if(contentTable.get(id) != null) {
+            return contentTable.get(id);
+        }
+
+        try {
+            int loc = locationTable.get(id);
+
+            String alias = readString(aliasWidth, loc);
+            loc+=aliasWidth;
+
+            BinaryClassObject bco = classes.get(alias);
+
+            if(bco == null) {
+                logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "NULL class object: " + alias);
+                return null;
+            }
+
+            int dataLength = ByteUtils.convertIntFromBytes(dataArray, loc);
+            loc+=4;
+
+            Savable out = null;
+            if (assetManager != null) {
+                out = SavableClassUtil.fromName(bco.className, assetManager.getClassLoaders());
+            } else {
+                out = SavableClassUtil.fromName(bco.className);
+            }
+
+            BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco);
+            cap.setContent(dataArray, loc, loc+dataLength);
+
+            capsuleTable.put(out, cap);
+            contentTable.put(id, out);
+
+            out.read(this);
+
+            capsuleTable.remove(out);
+
+            return out;
+
+        } catch (IOException e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
+            return null;
+        } catch (ClassNotFoundException e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
+            return null;
+        } catch (InstantiationException e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
+            return null;
+        } catch (IllegalAccessException e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java
new file mode 100644
index 0000000..cebd764
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java
@@ -0,0 +1,1380 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.export.SavableClassUtil;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+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;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Joshua Slack
+ */
+final class BinaryInputCapsule implements InputCapsule {
+
+    private static final Logger logger = Logger
+            .getLogger(BinaryInputCapsule.class.getName());
+
+    protected BinaryImporter importer;
+    protected BinaryClassObject cObj;
+    protected Savable savable;
+    protected HashMap<Byte, Object> fieldData;
+
+    protected int index = 0;
+
+    public BinaryInputCapsule(BinaryImporter importer, Savable savable, BinaryClassObject bco) {
+        this.importer = importer;
+        this.cObj = bco;
+        this.savable = savable;
+    }
+
+    public void setContent(byte[] content, int start, int limit) {
+        fieldData = new HashMap<Byte, Object>();
+        for (index = start; index < limit;) {
+            byte alias = content[index];
+
+            index++;
+
+            try {
+                byte type = cObj.aliasFields.get(alias).type;
+                Object value = null;
+
+                switch (type) {
+                    case BinaryClassField.BITSET: {
+                        value = readBitSet(content);
+                        break;
+                    }
+                    case BinaryClassField.BOOLEAN: {
+                        value = readBoolean(content);
+                        break;
+                    }
+                    case BinaryClassField.BOOLEAN_1D: {
+                        value = readBooleanArray(content);
+                        break;
+                    }
+                    case BinaryClassField.BOOLEAN_2D: {
+                        value = readBooleanArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.BYTE: {
+                        value = readByte(content);
+                        break;
+                    }
+                    case BinaryClassField.BYTE_1D: {
+                        value = readByteArray(content);
+                        break;
+                    }
+                    case BinaryClassField.BYTE_2D: {
+                        value = readByteArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.BYTEBUFFER: {
+                        value = readByteBuffer(content);
+                        break;
+                    }
+                    case BinaryClassField.DOUBLE: {
+                        value = readDouble(content);
+                        break;
+                    }
+                    case BinaryClassField.DOUBLE_1D: {
+                        value = readDoubleArray(content);
+                        break;
+                    }
+                    case BinaryClassField.DOUBLE_2D: {
+                        value = readDoubleArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.FLOAT: {
+                        value = readFloat(content);
+                        break;
+                    }
+                    case BinaryClassField.FLOAT_1D: {
+                        value = readFloatArray(content);
+                        break;
+                    }
+                    case BinaryClassField.FLOAT_2D: {
+                        value = readFloatArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.FLOATBUFFER: {
+                        value = readFloatBuffer(content);
+                        break;
+                    }
+                    case BinaryClassField.FLOATBUFFER_ARRAYLIST: {
+                        value = readFloatBufferArrayList(content);
+                        break;
+                    }
+                    case BinaryClassField.BYTEBUFFER_ARRAYLIST: {
+                        value = readByteBufferArrayList(content);
+                        break;
+                    }
+                    case BinaryClassField.INT: {
+                        value = readInt(content);
+                        break;
+                    }
+                    case BinaryClassField.INT_1D: {
+                        value = readIntArray(content);
+                        break;
+                    }
+                    case BinaryClassField.INT_2D: {
+                        value = readIntArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.INTBUFFER: {
+                        value = readIntBuffer(content);
+                        break;
+                    }
+                    case BinaryClassField.LONG: {
+                        value = readLong(content);
+                        break;
+                    }
+                    case BinaryClassField.LONG_1D: {
+                        value = readLongArray(content);
+                        break;
+                    }
+                    case BinaryClassField.LONG_2D: {
+                        value = readLongArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE: {
+                        value = readSavable(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_1D: {
+                        value = readSavableArray(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_2D: {
+                        value = readSavableArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_ARRAYLIST: {
+                        value = readSavableArray(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_ARRAYLIST_1D: {
+                        value = readSavableArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_ARRAYLIST_2D: {
+                        value = readSavableArray3D(content);
+                        break;
+                    }
+                    case BinaryClassField.SAVABLE_MAP: {
+                        value = readSavableMap(content);
+                        break;
+                    }
+                    case BinaryClassField.STRING_SAVABLE_MAP: {
+                        value = readStringSavableMap(content);
+                        break;
+                    }
+                    case BinaryClassField.INT_SAVABLE_MAP: {
+                        value = readIntSavableMap(content);
+                        break;
+                    }
+                    case BinaryClassField.SHORT: {
+                        value = readShort(content);
+                        break;
+                    }
+                    case BinaryClassField.SHORT_1D: {
+                        value = readShortArray(content);
+                        break;
+                    }
+                    case BinaryClassField.SHORT_2D: {
+                        value = readShortArray2D(content);
+                        break;
+                    }
+                    case BinaryClassField.SHORTBUFFER: {
+                        value = readShortBuffer(content);
+                        break;
+                    }
+                    case BinaryClassField.STRING: {
+                        value = readString(content);
+                        break;
+                    }
+                    case BinaryClassField.STRING_1D: {
+                        value = readStringArray(content);
+                        break;
+                    }
+                    case BinaryClassField.STRING_2D: {
+                        value = readStringArray2D(content);
+                        break;
+                    }
+
+                    default:
+                        // skip put statement
+                        continue;
+                }
+
+                fieldData.put(alias, value);
+
+            } catch (IOException e) {
+                logger.logp(Level.SEVERE, this.getClass().toString(),
+                        "setContent(byte[] content)", "Exception", e);
+            }
+        }
+    }
+    
+    public int getSavableVersion(Class<? extends Savable> desiredClass){
+        return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, 
+                                            cObj.classHierarchyVersions, importer.getFormatVersion());
+    }
+
+    public BitSet readBitSet(String name, BitSet defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (BitSet) fieldData.get(field.alias);
+    }
+
+    public boolean readBoolean(String name, boolean defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Boolean) fieldData.get(field.alias)).booleanValue();
+    }
+
+    public boolean[] readBooleanArray(String name, boolean[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (boolean[]) fieldData.get(field.alias);
+    }
+
+    public boolean[][] readBooleanArray2D(String name, boolean[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (boolean[][]) fieldData.get(field.alias);
+    }
+
+    public byte readByte(String name, byte defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Byte) fieldData.get(field.alias)).byteValue();
+    }
+
+    public byte[] readByteArray(String name, byte[] defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (byte[]) fieldData.get(field.alias);
+    }
+
+    public byte[][] readByteArray2D(String name, byte[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (byte[][]) fieldData.get(field.alias);
+    }
+
+    public ByteBuffer readByteBuffer(String name, ByteBuffer defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (ByteBuffer) fieldData.get(field.alias);
+    }
+
+    @SuppressWarnings("unchecked")
+    public ArrayList<ByteBuffer> readByteBufferArrayList(String name,
+            ArrayList<ByteBuffer> defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (ArrayList<ByteBuffer>) fieldData.get(field.alias);
+    }
+
+    public double readDouble(String name, double defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Double) fieldData.get(field.alias)).doubleValue();
+    }
+
+    public double[] readDoubleArray(String name, double[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (double[]) fieldData.get(field.alias);
+    }
+
+    public double[][] readDoubleArray2D(String name, double[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (double[][]) fieldData.get(field.alias);
+    }
+
+    public float readFloat(String name, float defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Float) fieldData.get(field.alias)).floatValue();
+    }
+
+    public float[] readFloatArray(String name, float[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (float[]) fieldData.get(field.alias);
+    }
+
+    public float[][] readFloatArray2D(String name, float[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (float[][]) fieldData.get(field.alias);
+    }
+
+    public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (FloatBuffer) fieldData.get(field.alias);
+    }
+
+    @SuppressWarnings("unchecked")
+    public ArrayList<FloatBuffer> readFloatBufferArrayList(String name,
+            ArrayList<FloatBuffer> defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (ArrayList<FloatBuffer>) fieldData.get(field.alias);
+    }
+
+    public int readInt(String name, int defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Integer) fieldData.get(field.alias)).intValue();
+    }
+
+    public int[] readIntArray(String name, int[] defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (int[]) fieldData.get(field.alias);
+    }
+
+    public int[][] readIntArray2D(String name, int[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (int[][]) fieldData.get(field.alias);
+    }
+
+    public IntBuffer readIntBuffer(String name, IntBuffer defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (IntBuffer) fieldData.get(field.alias);
+    }
+
+    public long readLong(String name, long defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Long) fieldData.get(field.alias)).longValue();
+    }
+
+    public long[] readLongArray(String name, long[] defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (long[]) fieldData.get(field.alias);
+    }
+
+    public long[][] readLongArray2D(String name, long[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (long[][]) fieldData.get(field.alias);
+    }
+
+    public Savable readSavable(String name, Savable defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value == null)
+            return null;
+        else if (value instanceof ID) {
+            value = importer.readObject(((ID) value).id);
+            fieldData.put(field.alias, value);
+            return (Savable) value;
+        } else
+            return defVal;
+    }
+
+    public Savable[] readSavableArray(String name, Savable[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object[] values = (Object[]) fieldData.get(field.alias);
+        if (values instanceof ID[]) {
+            values = resolveIDs(values);
+            fieldData.put(field.alias, values);
+            return (Savable[]) values;
+        } else
+            return defVal;
+    }
+
+    private Savable[] resolveIDs(Object[] values) {
+        if (values != null) {
+            Savable[] savables = new Savable[values.length];
+            for (int i = 0; i < values.length; i++) {
+                final ID id = (ID) values[i];
+                savables[i] = id != null ? importer.readObject(id.id) : null;
+            }
+            return savables;
+        } else {
+            return null;
+        }
+    }
+
+    public Savable[][] readSavableArray2D(String name, Savable[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null ||!fieldData.containsKey(field.alias))
+            return defVal;
+        Object[][] values = (Object[][]) fieldData.get(field.alias);
+        if (values instanceof ID[][]) {
+            Savable[][] savables = new Savable[values.length][];
+            for (int i = 0; i < values.length; i++) {
+                if (values[i] != null) {
+                    savables[i] = resolveIDs(values[i]);
+                } else savables[i] = null;
+            }
+            values = savables;
+            fieldData.put(field.alias, values);
+        }
+        return (Savable[][]) values;
+    }
+
+    public Savable[][][] readSavableArray3D(String name, Savable[][][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object[][][] values = (Object[][][]) fieldData.get(field.alias);
+        if (values instanceof ID[][][]) {
+            Savable[][][] savables = new Savable[values.length][][];
+            for (int i = 0; i < values.length; i++) {
+                if (values[i] != null) {
+                    savables[i] = new Savable[values[i].length][];
+                    for (int j = 0; j < values[i].length; j++) {
+                        savables[i][j] = resolveIDs(values[i][j]);
+                    }
+                } else savables[i] = null;
+            }
+            fieldData.put(field.alias, savables);
+            return savables;
+        } else
+            return defVal;
+    }
+
+    private ArrayList<Savable> savableArrayListFromArray(Savable[] savables) {
+        if(savables == null) {
+            return null;
+        }
+        ArrayList<Savable> arrayList = new ArrayList<Savable>(savables.length);
+        for (int x = 0; x < savables.length; x++) {
+            arrayList.add(savables[x]);
+        }
+        return arrayList;
+    }
+
+    // Assumes array of size 2 arrays where pos 0 is key and pos 1 is value.
+    private Map<Savable, Savable> savableMapFrom2DArray(Savable[][] savables) {
+        if(savables == null) {
+            return null;
+        }
+        Map<Savable, Savable> map = new HashMap<Savable, Savable>(savables.length);
+        for (int x = 0; x < savables.length; x++) {
+            map.put(savables[x][0], savables[x][1]);
+        }
+        return map;
+    }
+
+    private Map<String, Savable> stringSavableMapFromKV(String[] keys, Savable[] values) {
+        if(keys == null || values == null) {
+            return null;
+        }
+
+        Map<String, Savable> map = new HashMap<String, Savable>(keys.length);
+        for (int x = 0; x < keys.length; x++)
+            map.put(keys[x], values[x]);
+
+        return map;
+    }
+
+    private IntMap<Savable> intSavableMapFromKV(int[] keys, Savable[] values) {
+        if(keys == null || values == null) {
+            return null;
+        }
+
+        IntMap<Savable> map = new IntMap<Savable>(keys.length);
+        for (int x = 0; x < keys.length; x++)
+            map.put(keys[x], values[x]);
+
+        return map;
+    }
+
+    public ArrayList readSavableArrayList(String name, ArrayList defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof ID[]) {
+            // read Savable array and convert to ArrayList
+            Savable[] savables = readSavableArray(name, null);
+            value = savableArrayListFromArray(savables);
+            fieldData.put(field.alias, value);
+        }
+        return (ArrayList) value;
+    }
+
+    public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof ID[][]) {
+            // read 2D Savable array and convert to ArrayList array
+            Savable[][] savables = readSavableArray2D(name, null);
+            if (savables != null) {
+                ArrayList[] arrayLists = new ArrayList[savables.length];
+                for (int i = 0; i < savables.length; i++) {
+                    arrayLists[i] = savableArrayListFromArray(savables[i]);
+                }
+                value = arrayLists;
+            } else
+                value = defVal;
+            fieldData.put(field.alias, value);
+        }
+        return (ArrayList[]) value;
+    }
+
+    public ArrayList[][] readSavableArrayListArray2D(String name,
+            ArrayList[][] defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof ID[][][]) {
+            // read 3D Savable array and convert to 2D ArrayList array
+            Savable[][][] savables = readSavableArray3D(name, null);
+            if (savables != null && savables.length > 0) {
+                ArrayList[][] arrayLists = new ArrayList[savables.length][];
+                for (int i = 0; i < savables.length; i++) {
+                    arrayLists[i] = new ArrayList[savables[i].length];
+                    for (int j = 0; j < savables[i].length; j++) {
+                        arrayLists[i][j] = savableArrayListFromArray(savables[i][j]);
+                    }
+                }
+                value = arrayLists;
+            } else
+                value = defVal;
+            fieldData.put(field.alias, value);
+        }
+        return (ArrayList[][]) value;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<? extends Savable, ? extends Savable> readSavableMap(String name, Map<? extends Savable, ? extends Savable> defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof ID[][]) {
+            // read Savable array and convert to Map
+            Savable[][] savables = readSavableArray2D(name, null);
+            value = savableMapFrom2DArray(savables);
+            fieldData.put(field.alias, value);
+        }
+        return (Map<? extends Savable, ? extends Savable>) value;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, ? extends Savable> readStringSavableMap(String name, Map<String, ? extends Savable> defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof StringIDMap) {
+            // read Savable array and convert to Map values
+            StringIDMap in = (StringIDMap) value;
+            Savable[] values = resolveIDs(in.values);
+            value = stringSavableMapFromKV(in.keys, values);
+            fieldData.put(field.alias, value);
+        }
+        return (Map<String, Savable>) value;
+    }
+
+    @SuppressWarnings("unchecked")
+    public IntMap<? extends Savable> readIntSavableMap(String name, IntMap<? extends Savable> defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        Object value = fieldData.get(field.alias);
+        if (value instanceof IntIDMap) {
+            // read Savable array and convert to Map values
+            IntIDMap in = (IntIDMap) value;
+            Savable[] values = resolveIDs(in.values);
+            value = intSavableMapFromKV(in.keys, values);
+            fieldData.put(field.alias, value);
+        }
+        return (IntMap<Savable>) value;
+    }
+
+    public short readShort(String name, short defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return ((Short) fieldData.get(field.alias)).shortValue();
+    }
+
+    public short[] readShortArray(String name, short[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (short[]) fieldData.get(field.alias);
+    }
+
+    public short[][] readShortArray2D(String name, short[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (short[][]) fieldData.get(field.alias);
+    }
+
+    public ShortBuffer readShortBuffer(String name, ShortBuffer defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (ShortBuffer) fieldData.get(field.alias);
+    }
+
+    public String readString(String name, String defVal) throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (String) fieldData.get(field.alias);
+    }
+
+    public String[] readStringArray(String name, String[] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (String[]) fieldData.get(field.alias);
+    }
+
+    public String[][] readStringArray2D(String name, String[][] defVal)
+            throws IOException {
+        BinaryClassField field = cObj.nameFields.get(name);
+        if (field == null || !fieldData.containsKey(field.alias))
+            return defVal;
+        return (String[][]) fieldData.get(field.alias);
+    }
+
+    // byte primitive
+
+    protected byte readByte(byte[] content) throws IOException {
+        byte value = content[index];
+        index++;
+        return value;
+    }
+
+    protected byte readByteForBuffer(byte[] content) throws IOException {
+        byte value = content[index];
+        index++;
+        return value;
+    }
+
+    protected byte[] readByteArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        byte[] value = new byte[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readByte(content);
+        return value;
+    }
+
+    protected byte[][] readByteArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        byte[][] value = new byte[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readByteArray(content);
+        return value;
+    }
+
+    // int primitive
+
+    protected int readIntForBuffer(byte[] content){
+        int number = ((content[index+3] & 0xFF) << 24)
+                   + ((content[index+2] & 0xFF) << 16)
+                   + ((content[index+1] & 0xFF) << 8)
+                   +  (content[index]   & 0xFF);
+        index += 4;
+        return number;
+    }
+
+    protected int readInt(byte[] content) throws IOException {
+        byte[] bytes = inflateFrom(content, index);
+        index += 1 + bytes.length;
+        bytes = ByteUtils.rightAlignBytes(bytes, 4);
+        int value = ByteUtils.convertIntFromBytes(bytes);
+        if (value == BinaryOutputCapsule.NULL_OBJECT
+                || value == BinaryOutputCapsule.DEFAULT_OBJECT)
+            index -= 4;
+        return value;
+    }
+
+    protected int[] readIntArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        int[] value = new int[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readInt(content);
+        return value;
+    }
+
+    protected int[][] readIntArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        int[][] value = new int[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readIntArray(content);
+        return value;
+    }
+
+    // float primitive
+
+    protected float readFloat(byte[] content) throws IOException {
+        float value = ByteUtils.convertFloatFromBytes(content, index);
+        index += 4;
+        return value;
+    }
+
+    protected float readFloatForBuffer(byte[] content) throws IOException {
+        int number = readIntForBuffer(content);
+        return Float.intBitsToFloat(number);
+    }
+
+    protected float[] readFloatArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        float[] value = new float[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readFloat(content);
+        return value;
+    }
+
+    protected float[][] readFloatArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        float[][] value = new float[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readFloatArray(content);
+        return value;
+    }
+
+    // double primitive
+
+    protected double readDouble(byte[] content) throws IOException {
+        double value = ByteUtils.convertDoubleFromBytes(content, index);
+        index += 8;
+        return value;
+    }
+
+    protected double[] readDoubleArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        double[] value = new double[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readDouble(content);
+        return value;
+    }
+
+    protected double[][] readDoubleArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        double[][] value = new double[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readDoubleArray(content);
+        return value;
+    }
+
+    // long primitive
+
+    protected long readLong(byte[] content) throws IOException {
+        byte[] bytes = inflateFrom(content, index);
+        index += 1 + bytes.length;
+        bytes = ByteUtils.rightAlignBytes(bytes, 8);
+        long value = ByteUtils.convertLongFromBytes(bytes);
+        return value;
+    }
+
+    protected long[] readLongArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        long[] value = new long[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readLong(content);
+        return value;
+    }
+
+    protected long[][] readLongArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        long[][] value = new long[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readLongArray(content);
+        return value;
+    }
+
+    // short primitive
+
+    protected short readShort(byte[] content) throws IOException {
+        short value = ByteUtils.convertShortFromBytes(content, index);
+        index += 2;
+        return value;
+    }
+
+    protected short readShortForBuffer(byte[] content) throws IOException {
+        short number = (short) ((content[index+0] & 0xFF)
+                             + ((content[index+1] & 0xFF) << 8));
+        index += 2;
+        return number;
+    }
+
+    protected short[] readShortArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        short[] value = new short[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readShort(content);
+        return value;
+    }
+
+    protected short[][] readShortArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        short[][] value = new short[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readShortArray(content);
+        return value;
+    }
+
+    // boolean primitive
+
+    protected boolean readBoolean(byte[] content) throws IOException {
+        boolean value = ByteUtils.convertBooleanFromBytes(content, index);
+        index += 1;
+        return value;
+    }
+
+    protected boolean[] readBooleanArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        boolean[] value = new boolean[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readBoolean(content);
+        return value;
+    }
+
+    protected boolean[][] readBooleanArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        boolean[][] value = new boolean[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readBooleanArray(content);
+        return value;
+    }
+
+    /*
+     * UTF-8 crash course:
+     *
+     * UTF-8 codepoints map to UTF-16 codepoints and vv, which is what Java uses for it's Strings.
+     * (so a UTF-8 codepoint can contain all possible values for a Java char)
+     *
+     * A UTF-8 codepoint can be 1, 2 or 3 bytes long. How long a codepint is can be told by reading the first byte:
+     * b < 0x80, 1 byte
+     * (b & 0xC0) == 0xC0, 2 bytes
+     * (b & 0xE0) == 0xE0, 3 bytes
+     *
+     * However there is an additional restriction to UTF-8, to enable you to find the start of a UTF-8 codepoint,
+     * if you start reading at a random point in a UTF-8 byte stream. That's why UTF-8 requires for the second and third byte of
+     * a multibyte codepoint:
+     * (b & 0x80) == 0x80  (in other words, first bit must be 1)
+     */
+    private final static int UTF8_START = 0; // next byte should be the start of a new
+    private final static int UTF8_2BYTE = 2; // next byte should be the second byte of a 2 byte codepoint
+    private final static int UTF8_3BYTE_1 = 3; // next byte should be the second byte of a 3 byte codepoint
+    private final static int UTF8_3BYTE_2 = 4; // next byte should be the third byte of a 3 byte codepoint
+    private final static int UTF8_ILLEGAL = 10; // not an UTF8 string
+
+    // String
+    protected String readString(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+
+        /*
+         * @see ISSUE 276
+         *
+         * We'll transfer the bytes into a seperate byte array.
+         * While we do that we'll take the opportunity to check if the byte data is valid UTF-8.
+         *
+         * If it is not UTF-8 it is most likely saved with the BinaryOutputCapsule bug, that saves Strings using their native
+         * encoding. Unfortunatly there is no way to know what encoding was used, so we'll parse using the most common one in
+         * that case; latin-1 aka ISO8859_1
+         *
+         * Encoding of "low" ASCII codepoint (in plain speak: when no special characters are used) will usually look the same
+         * for UTF-8 and the other 1 byte codepoint encodings (espc true for numbers and regular letters of the alphabet). So these
+         * are valid UTF-8 and will give the same result (at most a few charakters will appear different, such as the euro sign).
+         *
+         * However, when "high" codepoints are used (any codepoint that over 0x7F, in other words where the first bit is a 1) it's
+         * a different matter and UTF-8 and the 1 byte encoding greatly will differ, as well as most 1 byte encodings relative to each
+         * other.
+         *
+         * It is impossible to detect which one-byte encoding is used. Since UTF8 and practically all 1-byte encodings share the most
+         * used characters (the "none-high" ones) parsing them will give the same result. However, not all byte sequences are legal in
+         * UTF-8 (see explantion above). If not UTF-8 encoded content is detected we therefor fallback on latin1. We also log a warning.
+         *
+         * By this method we detect all use of 1 byte encoding if they:
+         * - use a "high" codepoint after a "low" codepoint or a sequence of codepoints that is valid as UTF-8 bytes, that starts with 1000
+         * - use a "low" codepoint after a "high" codepoint
+         * - use a "low" codepoint after "high" codepoint, after a "high" codepoint that starts with 1110
+         *
+         *  In practise this means that unless 2 or 3 "high" codepoints are used after each other in proper order, we'll detect the string
+         *  was not originally UTF-8 encoded.
+         *
+         */
+        byte[] bytes = new byte[length];
+        int utf8State = UTF8_START;
+        int b;
+        for (int x = 0; x < length; x++) {
+            bytes[x] =  content[index++];
+            b = (int) bytes[x] & 0xFF; // unsign our byte
+
+            switch (utf8State) {
+            case UTF8_START:
+                if (b < 0x80) {
+                    // good
+                }
+                else if ((b & 0xC0) == 0xC0) {
+                    utf8State = UTF8_2BYTE;
+                }
+                else if ((b & 0xE0) == 0xE0) {
+                    utf8State = UTF8_3BYTE_1;
+                }
+                else {
+                    utf8State = UTF8_ILLEGAL;
+                }
+                break;
+            case UTF8_3BYTE_1:
+            case UTF8_3BYTE_2:
+            case UTF8_2BYTE:
+                 if ((b & 0x80) == 0x80)
+                    utf8State = utf8State == UTF8_3BYTE_1 ? UTF8_3BYTE_2 : UTF8_START;
+                 else
+                    utf8State = UTF8_ILLEGAL;
+                break;
+            }
+        }
+
+        try {
+            // even though so far the parsing might have been a legal UTF-8 sequence, only if a codepoint is fully given is it correct UTF-8
+            if (utf8State == UTF8_START) {
+                // Java misspells UTF-8 as UTF8 for official use in java.lang
+                return new String(bytes, "UTF8");
+            }
+            else {
+                logger.log(
+                        Level.WARNING,
+                        "Your export has been saved with an incorrect encoding for it's String fields which means it might not load correctly " +
+                        "due to encoding issues. You should probably re-export your work. See ISSUE 276 in the jME issue tracker."
+                );
+                // We use ISO8859_1 to be consistent across platforms. We could default to native encoding, but this would lead to inconsistent
+                // behaviour across platforms!
+                // Developers that have previously saved their exports using the old exporter (wich uses native encoding), can temporarly
+                // remove the ""ISO8859_1" parameter, and change the above if statement to "if (false)".
+                // They should then import and re-export their models using the same enviroment they were orginally created in.
+                return new String(bytes, "ISO8859_1");
+            }
+        } catch (UnsupportedEncodingException uee) {
+            // as a last resort fall back to platform native.
+            // JavaDoc is vague about what happens when a decoding a String that contains un undecodable sequence
+            // it also doesn't specify which encodings have to be supported (though UTF-8 and ISO8859 have been in the SUN JRE since at least 1.1)
+            logger.log(
+                    Level.SEVERE,
+                    "Your export has been saved with an incorrect encoding or your version of Java is unable to decode the stored string. " +
+                    "While your export may load correctly by falling back, using it on different platforms or java versions might lead to "+
+                    "very strange inconsitenties. You should probably re-export your work. See ISSUE 276 in the jME issue tracker."
+            );
+            return new String(bytes);
+        }
+    }
+
+    protected String[] readStringArray(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        String[] value = new String[length];
+        for (int x = 0; x < length; x++)
+            value[x] = readString(content);
+        return value;
+    }
+
+    protected String[][] readStringArray2D(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        String[][] value = new String[length][];
+        for (int x = 0; x < length; x++)
+            value[x] = readStringArray(content);
+        return value;
+    }
+
+    // BitSet
+
+    protected BitSet readBitSet(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        BitSet value = new BitSet(length);
+        for (int x = 0; x < length; x++)
+            value.set(x, readBoolean(content));
+        return value;
+    }
+
+    // INFLATOR for int and long
+
+    protected static byte[] inflateFrom(byte[] contents, int index) {
+        byte firstByte = contents[index];
+        if (firstByte == BinaryOutputCapsule.NULL_OBJECT)
+            return ByteUtils.convertToBytes(BinaryOutputCapsule.NULL_OBJECT);
+        else if (firstByte == BinaryOutputCapsule.DEFAULT_OBJECT)
+            return ByteUtils.convertToBytes(BinaryOutputCapsule.DEFAULT_OBJECT);
+        else if (firstByte == 0)
+            return new byte[0];
+        else {
+            byte[] rVal = new byte[firstByte];
+            for (int x = 0; x < rVal.length; x++)
+                rVal[x] = contents[x + 1 + index];
+            return rVal;
+        }
+    }
+
+    // BinarySavable
+
+    protected ID readSavable(byte[] content) throws IOException {
+        int id = readInt(content);
+        if (id == BinaryOutputCapsule.NULL_OBJECT) {
+            return null;
+        }
+
+        return new ID(id);
+    }
+
+    // BinarySavable array
+
+    protected ID[] readSavableArray(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        ID[] rVal = new ID[elements];
+        for (int x = 0; x < elements; x++) {
+            rVal[x] = readSavable(content);
+        }
+        return rVal;
+    }
+
+    protected ID[][] readSavableArray2D(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        ID[][] rVal = new ID[elements][];
+        for (int x = 0; x < elements; x++) {
+            rVal[x] = readSavableArray(content);
+        }
+        return rVal;
+    }
+
+    protected ID[][][] readSavableArray3D(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        ID[][][] rVal = new ID[elements][][];
+        for (int x = 0; x < elements; x++) {
+            rVal[x] = readSavableArray2D(content);
+        }
+        return rVal;
+    }
+
+    // BinarySavable map
+
+    protected ID[][] readSavableMap(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        ID[][] rVal = new ID[elements][];
+        for (int x = 0; x < elements; x++) {
+            rVal[x] = readSavableArray(content);
+        }
+        return rVal;
+    }
+
+    protected StringIDMap readStringSavableMap(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        String[] keys = readStringArray(content);
+        ID[] values = readSavableArray(content);
+        StringIDMap rVal = new StringIDMap();
+        rVal.keys = keys;
+        rVal.values = values;
+        return rVal;
+    }
+
+    protected IntIDMap readIntSavableMap(byte[] content) throws IOException {
+        int elements = readInt(content);
+        if (elements == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+        int[] keys = readIntArray(content);
+        ID[] values = readSavableArray(content);
+        IntIDMap rVal = new IntIDMap();
+        rVal.keys = keys;
+        rVal.values = values;
+        return rVal;
+    }
+
+
+    // ArrayList<FloatBuffer>
+
+    protected ArrayList<FloatBuffer> readFloatBufferArrayList(byte[] content)
+            throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT) {
+            return null;
+        }
+        ArrayList<FloatBuffer> rVal = new ArrayList<FloatBuffer>(length);
+        for (int x = 0; x < length; x++) {
+            rVal.add(readFloatBuffer(content));
+        }
+        return rVal;
+    }
+
+    // ArrayList<ByteBuffer>
+
+    protected ArrayList<ByteBuffer> readByteBufferArrayList(byte[] content)
+            throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT) {
+            return null;
+        }
+        ArrayList<ByteBuffer> rVal = new ArrayList<ByteBuffer>(length);
+        for (int x = 0; x < length; x++) {
+            rVal.add(readByteBuffer(content));
+        }
+        return rVal;
+    }
+
+    // NIO BUFFERS
+    // float buffer
+
+    protected FloatBuffer readFloatBuffer(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+
+        if (BinaryImporter.canUseFastBuffers()){
+            ByteBuffer value = BufferUtils.createByteBuffer(length * 4);
+            value.put(content, index, length * 4).rewind();
+            index += length * 4;
+            return value.asFloatBuffer();
+        }else{
+            FloatBuffer value = BufferUtils.createFloatBuffer(length);
+            for (int x = 0; x < length; x++) {
+                value.put(readFloatForBuffer(content));
+            }
+            value.rewind();
+            return value;
+        }
+    }
+
+    // int buffer
+
+    protected IntBuffer readIntBuffer(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+
+        if (BinaryImporter.canUseFastBuffers()){
+            ByteBuffer value = BufferUtils.createByteBuffer(length * 4);
+            value.put(content, index, length * 4).rewind();
+            index += length * 4;
+            return value.asIntBuffer();
+        }else{
+            IntBuffer value = BufferUtils.createIntBuffer(length);
+            for (int x = 0; x < length; x++) {
+                value.put(readIntForBuffer(content));
+            }
+            value.rewind();
+            return value;
+        }
+    }
+
+    // byte buffer
+
+    protected ByteBuffer readByteBuffer(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+
+        if (BinaryImporter.canUseFastBuffers()){
+            ByteBuffer value = BufferUtils.createByteBuffer(length);
+            value.put(content, index, length).rewind();
+            index += length;
+            return value;
+        }else{
+            ByteBuffer value = BufferUtils.createByteBuffer(length);
+            for (int x = 0; x < length; x++) {
+                value.put(readByteForBuffer(content));
+            }
+            value.rewind();
+            return value;
+        }
+    }
+
+    // short buffer
+
+    protected ShortBuffer readShortBuffer(byte[] content) throws IOException {
+        int length = readInt(content);
+        if (length == BinaryOutputCapsule.NULL_OBJECT)
+            return null;
+
+        if (BinaryImporter.canUseFastBuffers()){
+            ByteBuffer value = BufferUtils.createByteBuffer(length * 2);
+            value.put(content, index, length * 2).rewind();
+            index += length * 2;
+            return value.asShortBuffer();
+        }else{
+            ShortBuffer value = BufferUtils.createShortBuffer(length);
+            for (int x = 0; x < length; x++) {
+                value.put(readShortForBuffer(content));
+            }
+            value.rewind();
+            return value;
+        }
+    }
+
+    static private class ID {
+        public int id;
+
+        public ID(int id) {
+            this.id = id;
+        }
+    }
+
+    static private class StringIDMap {
+        public String[] keys;
+        public ID[] values;
+    }
+
+    static private class IntIDMap {
+        public int[] keys;
+        public ID[] values;
+    }
+
+    public <T extends Enum<T>> T readEnum(String name, Class<T> enumType, T defVal) throws IOException {
+        String eVal = readString(name, defVal != null ? defVal.name() : null);
+        if (eVal != null) {
+            return Enum.valueOf(enumType, eVal);
+        } else {
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryOutputCapsule.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryOutputCapsule.java
new file mode 100644
index 0000000..4466db5
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryOutputCapsule.java
@@ -0,0 +1,944 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Map;
+
+/**
+ * @author Joshua Slack
+ */
+final class BinaryOutputCapsule implements OutputCapsule {
+
+    public static final int NULL_OBJECT = -1;
+    public static final int DEFAULT_OBJECT = -2;
+
+    public static byte[] NULL_BYTES = new byte[] { (byte) -1 };
+    public static byte[] DEFAULT_BYTES = new byte[] { (byte) -2 };
+
+    protected ByteArrayOutputStream baos;
+    protected byte[] bytes;
+    protected BinaryExporter exporter;
+    protected BinaryClassObject cObj;
+
+    public BinaryOutputCapsule(BinaryExporter exporter, BinaryClassObject bco) {
+        this.baos = new ByteArrayOutputStream();
+        this.exporter = exporter;
+        this.cObj = bco;
+    }
+
+    public void write(byte value, String name, byte defVal) throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BYTE);
+        write(value);
+    }
+
+    public void write(byte[] value, String name, byte[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BYTE_1D);
+        write(value);
+    }
+
+    public void write(byte[][] value, String name, byte[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BYTE_2D);
+        write(value);
+    }
+
+    public void write(int value, String name, int defVal) throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.INT);
+        write(value);
+    }
+
+    public void write(int[] value, String name, int[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.INT_1D);
+        write(value);
+    }
+
+    public void write(int[][] value, String name, int[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.INT_2D);
+        write(value);
+    }
+
+    public void write(float value, String name, float defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.FLOAT);
+        write(value);
+    }
+
+    public void write(float[] value, String name, float[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.FLOAT_1D);
+        write(value);
+    }
+
+    public void write(float[][] value, String name, float[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.FLOAT_2D);
+        write(value);
+    }
+
+    public void write(double value, String name, double defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.DOUBLE);
+        write(value);
+    }
+
+    public void write(double[] value, String name, double[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.DOUBLE_1D);
+        write(value);
+    }
+
+    public void write(double[][] value, String name, double[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.DOUBLE_2D);
+        write(value);
+    }
+
+    public void write(long value, String name, long defVal) throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.LONG);
+        write(value);
+    }
+
+    public void write(long[] value, String name, long[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.LONG_1D);
+        write(value);
+    }
+
+    public void write(long[][] value, String name, long[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.LONG_2D);
+        write(value);
+    }
+
+    public void write(short value, String name, short defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SHORT);
+        write(value);
+    }
+
+    public void write(short[] value, String name, short[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SHORT_1D);
+        write(value);
+    }
+
+    public void write(short[][] value, String name, short[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SHORT_2D);
+        write(value);
+    }
+
+    public void write(boolean value, String name, boolean defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BOOLEAN);
+        write(value);
+    }
+
+    public void write(boolean[] value, String name, boolean[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BOOLEAN_1D);
+        write(value);
+    }
+
+    public void write(boolean[][] value, String name, boolean[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BOOLEAN_2D);
+        write(value);
+    }
+
+    public void write(String value, String name, String defVal)
+            throws IOException {
+        if (value == null ? defVal == null : value.equals(defVal))
+            return;
+        writeAlias(name, BinaryClassField.STRING);
+        write(value);
+    }
+
+    public void write(String[] value, String name, String[] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.STRING_1D);
+        write(value);
+    }
+
+    public void write(String[][] value, String name, String[][] defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.STRING_2D);
+        write(value);
+    }
+
+    public void write(BitSet value, String name, BitSet defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BITSET);
+        write(value);
+    }
+
+    public void write(Savable object, String name, Savable defVal)
+            throws IOException {
+        if (object == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE);
+        write(object);
+    }
+
+    public void write(Savable[] objects, String name, Savable[] defVal)
+            throws IOException {
+        if (objects == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_1D);
+        write(objects);
+    }
+
+    public void write(Savable[][] objects, String name, Savable[][] defVal)
+            throws IOException {
+        if (objects == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_2D);
+        write(objects);
+    }
+
+    public void write(FloatBuffer value, String name, FloatBuffer defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.FLOATBUFFER);
+        write(value);
+    }
+
+    public void write(IntBuffer value, String name, IntBuffer defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.INTBUFFER);
+        write(value);
+    }
+
+    public void write(ByteBuffer value, String name, ByteBuffer defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BYTEBUFFER);
+        write(value);
+    }
+
+    public void write(ShortBuffer value, String name, ShortBuffer defVal)
+            throws IOException {
+        if (value == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SHORTBUFFER);
+        write(value);
+    }
+
+    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array,
+            String name, ArrayList<FloatBuffer> defVal) throws IOException {
+        if (array == defVal)
+            return;
+        writeAlias(name, BinaryClassField.FLOATBUFFER_ARRAYLIST);
+        writeFloatBufferArrayList(array);
+    }
+
+    public void writeByteBufferArrayList(ArrayList<ByteBuffer> array,
+            String name, ArrayList<ByteBuffer> defVal) throws IOException {
+        if (array == defVal)
+            return;
+        writeAlias(name, BinaryClassField.BYTEBUFFER_ARRAYLIST);
+        writeByteBufferArrayList(array);
+    }
+
+    public void writeSavableArrayList(ArrayList array, String name,
+            ArrayList defVal) throws IOException {
+        if (array == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST);
+        writeSavableArrayList(array);
+    }
+
+    public void writeSavableArrayListArray(ArrayList[] array, String name,
+            ArrayList[] defVal) throws IOException {
+        if (array == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_1D);
+        writeSavableArrayListArray(array);
+    }
+
+    public void writeSavableArrayListArray2D(ArrayList[][] array, String name,
+            ArrayList[][] defVal) throws IOException {
+        if (array == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_2D);
+        writeSavableArrayListArray2D(array);
+    }
+
+    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map,
+            String name, Map<? extends Savable, ? extends Savable> defVal)
+            throws IOException {
+        if (map == defVal)
+            return;
+        writeAlias(name, BinaryClassField.SAVABLE_MAP);
+        writeSavableMap(map);
+    }
+
+    public void writeStringSavableMap(Map<String, ? extends Savable> map,
+            String name, Map<String, ? extends Savable> defVal)
+            throws IOException {
+        if (map == defVal)
+            return;
+        writeAlias(name, BinaryClassField.STRING_SAVABLE_MAP);
+        writeStringSavableMap(map);
+    }
+
+    public void writeIntSavableMap(IntMap<? extends Savable> map,
+            String name, IntMap<? extends Savable> defVal)
+            throws IOException {
+        if (map == defVal)
+            return;
+        writeAlias(name, BinaryClassField.INT_SAVABLE_MAP);
+        writeIntSavableMap(map);
+    }
+
+    protected void writeAlias(String name, byte fieldType) throws IOException {
+        if (cObj.nameFields.get(name) == null)
+            generateAlias(name, fieldType);
+
+        byte alias = cObj.nameFields.get(name).alias;
+        write(alias);
+    }
+
+    // XXX: The generation of aliases is limited to 256 possible values.
+    // If we run into classes with more than 256 fields, we need to expand this.
+    // But I mean, come on...
+    protected void generateAlias(String name, byte type) {
+        byte alias = (byte) cObj.nameFields.size();
+        cObj.nameFields.put(name, new BinaryClassField(name, alias, type));
+    }
+
+    @Override
+    public boolean equals(Object arg0) {
+        if (!(arg0 instanceof BinaryOutputCapsule))
+            return false;
+
+        byte[] other = ((BinaryOutputCapsule) arg0).bytes;
+        if (bytes.length != other.length)
+            return false;
+        return Arrays.equals(bytes, other);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 23 * hash + Arrays.hashCode(this.bytes);
+        return hash;
+    }
+
+    public void finish() {
+        // renamed to finish as 'finalize' in java.lang.Object should not be
+        // overridden like this
+        // - finalize should not be called directly but is called by garbage
+        // collection!!!
+        bytes = baos.toByteArray();
+        baos = null;
+    }
+
+    // byte primitive
+
+    protected void write(byte value) throws IOException {
+        baos.write(value);
+    }
+
+    protected void writeForBuffer(byte value) throws IOException {
+        baos.write(value);
+    }
+    
+    protected void write(byte[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        baos.write(value);
+    }
+
+    protected void write(byte[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // int primitive
+
+    protected void write(int value) throws IOException {
+        baos.write(deflate(ByteUtils.convertToBytes(value)));
+    }
+
+    protected void writeForBuffer(int value) throws IOException {
+        byte[] byteArray = new byte[4];
+        byteArray[0] = (byte) value;
+        byteArray[1] = (byte) (value >> 8);
+        byteArray[2] = (byte) (value >> 16);
+        byteArray[3] = (byte) (value >> 24);
+        baos.write(byteArray);
+    }
+
+    protected void write(int[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(int[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // float primitive
+
+    protected void write(float value) throws IOException {
+        baos.write(ByteUtils.convertToBytes(value));
+    }
+
+    protected void writeForBuffer(float value) throws IOException {
+        int integer = Float.floatToIntBits(value);
+        writeForBuffer(integer);
+    }
+
+    protected void write(float[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(float[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // double primitive
+
+    protected void write(double value) throws IOException {
+        baos.write(ByteUtils.convertToBytes(value));
+    }
+
+    protected void write(double[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(double[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // long primitive
+
+    protected void write(long value) throws IOException {
+        baos.write(deflate(ByteUtils.convertToBytes(value)));
+    }
+
+    protected void write(long[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(long[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // short primitive
+
+    protected void write(short value) throws IOException {
+        baos.write(ByteUtils.convertToBytes(value));
+    }
+
+    protected void writeForBuffer(short value) throws IOException {
+        byte[] byteArray = new byte[2];
+        byteArray[0] = (byte) value;
+        byteArray[1] = (byte) (value >> 8);
+        baos.write(byteArray);
+    }
+
+    protected void write(short[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(short[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // boolean primitive
+
+    protected void write(boolean value) throws IOException {
+        baos.write(ByteUtils.convertToBytes(value));
+    }
+
+    protected void write(boolean[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(boolean[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // String
+
+    protected void write(String value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        // write our output as UTF-8. Java misspells UTF-8 as UTF8 for official use in java.lang
+        byte[] bytes = value.getBytes("UTF8");
+        write(bytes.length);
+        baos.write(bytes);
+    }
+
+    protected void write(String[] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    protected void write(String[][] value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.length);
+        for (int x = 0; x < value.length; x++)
+            write(value[x]);
+    }
+
+    // BitSet
+
+    protected void write(BitSet value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(value.size());
+        // TODO: MAKE THIS SMALLER
+        for (int x = 0, max = value.size(); x < max; x++)
+            write(value.get(x));
+    }
+
+    // DEFLATOR for int and long
+
+    protected static byte[] deflate(byte[] bytes) {
+        int size = bytes.length;
+        if (size == 4) {
+            int possibleMagic = ByteUtils.convertIntFromBytes(bytes);
+            if (possibleMagic == NULL_OBJECT)
+                return NULL_BYTES;
+            else if (possibleMagic == DEFAULT_OBJECT)
+                return DEFAULT_BYTES;
+        }
+        for (int x = 0; x < bytes.length; x++) {
+            if (bytes[x] != 0)
+                break;
+            size--;
+        }
+        if (size == 0)
+            return new byte[1];
+
+        byte[] rVal = new byte[1 + size];
+        rVal[0] = (byte) size;
+        for (int x = 1; x < rVal.length; x++)
+            rVal[x] = bytes[bytes.length - size - 1 + x];
+
+        return rVal;
+    }
+
+    // BinarySavable
+
+    protected void write(Savable object) throws IOException {
+        if (object == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        int id = exporter.processBinarySavable(object);
+        write(id);
+    }
+
+    // BinarySavable array
+
+    protected void write(Savable[] objects) throws IOException {
+        if (objects == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(objects.length);
+        for (int x = 0; x < objects.length; x++) {
+            write(objects[x]);
+        }
+    }
+
+    protected void write(Savable[][] objects) throws IOException {
+        if (objects == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(objects.length);
+        for (int x = 0; x < objects.length; x++) {
+            write(objects[x]);
+        }
+    }
+
+    // ArrayList<BinarySavable>
+
+    protected void writeSavableArrayList(ArrayList array) throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+        for (Object bs : array) {
+            write((Savable) bs);
+        }
+    }
+
+    protected void writeSavableArrayListArray(ArrayList[] array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.length);
+        for (ArrayList bs : array) {
+            writeSavableArrayList(bs);
+        }
+    }
+
+    protected void writeSavableArrayListArray2D(ArrayList[][] array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.length);
+        for (ArrayList[] bs : array) {
+            writeSavableArrayListArray(bs);
+        }
+    }
+
+    // Map<BinarySavable, BinarySavable>
+
+    protected void writeSavableMap(
+            Map<? extends Savable, ? extends Savable> array) throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+        for (Savable key : array.keySet()) {
+            write(new Savable[] { key, array.get(key) });
+        }
+    }
+
+    protected void writeStringSavableMap(Map<String, ? extends Savable> array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+
+        // write String array for keys
+        String[] keys = array.keySet().toArray(new String[] {});
+        write(keys);
+
+        // write Savable array for values
+        Savable[] values = array.values().toArray(new Savable[] {});
+        write(values);
+    }
+
+    protected void writeIntSavableMap(IntMap<? extends Savable> array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+
+        int[] keys = new int[array.size()];
+        Savable[] values = new Savable[keys.length];
+        int i = 0;
+        for (Entry<? extends Savable> entry : array){
+            keys[i] = entry.getKey();
+            values[i] = entry.getValue();
+            i++;
+        }
+
+        // write String array for keys
+        write(keys);
+
+        // write Savable array for values
+        write(values);
+    }
+
+    // ArrayList<FloatBuffer>
+
+    protected void writeFloatBufferArrayList(ArrayList<FloatBuffer> array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+        for (FloatBuffer buf : array) {
+            write(buf);
+        }
+    }
+
+    // ArrayList<FloatBuffer>
+
+    protected void writeByteBufferArrayList(ArrayList<ByteBuffer> array)
+            throws IOException {
+        if (array == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        write(array.size());
+        for (ByteBuffer buf : array) {
+            write(buf);
+        }
+    }
+
+    // NIO BUFFERS
+    // float buffer
+
+    protected void write(FloatBuffer value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        value.rewind();
+        int length = value.limit();
+        write(length);
+        for (int x = 0; x < length; x++) {
+            writeForBuffer(value.get());
+        }
+        value.rewind();
+    }
+
+    // int buffer
+
+    protected void write(IntBuffer value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        value.rewind();
+        int length = value.limit();
+        write(length);
+
+        for (int x = 0; x < length; x++) {
+            writeForBuffer(value.get());
+        }
+        value.rewind();
+    }
+
+    // byte buffer
+
+    protected void write(ByteBuffer value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        value.rewind();
+        int length = value.limit();
+        write(length);
+        for (int x = 0; x < length; x++) {
+            writeForBuffer(value.get());
+        }
+        value.rewind();
+    }
+
+    // short buffer
+
+    protected void write(ShortBuffer value) throws IOException {
+        if (value == null) {
+            write(NULL_OBJECT);
+            return;
+        }
+        value.rewind();
+        int length = value.limit();
+        write(length);
+        for (int x = 0; x < length; x++) {
+            writeForBuffer(value.get());
+        }
+        value.rewind();
+    }
+
+    public void write(Enum value, String name, Enum defVal) throws IOException {
+        if (value == defVal)
+            return;
+        if (value == null) {
+            return;
+        } else {
+            write(value.name(), name, null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core-plugins/com/jme3/export/binary/ByteUtils.java b/engine/src/core-plugins/com/jme3/export/binary/ByteUtils.java
new file mode 100644
index 0000000..bb835d0
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/export/binary/ByteUtils.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2009-2010 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.export.binary;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * <code>ByteUtils</code> is a helper class for converting numeric primitives
+ * to and from byte representations.
+ * 
+ * @author Joshua Slack
+ */
+public class ByteUtils {
+
+    /**
+     * Takes an InputStream and returns the complete byte content of it
+     * 
+     * @param inputStream
+     *            The input stream to read from
+     * @return The byte array containing the data from the input stream
+     * @throws java.io.IOException
+     *             thrown if there is a problem reading from the input stream
+     *             provided
+     */
+    public static byte[] getByteContent(InputStream inputStream)
+            throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(
+                16 * 1024);
+        byte[] buffer = new byte[1024];
+        int byteCount = -1;
+        byte[] data = null;
+
+        // Read the byte content into the output stream first
+        while ((byteCount = inputStream.read(buffer)) > 0) {
+            outputStream.write(buffer, 0, byteCount);
+        }
+
+        // Set data with byte content from stream
+        data = outputStream.toByteArray();
+
+        // Release resources
+        outputStream.close();
+
+        return data;
+    }
+
+    
+    // **********  byte <> short  METHODS  **********
+
+    /**
+     * Writes a short out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the short will be written to
+     * @param value
+     *            The short to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeShort(OutputStream outputStream, short value)
+            throws IOException {
+        byte[] byteArray = convertToBytes(value);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(short value) {
+        byte[] byteArray = new byte[2];
+
+        byteArray[0] = (byte) (value >> 8);
+        byteArray[1] = (byte) value;
+        return byteArray;
+    }
+
+    /**
+     * Read in a short from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the short
+     * @return A short, which is the next 2 bytes converted from the InputStream
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static short readShort(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[2];
+
+        // Read in the next 2 bytes
+        inputStream.read(byteArray);
+
+        short number = convertShortFromBytes(byteArray);
+
+        return number;
+    }
+
+    public static short convertShortFromBytes(byte[] byteArray) {
+        return convertShortFromBytes(byteArray, 0);
+    }
+
+    public static short convertShortFromBytes(byte[] byteArray, int offset) {
+        // Convert it to a short
+        short number = (short) ((byteArray[offset+1] & 0xFF) + ((byteArray[offset+0] & 0xFF) << 8));
+        return number;
+    }
+
+    
+    // **********  byte <> int  METHODS  **********
+
+    /**
+     * Writes an integer out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the integer will be written to
+     * @param integer
+     *            The integer to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeInt(OutputStream outputStream, int integer)
+            throws IOException {
+        byte[] byteArray = convertToBytes(integer);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(int integer) {
+        byte[] byteArray = new byte[4];
+
+        byteArray[0] = (byte) (integer >> 24);
+        byteArray[1] = (byte) (integer >> 16);
+        byteArray[2] = (byte) (integer >> 8);
+        byteArray[3] = (byte) integer;
+        return byteArray;
+    }
+
+    /**
+     * Read in an integer from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the integer
+     * @return An int, which is the next 4 bytes converted from the InputStream
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static int readInt(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[4];
+
+        // Read in the next 4 bytes
+        inputStream.read(byteArray);
+
+        int number = convertIntFromBytes(byteArray);
+
+        return number;
+    }
+
+    public static int convertIntFromBytes(byte[] byteArray) {
+        return convertIntFromBytes(byteArray, 0);
+    }
+    
+    public static int convertIntFromBytes(byte[] byteArray, int offset) {
+        // Convert it to an int
+        int number = ((byteArray[offset] & 0xFF) << 24)
+                + ((byteArray[offset+1] & 0xFF) << 16) + ((byteArray[offset+2] & 0xFF) << 8)
+                + (byteArray[offset+3] & 0xFF);
+        return number;
+    }
+
+    
+    // **********  byte <> long  METHODS  **********
+    
+    /**
+     * Writes a long out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the long will be written to
+     * @param value
+     *            The long to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeLong(OutputStream outputStream, long value)
+            throws IOException {
+        byte[] byteArray = convertToBytes(value);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(long n) {
+        byte[] bytes = new byte[8];
+
+        bytes[7] = (byte) (n);
+        n >>>= 8;
+        bytes[6] = (byte) (n);
+        n >>>= 8;
+        bytes[5] = (byte) (n);
+        n >>>= 8;
+        bytes[4] = (byte) (n);
+        n >>>= 8;
+        bytes[3] = (byte) (n);
+        n >>>= 8;
+        bytes[2] = (byte) (n);
+        n >>>= 8;
+        bytes[1] = (byte) (n);
+        n >>>= 8;
+        bytes[0] = (byte) (n);
+
+        return bytes;
+    }
+    
+    /**
+     * Read in a long from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the long
+     * @return A long, which is the next 8 bytes converted from the InputStream
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static long readLong(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[8];
+
+        // Read in the next 8 bytes
+        inputStream.read(byteArray);
+
+        long number = convertLongFromBytes(byteArray);
+
+        return number;
+    }
+
+    public static long convertLongFromBytes(byte[] bytes) {
+        return convertLongFromBytes(bytes, 0);
+    }
+
+    public static long convertLongFromBytes(byte[] bytes, int offset) {
+        // Convert it to an long
+        return    ((((long) bytes[offset+7]) & 0xFF) 
+                + ((((long) bytes[offset+6]) & 0xFF) << 8)
+                + ((((long) bytes[offset+5]) & 0xFF) << 16)
+                + ((((long) bytes[offset+4]) & 0xFF) << 24)
+                + ((((long) bytes[offset+3]) & 0xFF) << 32)
+                + ((((long) bytes[offset+2]) & 0xFF) << 40)
+                + ((((long) bytes[offset+1]) & 0xFF) << 48) 
+                + ((((long) bytes[offset+0]) & 0xFF) << 56));
+    }
+
+    
+    // **********  byte <> double  METHODS  **********
+    
+    /**
+     * Writes a double out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the double will be written to
+     * @param value
+     *            The double to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeDouble(OutputStream outputStream, double value)
+            throws IOException {
+        byte[] byteArray = convertToBytes(value);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(double n) {
+        long bits = Double.doubleToLongBits(n);
+        return convertToBytes(bits);
+    }
+    
+    /**
+     * Read in a double from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the double
+     * @return A double, which is the next 8 bytes converted from the InputStream
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static double readDouble(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[8];
+
+        // Read in the next 8 bytes
+        inputStream.read(byteArray);
+
+        double number = convertDoubleFromBytes(byteArray);
+
+        return number;
+    }
+
+    public static double convertDoubleFromBytes(byte[] bytes) {
+        return convertDoubleFromBytes(bytes, 0);
+    }
+
+    public static double convertDoubleFromBytes(byte[] bytes, int offset) {
+        // Convert it to a double
+        long bits = convertLongFromBytes(bytes, offset);
+        return Double.longBitsToDouble(bits);
+    }
+    
+    //  **********  byte <> float  METHODS  **********
+
+    /**
+     * Writes an float out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the float will be written to
+     * @param fVal
+     *            The float to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeFloat(OutputStream outputStream, float fVal)
+            throws IOException {
+        byte[] byteArray = convertToBytes(fVal);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(float f) {
+        int temp = Float.floatToIntBits(f);
+        return convertToBytes(temp);
+    }
+
+    /**
+     * Read in a float from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the float
+     * @return A float, which is the next 4 bytes converted from the InputStream
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static float readFloat(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[4];
+
+        // Read in the next 4 bytes
+        inputStream.read(byteArray);
+
+        float number = convertFloatFromBytes(byteArray);
+
+        return number;
+    }
+
+    public static float convertFloatFromBytes(byte[] byteArray) {
+        return convertFloatFromBytes(byteArray, 0); 
+    }
+    public static float convertFloatFromBytes(byte[] byteArray, int offset) {
+        // Convert it to an int
+        int number = convertIntFromBytes(byteArray, offset);
+        return Float.intBitsToFloat(number);
+    }
+
+    
+    
+    //  **********  byte <> boolean  METHODS  **********
+
+    /**
+     * Writes a boolean out to an OutputStream.
+     * 
+     * @param outputStream
+     *            The OutputStream the boolean will be written to
+     * @param bVal
+     *            The boolean to write
+     * @throws IOException
+     *             Thrown if there is a problem writing to the OutputStream
+     */
+    public static void writeBoolean(OutputStream outputStream, boolean bVal)
+            throws IOException {
+        byte[] byteArray = convertToBytes(bVal);
+
+        outputStream.write(byteArray);
+
+        return;
+    }
+
+    public static byte[] convertToBytes(boolean b) {
+        byte[] rVal = new byte[1];
+        rVal[0] = b ? (byte)1 : (byte)0;
+        return rVal;
+    }
+
+    /**
+     * Read in a boolean from an InputStream
+     * 
+     * @param inputStream
+     *            The InputStream used to read the boolean
+     * @return A boolean, which is the next byte converted from the InputStream (iow, byte != 0)
+     * @throws IOException
+     *             Thrown if there is a problem reading from the InputStream
+     */
+    public static boolean readBoolean(InputStream inputStream) throws IOException {
+        byte[] byteArray = new byte[1];
+
+        // Read in the next byte
+        inputStream.read(byteArray);
+
+        return convertBooleanFromBytes(byteArray);
+    }
+
+    public static boolean convertBooleanFromBytes(byte[] byteArray) {
+        return convertBooleanFromBytes(byteArray, 0); 
+    }
+    public static boolean convertBooleanFromBytes(byte[] byteArray, int offset) {
+        return byteArray[offset] != 0;
+    }
+
+    
+    /**
+     * Properly reads in data from the given stream until the specified number
+     * of bytes have been read.
+     * 
+     * @param store
+     *            the byte array to store in. Should have a length > bytes
+     * @param bytes
+     *            the number of bytes to read.
+     * @param is
+     *            the stream to read from
+     * @return the store array for chaining purposes
+     * @throws IOException
+     *             if an error occurs while reading from the stream
+     * @throws ArrayIndexOutOfBoundsException
+     *             if bytes greater than the length of the store.
+     */
+    public static byte[] readData(byte[] store, int bytes, InputStream is) throws IOException {
+        for (int i = 0; i < bytes; i++) {
+            store[i] = (byte)is.read();
+        }
+        return store;
+    }
+
+    public static byte[] rightAlignBytes(byte[] bytes, int width) {
+        if (bytes.length != width) {
+            byte[] rVal = new byte[width];
+            for (int x = width - bytes.length; x < width; x++) {
+                rVal[x] = bytes[x - (width - bytes.length)];
+            }
+            return rVal;
+        }
+            
+        return bytes;
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/font/plugins/BitmapFontLoader.java b/engine/src/core-plugins/com/jme3/font/plugins/BitmapFontLoader.java
new file mode 100644
index 0000000..688627d
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/font/plugins/BitmapFontLoader.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2009-2010 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.font.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.font.BitmapCharacter;
+import com.jme3.font.BitmapCharacterSet;
+import com.jme3.font.BitmapFont;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.texture.Texture;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class BitmapFontLoader implements AssetLoader {
+
+    private BitmapFont load(AssetManager assetManager, String folder, InputStream in) throws IOException{
+        MaterialDef spriteMat = 
+                (MaterialDef) assetManager.loadAsset(new AssetKey("Common/MatDefs/Misc/Unshaded.j3md"));
+
+        BitmapCharacterSet charSet = new BitmapCharacterSet();
+        Material[] matPages = null;
+        BitmapFont font = new BitmapFont();
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        String regex = "[\\s=]+";
+
+        font.setCharSet(charSet);
+        while (reader.ready()){
+            String line = reader.readLine();
+            String[] tokens = line.split(regex);
+            if (tokens[0].equals("info")){
+                // Get rendered size
+                for (int i = 1; i < tokens.length; i++){
+                    if (tokens[i].equals("size")){
+                        charSet.setRenderedSize(Integer.parseInt(tokens[i + 1]));
+                    }
+                }
+            }else if (tokens[0].equals("common")){
+                // Fill out BitmapCharacterSet fields
+                for (int i = 1; i < tokens.length; i++){
+                    String token = tokens[i];
+                    if (token.equals("lineHeight")){
+                        charSet.setLineHeight(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("base")){
+                        charSet.setBase(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("scaleW")){
+                        charSet.setWidth(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("scaleH")){
+                        charSet.setHeight(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("pages")){
+                        // number of texture pages
+                        matPages = new Material[Integer.parseInt(tokens[i + 1])];
+                        font.setPages(matPages);
+                    }
+                }
+            }else if (tokens[0].equals("page")){
+                int index = -1;
+                Texture tex = null;
+
+                for (int i = 1; i < tokens.length; i++){
+                    String token = tokens[i];
+                    if (token.equals("id")){
+                        index = Integer.parseInt(tokens[i + 1]);
+                    }else if (token.equals("file")){
+                        String file = tokens[i + 1];
+                        if (file.startsWith("\"")){
+                            file = file.substring(1, file.length()-1);
+                        }
+                        TextureKey key = new TextureKey(folder + file, true);
+                        key.setGenerateMips(false);
+                        tex = assetManager.loadTexture(key);
+                        tex.setMagFilter(Texture.MagFilter.Bilinear);
+                        tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+                    }
+                }
+                // set page
+                if (index >= 0 && tex != null){
+                    Material mat = new Material(spriteMat);
+                    mat.setTexture("ColorMap", tex);
+                    mat.setBoolean("VertexColor", true);
+                    mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+                    matPages[index] = mat;
+                }
+            }else if (tokens[0].equals("char")){
+                // New BitmapCharacter
+                BitmapCharacter ch = null;
+                for (int i = 1; i < tokens.length; i++){
+                    String token = tokens[i];
+                    if (token.equals("id")){
+                        int index = Integer.parseInt(tokens[i + 1]);
+                        ch = new BitmapCharacter();
+                        charSet.addCharacter(index, ch);
+                    }else if (token.equals("x")){
+                        ch.setX(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("y")){
+                        ch.setY(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("width")){
+                        ch.setWidth(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("height")){
+                        ch.setHeight(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("xoffset")){
+                        ch.setXOffset(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("yoffset")){
+                        ch.setYOffset(Integer.parseInt(tokens[i + 1]));
+                    }else if (token.equals("xadvance")){
+                        ch.setXAdvance(Integer.parseInt(tokens[i + 1]));
+                    } else if (token.equals("page")) {
+                        ch.setPage(Integer.parseInt(tokens[i + 1]));
+                    }
+                }
+            }else if (tokens[0].equals("kerning")){
+                // Build kerning list
+                int index = 0;
+                int second = 0;
+                int amount = 0;
+
+                for (int i = 1; i < tokens.length; i++){
+                    if (tokens[i].equals("first")){
+                        index = Integer.parseInt(tokens[i + 1]);
+                    }else if (tokens[i].equals("second")){
+                        second = Integer.parseInt(tokens[i + 1]);
+                    }else if (tokens[i].equals("amount")){
+                        amount = Integer.parseInt(tokens[i + 1]);
+                    }
+                }
+
+                BitmapCharacter ch = charSet.getCharacter(index);
+                ch.addKerning(second, amount);
+            }
+        }
+        return font;
+    }
+    
+    public Object load(AssetInfo info) throws IOException {
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            BitmapFont font = load(info.getManager(), info.getKey().getFolder(), in);
+            return font;
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java b/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java
new file mode 100644
index 0000000..e07d3e4
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/material/plugins/J3MLoader.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.material.*;
+import com.jme3.material.TechniqueDef.LightMode;
+import com.jme3.material.TechniqueDef.ShadowMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.PlaceholderAssets;
+import com.jme3.util.blockparser.BlockLanguageParser;
+import com.jme3.util.blockparser.Statement;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class J3MLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(J3MLoader.class.getName());
+    
+    private AssetManager assetManager;
+    private AssetKey key;
+
+    private MaterialDef materialDef;
+    private Material material;
+    private TechniqueDef technique;
+    private RenderState renderState;
+
+    private String shaderLang;
+    private String vertName;
+    private String fragName;
+    
+    private static final String whitespacePattern = "\\p{javaWhitespace}+";
+
+    public J3MLoader(){
+    }
+
+    private void throwIfNequal(String expected, String got) throws IOException {
+        if (expected == null)
+            throw new IOException("Expected a statement, got '"+got+"'!");
+
+        if (!expected.equals(got))
+            throw new IOException("Expected '"+expected+"', got '"+got+"'!");
+    }
+
+    // <TYPE> <LANG> : <SOURCE>
+    private void readShaderStatement(String statement) throws IOException {
+        String[] split = statement.split(":");
+        if (split.length != 2){
+            throw new IOException("Shader statement syntax incorrect" + statement);
+        }
+        String[] typeAndLang = split[0].split(whitespacePattern);
+        if (typeAndLang.length != 2){
+            throw new IOException("Shader statement syntax incorrect: " + statement);
+        }
+        shaderLang = typeAndLang[1];
+        if (typeAndLang[0].equals("VertexShader")){
+            vertName = split[1].trim();
+        }else if (typeAndLang[0].equals("FragmentShader")){
+            fragName = split[1].trim();
+        }
+    }
+
+    // LightMode <MODE>
+    private void readLightMode(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("LightMode statement syntax incorrect");
+        }
+        LightMode lm = LightMode.valueOf(split[1]);
+        technique.setLightMode(lm);
+    }
+
+    // ShadowMode <MODE>
+    private void readShadowMode(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("ShadowMode statement syntax incorrect");
+        }
+        ShadowMode sm = ShadowMode.valueOf(split[1]);
+        technique.setShadowMode(sm);
+    }
+
+    private Object readValue(VarType type, String value) throws IOException{
+        if (type.isTextureType()){
+//            String texturePath = readString("[\n;(//)(\\})]");
+            String texturePath = value.trim();
+            boolean flipY = false;
+            boolean repeat = false;
+            if (texturePath.startsWith("Flip Repeat ")){
+                texturePath = texturePath.substring(12).trim();
+                flipY = true;
+                repeat = true;
+            }else if (texturePath.startsWith("Flip ")){
+                texturePath = texturePath.substring(5).trim();
+                flipY = true;
+            }else if (texturePath.startsWith("Repeat ")){
+                texturePath = texturePath.substring(7).trim();
+                repeat = true;
+            }
+
+            TextureKey texKey = new TextureKey(texturePath, flipY);
+            texKey.setAsCube(type == VarType.TextureCubeMap);
+            texKey.setGenerateMips(true);
+
+            Texture tex;
+            try {
+                tex = assetManager.loadTexture(texKey);
+            } catch (AssetNotFoundException ex){
+                logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
+                tex = null;
+            }
+            if (tex != null){
+                if (repeat){
+                    tex.setWrap(WrapMode.Repeat);
+                }
+            }else{
+                tex = new Texture2D(PlaceholderAssets.getPlaceholderImage());
+            }
+            return tex;
+        }else{
+            String[] split = value.trim().split(whitespacePattern);
+            switch (type){
+                case Float:
+                    if (split.length != 1){
+                        throw new IOException("Float value parameter must have 1 entry: " + value);
+                    }
+                     return Float.parseFloat(split[0]);
+                case Vector2:
+                    if (split.length != 2){
+                        throw new IOException("Vector2 value parameter must have 2 entries: " + value);
+                    }
+                    return new Vector2f(Float.parseFloat(split[0]),
+                                                               Float.parseFloat(split[1]));
+                case Vector3:
+                    if (split.length != 3){
+                        throw new IOException("Vector3 value parameter must have 3 entries: " + value);
+                    }
+                    return new Vector3f(Float.parseFloat(split[0]),
+                                                               Float.parseFloat(split[1]),
+                                                               Float.parseFloat(split[2]));
+                case Vector4:
+                    if (split.length != 4){
+                        throw new IOException("Vector4 value parameter must have 4 entries: " + value);
+                    }
+                    return new ColorRGBA(Float.parseFloat(split[0]),
+                                                                Float.parseFloat(split[1]),
+                                                                Float.parseFloat(split[2]),
+                                                                Float.parseFloat(split[3]));
+                case Int:
+                    if (split.length != 1){
+                        throw new IOException("Int value parameter must have 1 entry: " + value);
+                    }
+                    return Integer.parseInt(split[0]);
+                case Boolean:
+                    if (split.length != 1){
+                        throw new IOException("Boolean value parameter must have 1 entry: " + value);
+                    }
+                    return Boolean.parseBoolean(split[0]);
+                default:
+                    throw new UnsupportedOperationException("Unknown type: "+type);
+            }
+        }
+    }
+    
+    // <TYPE> <NAME> [ "(" <FFBINDING> ")" ] [ ":" <DEFAULTVAL> ]
+    private void readParam(String statement) throws IOException{
+        String name;
+        String defaultVal = null;
+        FixedFuncBinding ffBinding = null;
+        
+        String[] split = statement.split(":");
+        
+        // Parse default val
+        if (split.length == 1){
+            // Doesn't contain default value
+        }else{
+            if (split.length != 2){
+                throw new IOException("Parameter statement syntax incorrect");
+            }
+            statement = split[0].trim();
+            defaultVal = split[1].trim();           
+        }
+        
+        // Parse ffbinding
+        int startParen = statement.indexOf("(");
+        if (startParen != -1){
+            // get content inside parentheses
+            int endParen = statement.indexOf(")", startParen);
+            String bindingStr = statement.substring(startParen+1, endParen).trim();
+            try {
+                ffBinding = FixedFuncBinding.valueOf(bindingStr);
+            } catch (IllegalArgumentException ex){
+                throw new IOException("FixedFuncBinding '" +
+                                      split[1] + "' does not exist!");
+            }
+            statement = statement.substring(0, startParen);
+        }
+        
+        // Parse type + name
+        split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("Parameter statement syntax incorrect");
+        }
+        
+        VarType type;
+        if (split[0].equals("Color")){
+            type = VarType.Vector4;
+        }else{
+            type = VarType.valueOf(split[0]);
+        }
+        
+        name = split[1];
+        
+        Object defaultValObj = null;
+        if (defaultVal != null){ 
+            defaultValObj = readValue(type, defaultVal);
+        }
+        
+        materialDef.addMaterialParam(type, name, defaultValObj, ffBinding);
+    }
+
+    private void readValueParam(String statement) throws IOException{
+        // Use limit=1 incase filename contains colons
+        String[] split = statement.split(":", 2);
+        if (split.length != 2){
+            throw new IOException("Value parameter statement syntax incorrect");
+        }
+        String name = split[0].trim();
+
+        // parse value
+        MatParam p = material.getMaterialDef().getMaterialParam(name);
+        if (p == null){
+            throw new IOException("The material parameter: "+name+" is undefined.");
+        }
+
+        Object valueObj = readValue(p.getVarType(), split[1]);
+        if (p.getVarType().isTextureType()){
+            material.setTextureParam(name, p.getVarType(), (Texture) valueObj);
+        }else{
+            material.setParam(name, p.getVarType(), valueObj);
+        }
+    }
+
+    private void readMaterialParams(List<Statement> paramsList) throws IOException{
+        for (Statement statement : paramsList){
+            readParam(statement.getLine());
+        }
+    }
+
+    private void readExtendingMaterialParams(List<Statement> paramsList) throws IOException{
+        for (Statement statement : paramsList){
+            readValueParam(statement.getLine());
+        }
+    }
+
+    private void readWorldParams(List<Statement> worldParams) throws IOException{
+        for (Statement statement : worldParams){
+            technique.addWorldParam(statement.getLine());
+        }
+    }
+
+    private boolean parseBoolean(String word){
+        return word != null && word.equals("On");
+    }
+
+    private void readRenderStateStatement(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split[0].equals("Wireframe")){
+            renderState.setWireframe(parseBoolean(split[1]));
+        }else if (split[0].equals("FaceCull")){
+            renderState.setFaceCullMode(FaceCullMode.valueOf(split[1]));
+        }else if (split[0].equals("DepthWrite")){
+            renderState.setDepthWrite(parseBoolean(split[1]));
+        }else if (split[0].equals("DepthTest")){
+            renderState.setDepthTest(parseBoolean(split[1]));
+        }else if (split[0].equals("Blend")){
+            renderState.setBlendMode(BlendMode.valueOf(split[1]));
+        }else if (split[0].equals("AlphaTestFalloff")){
+            renderState.setAlphaTest(true);
+            renderState.setAlphaFallOff(Float.parseFloat(split[1]));
+        }else if (split[0].equals("PolyOffset")){
+            float factor = Float.parseFloat(split[1]);
+            float units = Float.parseFloat(split[2]);
+            renderState.setPolyOffset(factor, units);
+        }else if (split[0].equals("ColorWrite")){
+            renderState.setColorWrite(parseBoolean(split[1]));
+        }else if (split[0].equals("PointSprite")){
+            renderState.setPointSprite(parseBoolean(split[1]));
+        }else{
+            throwIfNequal(null, split[0]);
+        }
+    }
+
+    private void readAdditionalRenderState(List<Statement> renderStates) throws IOException{
+        renderState = material.getAdditionalRenderState();
+        for (Statement statement : renderStates){
+            readRenderStateStatement(statement.getLine());
+        }
+        renderState = null;
+    }
+
+    private void readRenderState(List<Statement> renderStates) throws IOException{
+        renderState = new RenderState();
+        for (Statement statement : renderStates){
+            readRenderStateStatement(statement.getLine());
+        }
+        technique.setRenderState(renderState);
+        renderState = null;
+    }
+    
+    // <DEFINENAME> [ ":" <PARAMNAME> ]
+    private void readDefine(String statement) throws IOException{
+        String[] split = statement.split(":");
+        if (split.length == 1){
+            // add preset define
+            technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
+        }else if (split.length == 2){
+            technique.addShaderParamDefine(split[1].trim(), split[0].trim());
+        }else{
+            throw new IOException("Define syntax incorrect");
+        }
+    }
+
+    private void readDefines(List<Statement> defineList) throws IOException{
+        for (Statement statement : defineList){
+            readDefine(statement.getLine());
+        }
+
+    }
+
+    private void readTechniqueStatement(Statement statement) throws IOException{
+        String[] split = statement.getLine().split("[ \\{]");
+        if (split[0].equals("VertexShader") ||
+            split[0].equals("FragmentShader")){
+            readShaderStatement(statement.getLine());
+        }else if (split[0].equals("LightMode")){
+            readLightMode(statement.getLine());
+        }else if (split[0].equals("ShadowMode")){
+            readShadowMode(statement.getLine());
+        }else if (split[0].equals("WorldParameters")){
+            readWorldParams(statement.getContents());
+        }else if (split[0].equals("RenderState")){
+            readRenderState(statement.getContents());
+        }else if (split[0].equals("Defines")){
+            readDefines(statement.getContents());
+        }else{
+            throwIfNequal(null, split[0]);
+        }
+    }
+
+    private void readTransparentStatement(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("Transparent statement syntax incorrect");
+        }
+        material.setTransparent(parseBoolean(split[1]));
+    }
+
+    private void readTechnique(Statement techStat) throws IOException{
+        String[] split = techStat.getLine().split(whitespacePattern);
+        if (split.length == 1){
+            technique = new TechniqueDef(null);
+        }else if (split.length == 2){
+            technique = new TechniqueDef(split[1]);
+        }else{
+            throw new IOException("Technique statement syntax incorrect");
+        }
+        
+        for (Statement statement : techStat.getContents()){
+            readTechniqueStatement(statement);
+        }
+
+        if (vertName != null && fragName != null){
+            technique.setShaderFile(vertName, fragName, shaderLang);
+        }
+        
+        materialDef.addTechniqueDef(technique);
+        technique = null;
+        vertName = null;
+        fragName = null;
+        shaderLang = null;
+    }
+
+    private void loadFromRoot(List<Statement> roots) throws IOException{
+        if (roots.size() == 2){
+            Statement exception = roots.get(0);
+            String line = exception.getLine();
+            if (line.startsWith("Exception")){
+                throw new AssetLoadException(line.substring("Exception ".length()));
+            }else{
+                throw new IOException("In multiroot material, expected first statement to be 'Exception'");
+            }
+        }else if (roots.size() != 1){
+            throw new IOException("Too many roots in J3M/J3MD file");
+        }
+        
+        boolean extending = false;
+        Statement materialStat = roots.get(0);
+        String materialName = materialStat.getLine();
+        if (materialName.startsWith("MaterialDef")){
+            materialName = materialName.substring("MaterialDef ".length()).trim();
+            extending = false;
+        }else if (materialName.startsWith("Material")){
+            materialName = materialName.substring("Material ".length()).trim();
+            extending = true;
+        }else{
+            throw new IOException("Specified file is not a Material file");
+        }
+        
+        String[] split = materialName.split(":", 2);
+        
+        if (materialName.equals("")){
+            throw new IOException("Material name cannot be empty");
+        }
+
+        if (split.length == 2){
+            if (!extending){
+                throw new IOException("Must use 'Material' when extending.");
+            }
+
+            String extendedMat = split[1].trim();
+
+            MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat));
+            if (def == null)
+                throw new IOException("Extended material "+extendedMat+" cannot be found.");
+
+            material = new Material(def);
+            material.setKey(key);
+//            material.setAssetName(fileName);
+        }else if (split.length == 1){
+            if (extending){
+                throw new IOException("Expected ':', got '{'");
+            }
+            materialDef = new MaterialDef(assetManager, materialName);
+            // NOTE: pass file name for defs so they can be loaded later
+            materialDef.setAssetName(key.getName());
+        }else{
+            throw new IOException("Cannot use colon in material name/path");
+        }
+        
+        for (Statement statement : materialStat.getContents()){
+            split = statement.getLine().split("[ \\{]");
+            String statType = split[0];
+            if (extending){
+                if (statType.equals("MaterialParameters")){
+                    readExtendingMaterialParams(statement.getContents());
+                }else if (statType.equals("AdditionalRenderState")){
+                    readAdditionalRenderState(statement.getContents());
+                }else if (statType.equals("Transparent")){
+                    readTransparentStatement(statement.getLine());
+                }
+            }else{
+                if (statType.equals("Technique")){
+                    readTechnique(statement);
+                }else if (statType.equals("MaterialParameters")){
+                    readMaterialParams(statement.getContents());
+                }else{
+                    throw new IOException("Expected material statement, got '"+statType+"'");
+                }
+            }
+        }
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        this.assetManager = info.getManager();
+
+        InputStream in = info.openStream();
+        try {
+            key = info.getKey();
+            loadFromRoot(BlockLanguageParser.parse(in));
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+        
+        if (material != null){
+            if (!(info.getKey() instanceof MaterialKey)){
+                throw new IOException("Material instances must be loaded via MaterialKey");
+            }
+            // material implementation
+            return material;
+        }else{
+            // material definition
+            return materialDef;
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java b/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java
new file mode 100644
index 0000000..1f701cd
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/scene/plugins/MTLLoader.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.PlaceholderAssets;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MTLLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(MTLLoader.class.getName());
+    
+    protected Scanner scan;
+    protected MaterialList matList;
+    //protected Material material;
+    protected AssetManager assetManager;
+    protected String folderName;
+    protected AssetKey key;
+    
+    protected Texture diffuseMap, normalMap, specularMap, alphaMap;
+    protected ColorRGBA ambient = new ColorRGBA();
+    protected ColorRGBA diffuse = new ColorRGBA();
+    protected ColorRGBA specular = new ColorRGBA();
+    protected float shininess = 16;
+    protected boolean shadeless;
+    protected String matName;
+    protected float alpha = 1;
+    protected boolean transparent = false;
+    protected boolean disallowTransparency = false;
+    protected boolean disallowAmbient = false;
+    protected boolean disallowSpecular = false;
+    
+    public void reset(){
+        scan = null;
+        matList = null;
+//        material = null;
+        
+        resetMaterial();
+    }
+
+    protected ColorRGBA readColor(){
+        ColorRGBA v = new ColorRGBA();
+        v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f);
+        return v;
+    }
+
+    protected String nextStatement(){
+        scan.useDelimiter("\n");
+        String result = scan.next();
+        scan.useDelimiter("\\p{javaWhitespace}+");
+        return result;
+    }
+    
+    protected boolean skipLine(){
+        try {
+            scan.skip(".*\r{0,1}\n");
+            return true;
+        } catch (NoSuchElementException ex){
+            // EOF
+            return false;
+        }
+    }
+    
+    protected void resetMaterial(){
+        ambient.set(ColorRGBA.DarkGray);
+        diffuse.set(ColorRGBA.LightGray);
+        specular.set(ColorRGBA.Black);
+        shininess = 16;
+        disallowTransparency = false;
+        disallowAmbient = false;
+        disallowSpecular = false;
+        shadeless = false;
+        transparent = false;
+        matName = null;
+        diffuseMap = null;
+        specularMap = null;
+        normalMap = null;
+        alphaMap = null;
+        alpha = 1;
+    }
+    
+    protected void createMaterial(){
+        Material material;
+        
+        if (alpha < 1f && transparent && !disallowTransparency){
+            diffuse.a = alpha;
+        }
+        
+        if (shadeless){
+            material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+            material.setColor("Color", diffuse.clone());
+            material.setTexture("ColorMap", diffuseMap);
+            // TODO: Add handling for alpha map?
+        }else{
+            material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+            material.setBoolean("UseMaterialColors", true);
+            material.setColor("Ambient",  ambient.clone());
+            material.setColor("Diffuse",  diffuse.clone());
+            material.setColor("Specular", specular.clone());
+            material.setFloat("Shininess", shininess); // prevents "premature culling" bug
+            
+            if (diffuseMap != null)  material.setTexture("DiffuseMap", diffuseMap);
+            if (specularMap != null) material.setTexture("SpecularMap", specularMap);
+            if (normalMap != null)   material.setTexture("NormalMap", normalMap);
+            if (alphaMap != null)    material.setTexture("AlphaMap", alphaMap);
+        }
+        
+        if (transparent && !disallowTransparency){
+            material.setTransparent(true);
+            material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+            material.getAdditionalRenderState().setAlphaTest(true);
+            material.getAdditionalRenderState().setAlphaFallOff(0.01f);
+        }
+        
+        matList.put(matName, material);
+    }
+
+    protected void startMaterial(String name){
+        if (matName != null){
+            // material is already in cache, generate it
+            createMaterial();
+        }
+        
+        // now, reset the params and set the name to start a new material
+        resetMaterial();
+        matName = name;
+    }
+    
+    protected Texture loadTexture(String path){
+        String[] split = path.trim().split("\\p{javaWhitespace}+");
+        
+        // will crash if path is an empty string
+        path = split[split.length-1];
+        
+        String name = new File(path).getName();
+        TextureKey texKey = new TextureKey(folderName + name);
+        texKey.setGenerateMips(true);
+        Texture texture;
+        try {
+            texture = assetManager.loadTexture(texKey);
+            texture.setWrap(WrapMode.Repeat);
+        } catch (AssetNotFoundException ex){
+            logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
+            texture = new Texture2D(PlaceholderAssets.getPlaceholderImage());
+        }
+        return texture;
+    }
+
+    protected boolean readLine(){
+        if (!scan.hasNext()){
+            return false;
+        }
+
+        String cmd = scan.next().toLowerCase();
+        if (cmd.startsWith("#")){
+            // skip entire comment until next line
+            return skipLine();
+        }else if (cmd.equals("newmtl")){
+            String name = scan.next();
+            startMaterial(name);
+        }else if (cmd.equals("ka")){
+            ambient.set(readColor());
+        }else if (cmd.equals("kd")){
+            diffuse.set(readColor());
+        }else if (cmd.equals("ks")){
+            specular.set(readColor());
+        }else if (cmd.equals("ns")){
+            float shiny = scan.nextFloat();
+            if (shiny >= 1){
+                shininess = shiny; /* (128f / 1000f)*/
+                if (specular.equals(ColorRGBA.Black)){
+                    specular.set(ColorRGBA.White);
+                }
+            }else{
+                // For some reason blender likes to export Ns 0 statements
+                // Ignore Ns 0 instead of setting it
+            }
+            
+        }else if (cmd.equals("d") || cmd.equals("tr")){
+            alpha = scan.nextFloat();
+            transparent = true;
+        }else if (cmd.equals("map_ka")){
+            // ignore it for now
+            return skipLine();
+        }else if (cmd.equals("map_kd")){
+            String path = nextStatement();
+            diffuseMap = loadTexture(path);
+        }else if (cmd.equals("map_bump") || cmd.equals("bump")){
+            if (normalMap == null){
+                String path = nextStatement();
+                normalMap = loadTexture(path);
+            }
+        }else if (cmd.equals("map_ks")){
+            String path = nextStatement();
+            specularMap = loadTexture(path);
+            if (specularMap != null){
+                // NOTE: since specular color is modulated with specmap
+                // make sure we have it set
+                if (specular.equals(ColorRGBA.Black)){
+                    specular.set(ColorRGBA.White);
+                }
+            }
+        }else if (cmd.equals("map_d")){
+            String path = scan.next();
+            alphaMap = loadTexture(path);
+            transparent = true;
+        }else if (cmd.equals("illum")){
+            int mode = scan.nextInt();
+            
+            switch (mode){
+                case 0:
+                    // no lighting
+                    shadeless = true;
+                    disallowTransparency = true;
+                    break;
+                case 1:
+                    disallowSpecular = true;
+                    disallowTransparency = true;
+                    break;
+                case 2:
+                case 3:
+                case 5:
+                case 8:
+                    disallowTransparency = true;
+                    break;
+                case 4:
+                case 6:
+                case 7:
+                case 9:
+                    // Enable transparency
+                    // Works best if diffuse map has an alpha channel
+                    transparent = true;
+                    break;
+            }
+        }else if (cmd.equals("ke") || cmd.equals("ni")){
+            // Ni: index of refraction - unsupported in jME
+            // Ke: emission color
+            return skipLine();
+        }else{
+            logger.log(Level.WARNING, "Unknown statement in MTL! {0}", cmd);
+            return skipLine();
+        }
+        
+        return true;
+    }
+
+    @SuppressWarnings("empty-statement")
+    public Object load(AssetInfo info) throws IOException{
+        reset();
+        
+        this.key = info.getKey();
+        this.assetManager = info.getManager();
+        folderName = info.getKey().getFolder();
+        matList = new MaterialList();
+
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            scan = new Scanner(in);
+            scan.useLocale(Locale.US);
+            
+            while (readLine());
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+        
+        if (matName != null){
+            // still have a material in the vars
+            createMaterial();
+            resetMaterial();
+        }
+        
+        MaterialList list = matList;
+
+        
+
+        return list;
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java b/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java
new file mode 100644
index 0000000..3ce7f52
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/scene/plugins/OBJLoader.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins;
+
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.scene.mesh.IndexIntBuffer;
+import com.jme3.scene.mesh.IndexShortBuffer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.Map.Entry;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Reads OBJ format models.
+ */
+public final class OBJLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(OBJLoader.class.getName());
+
+    protected final ArrayList<Vector3f> verts = new ArrayList<Vector3f>();
+    protected final ArrayList<Vector2f> texCoords = new ArrayList<Vector2f>();
+    protected final ArrayList<Vector3f> norms = new ArrayList<Vector3f>();
+    
+    protected final ArrayList<Face> faces = new ArrayList<Face>();
+    protected final HashMap<String, ArrayList<Face>> matFaces = new HashMap<String, ArrayList<Face>>();
+    
+    protected String currentMatName;
+    protected String currentObjectName;
+
+    protected final HashMap<Vertex, Integer> vertIndexMap = new HashMap<Vertex, Integer>(100);
+    protected final IntMap<Vertex> indexVertMap = new IntMap<Vertex>(100);
+    protected int curIndex    = 0;
+    protected int objectIndex = 0;
+    protected int geomIndex   = 0;
+
+    protected Scanner scan;
+    protected ModelKey key;
+    protected AssetManager assetManager;
+    protected MaterialList matList;
+
+    protected String objName;
+    protected Node objNode;
+
+    protected static class Vertex {
+
+        Vector3f v;
+        Vector2f vt;
+        Vector3f vn;
+        int index;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final Vertex other = (Vertex) obj;
+            if (this.v != other.v && (this.v == null || !this.v.equals(other.v))) {
+                return false;
+            }
+            if (this.vt != other.vt && (this.vt == null || !this.vt.equals(other.vt))) {
+                return false;
+            }
+            if (this.vn != other.vn && (this.vn == null || !this.vn.equals(other.vn))) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 5;
+            hash = 53 * hash + (this.v != null ? this.v.hashCode() : 0);
+            hash = 53 * hash + (this.vt != null ? this.vt.hashCode() : 0);
+            hash = 53 * hash + (this.vn != null ? this.vn.hashCode() : 0);
+            return hash;
+        }
+    }
+    
+    protected static class Face {
+        Vertex[] verticies;
+    }
+    
+    protected class ObjectGroup {
+        
+        final String objectName;
+        
+        public ObjectGroup(String objectName){
+            this.objectName = objectName;
+        }
+        
+        public Spatial createGeometry(){
+            Node groupNode = new Node(objectName);
+            
+//            if (matFaces.size() > 0){
+//                for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
+//                    ArrayList<Face> materialFaces = entry.getValue();
+//                    if (materialFaces.size() > 0){
+//                        Geometry geom = createGeometry(materialFaces, entry.getKey());
+//                        objNode.attachChild(geom);
+//                    }
+//                }
+//            }else if (faces.size() > 0){
+//                // generate final geometry
+//                Geometry geom = createGeometry(faces, null);
+//                objNode.attachChild(geom);
+//            }
+            
+            return groupNode;
+        }
+    }
+
+    public void reset(){
+        verts.clear();
+        texCoords.clear();
+        norms.clear();
+        faces.clear();
+        matFaces.clear();
+
+        vertIndexMap.clear();
+        indexVertMap.clear();
+
+        currentMatName = null;
+        matList = null;
+        curIndex = 0;
+        geomIndex = 0;
+        scan = null;
+    }
+
+    protected void findVertexIndex(Vertex vert){
+        Integer index = vertIndexMap.get(vert);
+        if (index != null){
+            vert.index = index.intValue();
+        }else{
+            vert.index = curIndex++;
+            vertIndexMap.put(vert, vert.index);
+            indexVertMap.put(vert.index, vert);
+        }
+    }
+
+    protected Face[] quadToTriangle(Face f){
+        assert f.verticies.length == 4;
+
+        Face[] t = new Face[]{ new Face(), new Face() };
+        t[0].verticies = new Vertex[3];
+        t[1].verticies = new Vertex[3];
+
+        Vertex v0 = f.verticies[0];
+        Vertex v1 = f.verticies[1];
+        Vertex v2 = f.verticies[2];
+        Vertex v3 = f.verticies[3];
+
+        // find the pair of verticies that is closest to each over
+        // v0 and v2
+        // OR
+        // v1 and v3
+        float d1 = v0.v.distanceSquared(v2.v);
+        float d2 = v1.v.distanceSquared(v3.v);
+        if (d1 < d2){
+            // put an edge in v0, v2
+            t[0].verticies[0] = v0;
+            t[0].verticies[1] = v1;
+            t[0].verticies[2] = v3;
+
+            t[1].verticies[0] = v1;
+            t[1].verticies[1] = v2;
+            t[1].verticies[2] = v3;
+        }else{
+            // put an edge in v1, v3
+            t[0].verticies[0] = v0;
+            t[0].verticies[1] = v1;
+            t[0].verticies[2] = v2;
+
+            t[1].verticies[0] = v0;
+            t[1].verticies[1] = v2;
+            t[1].verticies[2] = v3;
+        }
+
+        return t;
+    }
+
+    private ArrayList<Vertex> vertList = new ArrayList<Vertex>();
+
+    protected void readFace(){
+        Face f = new Face();
+        vertList.clear();
+
+        String line = scan.nextLine().trim();
+        String[] verticies = line.split("\\s");
+        for (String vertex : verticies){
+            int v = 0;
+            int vt = 0;
+            int vn = 0;
+
+            String[] split = vertex.split("/");
+            if (split.length == 1){
+                v = Integer.parseInt(split[0].trim());
+            }else if (split.length == 2){
+                v = Integer.parseInt(split[0].trim());
+                vt = Integer.parseInt(split[1].trim());
+            }else if (split.length == 3 && !split[1].equals("")){
+                v = Integer.parseInt(split[0].trim());
+                vt = Integer.parseInt(split[1].trim());
+                vn = Integer.parseInt(split[2].trim());
+            }else if (split.length == 3){
+                v = Integer.parseInt(split[0].trim());
+                vn = Integer.parseInt(split[2].trim());
+            }
+
+            Vertex vx = new Vertex();
+            vx.v = verts.get(v - 1);
+
+            if (vt > 0)
+                vx.vt = texCoords.get(vt - 1);
+
+            if (vn > 0)
+                vx.vn = norms.get(vn - 1);
+
+            vertList.add(vx);
+        }
+
+        if (vertList.size() > 4 || vertList.size() <= 2)
+            logger.warning("Edge or polygon detected in OBJ. Ignored.");
+
+        f.verticies = new Vertex[vertList.size()];
+        for (int i = 0; i < vertList.size(); i++){
+            f.verticies[i] = vertList.get(i);
+        }
+
+        if (matList != null && matFaces.containsKey(currentMatName)){
+            matFaces.get(currentMatName).add(f);
+        }else{
+            faces.add(f); // faces that belong to the default material
+        }
+    }
+
+    protected Vector3f readVector3(){
+        Vector3f v = new Vector3f();
+
+        v.set(Float.parseFloat(scan.next()),
+              Float.parseFloat(scan.next()),
+              Float.parseFloat(scan.next()));
+
+        return v;
+    }
+
+    protected Vector2f readVector2(){
+        Vector2f v = new Vector2f();
+
+        String line = scan.nextLine().trim();
+        String[] split = line.split("\\s");
+        v.setX( Float.parseFloat(split[0].trim()) );
+        v.setY( Float.parseFloat(split[1].trim()) );
+
+//        v.setX(scan.nextFloat());
+//        if (scan.hasNextFloat()){
+//            v.setY(scan.nextFloat());
+//            if (scan.hasNextFloat()){
+//                scan.nextFloat(); // ignore
+//            }
+//        }
+
+        return v;
+    }
+
+    protected void loadMtlLib(String name) throws IOException{
+        if (!name.toLowerCase().endsWith(".mtl"))
+            throw new IOException("Expected .mtl file! Got: " + name);
+
+        // NOTE: Cut off any relative/absolute paths
+        name = new File(name).getName();
+        AssetKey mtlKey = new AssetKey(key.getFolder() + name);
+        try {
+            matList = (MaterialList) assetManager.loadAsset(mtlKey);
+        } catch (AssetNotFoundException ex){
+            logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key});
+        }
+
+        if (matList != null){
+            // create face lists for every material
+            for (String matName : matList.keySet()){
+                matFaces.put(matName, new ArrayList<Face>());
+            }
+        }
+    }
+
+    protected boolean nextStatement(){
+        try {
+            scan.skip(".*\r{0,1}\n");
+            return true;
+        } catch (NoSuchElementException ex){
+            // EOF
+            return false;
+        }
+    }
+
+    protected boolean readLine() throws IOException{
+        if (!scan.hasNext()){
+            return false;
+        }
+
+        String cmd = scan.next();
+        if (cmd.startsWith("#")){
+            // skip entire comment until next line
+            return nextStatement();
+        }else if (cmd.equals("v")){
+            // vertex position
+            verts.add(readVector3());
+        }else if (cmd.equals("vn")){
+            // vertex normal
+            norms.add(readVector3());
+        }else if (cmd.equals("vt")){
+            // texture coordinate
+            texCoords.add(readVector2());
+        }else if (cmd.equals("f")){
+            // face, can be triangle, quad, or polygon (unsupported)
+            readFace();
+        }else if (cmd.equals("usemtl")){
+            // use material from MTL lib for the following faces
+            currentMatName = scan.next();
+//            if (!matList.containsKey(currentMatName))
+//                throw new IOException("Cannot locate material " + currentMatName + " in MTL file!");
+            
+        }else if (cmd.equals("mtllib")){
+            // specify MTL lib to use for this OBJ file
+            String mtllib = scan.nextLine().trim();
+            loadMtlLib(mtllib);
+        }else if (cmd.equals("s") || cmd.equals("g")){
+            return nextStatement();
+        }else{
+            // skip entire command until next line
+            logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd);
+            return nextStatement();
+        }     
+
+        return true;
+    }
+
+    protected Geometry createGeometry(ArrayList<Face> faceList, String matName) throws IOException{
+        if (faceList.isEmpty())
+            throw new IOException("No geometry data to generate mesh");
+
+        // Create mesh from the faces
+        Mesh mesh = constructMesh(faceList);
+        
+        Geometry geom = new Geometry(objName + "-geom-" + (geomIndex++), mesh);
+        
+        Material material = null;
+        if (matName != null && matList != null){
+            // Get material from material list
+            material = matList.get(matName);
+        }
+        if (material == null){
+            // create default material
+            material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+            material.setFloat("Shininess", 64);
+        }
+        geom.setMaterial(material);
+        if (material.isTransparent())
+            geom.setQueueBucket(Bucket.Transparent);
+        else
+            geom.setQueueBucket(Bucket.Opaque);
+        
+        if (material.getMaterialDef().getName().contains("Lighting")
+          && mesh.getFloatBuffer(Type.Normal) == null){
+            logger.log(Level.WARNING, "OBJ mesh {0} doesn't contain normals! "
+                                    + "It might not display correctly", geom.getName());
+        }
+        
+        return geom;
+    }
+
+    protected Mesh constructMesh(ArrayList<Face> faceList){
+        Mesh m = new Mesh();
+        m.setMode(Mode.Triangles);
+
+        boolean hasTexCoord = false;
+        boolean hasNormals  = false;
+
+        ArrayList<Face> newFaces = new ArrayList<Face>(faceList.size());
+        for (int i = 0; i < faceList.size(); i++){
+            Face f = faceList.get(i);
+
+            for (Vertex v : f.verticies){
+                findVertexIndex(v);
+
+                if (!hasTexCoord && v.vt != null)
+                    hasTexCoord = true;
+                if (!hasNormals && v.vn != null)
+                    hasNormals = true;
+            }
+
+            if (f.verticies.length == 4){
+                Face[] t = quadToTriangle(f);
+                newFaces.add(t[0]);
+                newFaces.add(t[1]);
+            }else{
+                newFaces.add(f);
+            }
+        }
+
+        FloatBuffer posBuf  = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
+        FloatBuffer normBuf = null;
+        FloatBuffer tcBuf   = null;
+
+        if (hasNormals){
+            normBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 3);
+        }
+        if (hasTexCoord){
+            tcBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 2);
+        }
+
+        IndexBuffer indexBuf = null;
+        if (vertIndexMap.size() >= 65536){
+            // too many verticies: use intbuffer instead of shortbuffer
+            IntBuffer ib = BufferUtils.createIntBuffer(newFaces.size() * 3);
+            m.setBuffer(VertexBuffer.Type.Index, 3, ib);
+            indexBuf = new IndexIntBuffer(ib);
+        }else{
+            ShortBuffer sb = BufferUtils.createShortBuffer(newFaces.size() * 3);
+            m.setBuffer(VertexBuffer.Type.Index, 3, sb);
+            indexBuf = new IndexShortBuffer(sb);
+        }
+
+        int numFaces = newFaces.size();
+        for (int i = 0; i < numFaces; i++){
+            Face f = newFaces.get(i);
+            if (f.verticies.length != 3)
+                continue;
+
+            Vertex v0 = f.verticies[0];
+            Vertex v1 = f.verticies[1];
+            Vertex v2 = f.verticies[2];
+
+            posBuf.position(v0.index * 3);
+            posBuf.put(v0.v.x).put(v0.v.y).put(v0.v.z);
+            posBuf.position(v1.index * 3);
+            posBuf.put(v1.v.x).put(v1.v.y).put(v1.v.z);
+            posBuf.position(v2.index * 3);
+            posBuf.put(v2.v.x).put(v2.v.y).put(v2.v.z);
+
+            if (normBuf != null){
+                if (v0.vn != null){
+                    normBuf.position(v0.index * 3);
+                    normBuf.put(v0.vn.x).put(v0.vn.y).put(v0.vn.z);
+                    normBuf.position(v1.index * 3);
+                    normBuf.put(v1.vn.x).put(v1.vn.y).put(v1.vn.z);
+                    normBuf.position(v2.index * 3);
+                    normBuf.put(v2.vn.x).put(v2.vn.y).put(v2.vn.z);
+                }
+            }
+
+            if (tcBuf != null){
+                if (v0.vt != null){
+                    tcBuf.position(v0.index * 2);
+                    tcBuf.put(v0.vt.x).put(v0.vt.y);
+                    tcBuf.position(v1.index * 2);
+                    tcBuf.put(v1.vt.x).put(v1.vt.y);
+                    tcBuf.position(v2.index * 2);
+                    tcBuf.put(v2.vt.x).put(v2.vt.y);
+                }
+            }
+
+            int index = i * 3; // current face * 3 = current index
+            indexBuf.put(index,   v0.index);
+            indexBuf.put(index+1, v1.index);
+            indexBuf.put(index+2, v2.index);
+        }
+
+        m.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
+        m.setBuffer(VertexBuffer.Type.Normal,   3, normBuf);
+        m.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf);
+        // index buffer was set on creation
+
+        m.setStatic();
+        m.updateBound();
+        m.updateCounts();
+        //m.setInterleaved();
+
+        // clear data generated face statements
+        // to prepare for next mesh
+        vertIndexMap.clear();
+        indexVertMap.clear();
+        curIndex = 0;
+
+        return m;
+    }
+
+    @SuppressWarnings("empty-statement")
+    public Object load(AssetInfo info) throws IOException{
+        reset();
+        
+        key = (ModelKey) info.getKey();
+        assetManager = info.getManager();
+        objName    = key.getName();
+        
+        String folderName = key.getFolder();
+        String ext        = key.getExtension();
+        objName = objName.substring(0, objName.length() - ext.length() - 1);
+        if (folderName != null && folderName.length() > 0){
+            objName = objName.substring(folderName.length());
+        }
+
+        objNode = new Node(objName + "-objnode");
+
+        if (!(info.getKey() instanceof ModelKey))
+            throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
+
+        InputStream in = null; 
+        try {
+            in = info.openStream();
+            
+            scan = new Scanner(in);
+            scan.useLocale(Locale.US);
+
+            while (readLine());
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+        
+        if (matFaces.size() > 0){
+            for (Entry<String, ArrayList<Face>> entry : matFaces.entrySet()){
+                ArrayList<Face> materialFaces = entry.getValue();
+                if (materialFaces.size() > 0){
+                    Geometry geom = createGeometry(materialFaces, entry.getKey());
+                    objNode.attachChild(geom);
+                }
+            }
+        }else if (faces.size() > 0){
+            // generate final geometry
+            Geometry geom = createGeometry(faces, null);
+            objNode.attachChild(geom);
+        }
+
+        if (objNode.getQuantity() == 1)
+            // only 1 geometry, so no need to send node
+            return objNode.getChild(0); 
+        else
+            return objNode;
+    }
+ 
+}
diff --git a/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java
new file mode 100644
index 0000000..f9073b7
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+/**
+ * GLSL File parser that supports #import pre-processor statement
+ */
+public class GLSLLoader implements AssetLoader {
+
+    private AssetManager owner;
+    private Map<String, DependencyNode> dependCache = new HashMap<String, DependencyNode>();
+
+    private class DependencyNode {
+
+        private String shaderSource;
+        private String shaderName;
+
+        private final Set<DependencyNode> dependsOn = new HashSet<DependencyNode>();
+        private final Set<DependencyNode> dependOnMe = new HashSet<DependencyNode>();
+
+        public DependencyNode(String shaderName){
+            this.shaderName = shaderName;
+        }
+
+        public void setSource(String source){
+            this.shaderSource = source;
+        }
+
+        public void addDependency(DependencyNode node){
+            if (this.dependsOn.contains(node))
+                return; // already contains dependency
+
+//            System.out.println(shaderName + " depend on "+node.shaderName);
+            this.dependsOn.add(node);
+            node.dependOnMe.add(this);
+        }
+
+    }
+
+    private class GlslDependKey extends AssetKey<InputStream> {
+        public GlslDependKey(String name){
+            super(name);
+        }
+        @Override
+        public boolean shouldCache(){
+            return false;
+        }
+    }
+
+    private DependencyNode loadNode(InputStream in, String nodeName) throws IOException{
+        DependencyNode node = new DependencyNode(nodeName);
+        if (in == null)
+            throw new IOException("Dependency "+nodeName+" cannot be found.");
+
+        StringBuilder sb = new StringBuilder();
+        BufferedReader r = new BufferedReader(new InputStreamReader(in));
+        while (r.ready()){
+            String ln = r.readLine();
+            if (ln.startsWith("#import ")){
+                ln = ln.substring(8).trim();
+                if (ln.startsWith("\"") && ln.endsWith("\"") && ln.length() > 3){
+                    // import user code
+                    // remove quotes to get filename
+                    ln = ln.substring(1, ln.length()-1);
+                    if (ln.equals(nodeName))
+                        throw new IOException("Node depends on itself.");
+
+                    // check cache first
+                    DependencyNode dependNode = dependCache.get(ln);
+                    if (dependNode == null){
+                        GlslDependKey key = new GlslDependKey(ln);
+                        // make sure not to register an input stream with
+                        // the cache..
+                        InputStream stream = (InputStream) owner.loadAsset(key);
+                        dependNode = loadNode(stream, ln);
+                    }
+                    node.addDependency(dependNode);
+                }
+//            }else if (ln.startsWith("uniform") || ln.startsWith("varying") || ln.startsWith("attribute")){
+//                // these variables are included as dependencies as well
+//                DependencyNode dependNode = dependCache.get(ln);
+//                if (dependNode == null){
+//                    // the source and name are the same for variable dependencies
+//                    dependNode = new DependencyNode(ln);
+//                    dependNode.setSource(ln);
+//                    dependCache.put(ln, dependNode);
+//                }
+//                node.addDependency(dependNode);
+            }else{
+                sb.append(ln).append('\n');
+            }
+        }
+        r.close();
+
+        node.setSource(sb.toString());
+        dependCache.put(nodeName, node);
+        return node;
+    }
+
+    private DependencyNode nextIndependentNode(List<DependencyNode> checkedNodes){
+        Collection<DependencyNode> allNodes = dependCache.values();
+        if (allNodes == null || allNodes.isEmpty())
+            return null;
+        
+        for (DependencyNode node : allNodes){
+            if (node.dependsOn.isEmpty()){
+                return node;
+            }
+        }
+
+        // circular dependency found..
+        for (DependencyNode node : allNodes){
+            System.out.println(node.shaderName);
+        }
+        throw new RuntimeException("Circular dependency.");
+    }
+
+    private String resolveDependencies(DependencyNode root){
+        StringBuilder sb = new StringBuilder();
+
+        List<DependencyNode> checkedNodes = new ArrayList<DependencyNode>();
+        checkedNodes.add(root);
+        while (true){
+            DependencyNode indepnNode = nextIndependentNode(checkedNodes);
+            if (indepnNode == null)
+                break;
+
+            sb.append(indepnNode.shaderSource).append('\n');
+            dependCache.remove(indepnNode.shaderName);
+            
+            // take out this dependency
+            for (Iterator<DependencyNode> iter = indepnNode.dependOnMe.iterator();
+                 iter.hasNext();){
+                DependencyNode dependNode = iter.next();
+                iter.remove();
+                dependNode.dependsOn.remove(indepnNode);
+            }
+        }
+
+//        System.out.println(sb.toString());
+//        System.out.println("--------------------------------------------------");
+        
+        return sb.toString();
+    }
+
+    /**
+     *
+     * @param owner
+     * @param in
+     * @param extension
+     * @param key
+     * @return
+     * @throws java.io.IOException
+     */
+    public Object load(AssetInfo info) throws IOException {
+        // The input stream provided is for the vertex shader, 
+        // to retrieve the fragment shader, use the content manager
+        this.owner = info.getManager();
+        if (info.getKey().getExtension().equals("glsllib")){
+            // NOTE: Loopback, GLSLLIB is loaded by this loader
+            // and needs data as InputStream
+            return info.openStream();
+        }else{
+            // GLSLLoader wants result as String for
+            // fragment shader
+            DependencyNode rootNode = loadNode(info.openStream(), "[main]");
+            String code = resolveDependencies(rootNode);
+            dependCache.clear();
+            return code;
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java
new file mode 100644
index 0000000..897c9eb
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/DDSLoader.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.LittleEndien;
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * 
+ * <code>DDSLoader</code> is an image loader that reads in a DirectX DDS file.
+ * Supports DXT1, DXT3, DXT5, RGB, RGBA, Grayscale, Alpha pixel formats.
+ * 2D images, mipmapped 2D images, and cubemaps.
+ * 
+ * @author Gareth Jenkins-Jones
+ * @author Kirill Vainer
+ * @version $Id: DDSLoader.java,v 2.0 2008/8/15
+ */
+public class DDSLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(DDSLoader.class.getName());
+    private static final boolean forceRGBA = false;
+    private static final int DDSD_MANDATORY = 0x1007;
+    private static final int DDSD_MANDATORY_DX10 = 0x6;
+    private static final int DDSD_MIPMAPCOUNT = 0x20000;
+    private static final int DDSD_LINEARSIZE = 0x80000;
+    private static final int DDSD_DEPTH = 0x800000;
+    private static final int DDPF_ALPHAPIXELS = 0x1;
+    private static final int DDPF_FOURCC = 0x4;
+    private static final int DDPF_RGB = 0x40;
+    // used by compressonator to mark grayscale images, red channel mask is used for data and bitcount is 8
+    private static final int DDPF_GRAYSCALE = 0x20000;
+    // used by compressonator to mark alpha images, alpha channel mask is used for data and bitcount is 8
+    private static final int DDPF_ALPHA = 0x2;
+    // used by NVTextureTools to mark normal images.
+    private static final int DDPF_NORMAL = 0x80000000;
+    private static final int SWIZZLE_xGxR = 0x78477852;
+    private static final int DDSCAPS_COMPLEX = 0x8;
+    private static final int DDSCAPS_TEXTURE = 0x1000;
+    private static final int DDSCAPS_MIPMAP = 0x400000;
+    private static final int DDSCAPS2_CUBEMAP = 0x200;
+    private static final int DDSCAPS2_VOLUME = 0x200000;
+    private static final int PF_DXT1 = 0x31545844;
+    private static final int PF_DXT3 = 0x33545844;
+    private static final int PF_DXT5 = 0x35545844;
+    private static final int PF_ATI1 = 0x31495441;
+    private static final int PF_ATI2 = 0x32495441; // 0x41544932;
+    private static final int PF_DX10 = 0x30315844; // a DX10 format
+    private static final int DX10DIM_BUFFER = 0x1,
+            DX10DIM_TEXTURE1D = 0x2,
+            DX10DIM_TEXTURE2D = 0x3,
+            DX10DIM_TEXTURE3D = 0x4;
+    private static final int DX10MISC_GENERATE_MIPS = 0x1,
+            DX10MISC_TEXTURECUBE = 0x4;
+    private static final double LOG2 = Math.log(2);
+    private int width;
+    private int height;
+    private int depth;
+    private int flags;
+    private int pitchOrSize;
+    private int mipMapCount;
+    private int caps1;
+    private int caps2;
+    private boolean directx10;
+    private boolean compressed;
+    private boolean texture3D;
+    private boolean grayscaleOrAlpha;
+    private boolean normal;
+    private Format pixelFormat;
+    private int bpp;
+    private int[] sizes;
+    private int redMask, greenMask, blueMask, alphaMask;
+    private DataInput in;
+
+    public DDSLoader() {
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey)) {
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+        }
+
+        InputStream stream = null;
+        try {
+            stream = info.openStream();
+            in = new LittleEndien(stream);
+            loadHeader();
+            if (texture3D) {
+                ((TextureKey) info.getKey()).setTextureTypeHint(Type.ThreeDimensional);
+            } else if (depth > 1) {
+                ((TextureKey) info.getKey()).setTextureTypeHint(Type.CubeMap);
+            }
+            ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
+            return new Image(pixelFormat, width, height, depth, data, sizes);
+        } finally {
+            if (stream != null){
+                stream.close();
+            }
+        }
+    }
+
+    public Image load(InputStream stream) throws IOException {
+        in = new LittleEndien(stream);
+        loadHeader();
+        ArrayList<ByteBuffer> data = readData(false);
+        return new Image(pixelFormat, width, height, depth, data, sizes);
+    }
+
+    private void loadDX10Header() throws IOException {
+        int dxgiFormat = in.readInt();
+        if (dxgiFormat != 83) {
+            throw new IOException("Only DXGI_FORMAT_BC5_UNORM "
+                    + "is supported for DirectX10 DDS! Got: " + dxgiFormat);
+        }
+        pixelFormat = Format.LATC;
+        bpp = 8;
+        compressed = true;
+
+        int resDim = in.readInt();
+        if (resDim == DX10DIM_TEXTURE3D) {
+            texture3D = true;
+        }
+        int miscFlag = in.readInt();
+        int arraySize = in.readInt();
+        if (is(miscFlag, DX10MISC_TEXTURECUBE)) {
+            // mark texture as cube
+            if (arraySize != 6) {
+                throw new IOException("Cubemaps should consist of 6 images!");
+            }
+        }
+
+        in.skipBytes(4); // skip reserved value
+    }
+
+    /**
+     * Reads the header (first 128 bytes) of a DDS File
+     */
+    private void loadHeader() throws IOException {
+        if (in.readInt() != 0x20534444 || in.readInt() != 124) {
+            throw new IOException("Not a DDS file");
+        }
+
+        flags = in.readInt();
+
+        if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) {
+            throw new IOException("Mandatory flags missing");
+        }
+
+        height = in.readInt();
+        width = in.readInt();
+        pitchOrSize = in.readInt();
+        depth = in.readInt();
+        mipMapCount = in.readInt();
+        in.skipBytes(44);
+        pixelFormat = null;
+        directx10 = false;
+        readPixelFormat();
+        caps1 = in.readInt();
+        caps2 = in.readInt();
+        in.skipBytes(12);
+        texture3D = false;
+
+        if (!directx10) {
+            if (!is(caps1, DDSCAPS_TEXTURE)) {
+                throw new IOException("File is not a texture");
+            }
+
+            if (depth <= 0) {
+                depth = 1;
+            }
+
+            if (is(caps2, DDSCAPS2_CUBEMAP)) {
+                depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap
+            }
+
+            if (is(caps2, DDSCAPS2_VOLUME)) {
+                texture3D = true;
+            }
+        }
+
+        int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2);
+
+        if (is(caps1, DDSCAPS_MIPMAP)) {
+            if (!is(flags, DDSD_MIPMAPCOUNT)) {
+                mipMapCount = expectedMipmaps;
+            } else if (mipMapCount != expectedMipmaps) {
+                // changed to warning- images often do not have the required amount,
+                // or specify that they have mipmaps but include only the top level..
+                logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}",
+                        new Object[]{mipMapCount, expectedMipmaps});
+            }
+        } else {
+            mipMapCount = 1;
+        }
+
+        if (directx10) {
+            loadDX10Header();
+        }
+
+        loadSizes();
+    }
+
+    /**
+     * Reads the PixelFormat structure in a DDS file
+     */
+    private void readPixelFormat() throws IOException {
+        int pfSize = in.readInt();
+        if (pfSize != 32) {
+            throw new IOException("Pixel format size is " + pfSize + ", not 32");
+        }
+
+        int pfFlags = in.readInt();
+        normal = is(pfFlags, DDPF_NORMAL);
+
+        if (is(pfFlags, DDPF_FOURCC)) {
+            compressed = true;
+            int fourcc = in.readInt();
+            int swizzle = in.readInt();
+            in.skipBytes(16);
+
+            switch (fourcc) {
+                case PF_DXT1:
+                    bpp = 4;
+                    if (is(pfFlags, DDPF_ALPHAPIXELS)) {
+                        pixelFormat = Image.Format.DXT1A;
+                    } else {
+                        pixelFormat = Image.Format.DXT1;
+                    }
+                    break;
+                case PF_DXT3:
+                    bpp = 8;
+                    pixelFormat = Image.Format.DXT3;
+                    break;
+                case PF_DXT5:
+                    bpp = 8;
+                    pixelFormat = Image.Format.DXT5;
+                    if (swizzle == SWIZZLE_xGxR) {
+                        normal = true;
+                    }
+                    break;
+                case PF_ATI1:
+                    bpp = 4;
+                    pixelFormat = Image.Format.LTC;
+                    break;
+                case PF_ATI2:
+                    bpp = 8;
+                    pixelFormat = Image.Format.LATC;
+                    break;
+                case PF_DX10:
+                    compressed = false;
+                    directx10 = true;
+                    // exit here, the rest of the structure is not valid
+                    // the real format will be available in the DX10 header
+                    return;
+                default:
+                    throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
+            }
+
+            int size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2;
+
+            if (is(flags, DDSD_LINEARSIZE)) {
+                if (pitchOrSize == 0) {
+                    logger.warning("Must use linear size with fourcc");
+                    pitchOrSize = size;
+                } else if (pitchOrSize != size) {
+                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
+                            new Object[]{size, pitchOrSize});
+                }
+            } else {
+                pitchOrSize = size;
+            }
+        } else {
+            compressed = false;
+
+            // skip fourCC
+            in.readInt();
+
+            bpp = in.readInt();
+            redMask = in.readInt();
+            greenMask = in.readInt();
+            blueMask = in.readInt();
+            alphaMask = in.readInt();
+
+            if (is(pfFlags, DDPF_RGB)) {
+                if (is(pfFlags, DDPF_ALPHAPIXELS)) {
+                    pixelFormat = Format.RGBA8;
+                } else {
+                    pixelFormat = Format.RGB8;
+                }
+            } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) {
+                switch (bpp) {
+                    case 16:
+                        pixelFormat = Format.Luminance8Alpha8;
+                        break;
+                    case 32:
+                        pixelFormat = Format.Luminance16Alpha16;
+                        break;
+                    default:
+                        throw new IOException("Unsupported GrayscaleAlpha BPP: " + bpp);
+                }
+                grayscaleOrAlpha = true;
+            } else if (is(pfFlags, DDPF_GRAYSCALE)) {
+                switch (bpp) {
+                    case 8:
+                        pixelFormat = Format.Luminance8;
+                        break;
+                    case 16:
+                        pixelFormat = Format.Luminance16;
+                        break;
+                    default:
+                        throw new IOException("Unsupported Grayscale BPP: " + bpp);
+                }
+                grayscaleOrAlpha = true;
+            } else if (is(pfFlags, DDPF_ALPHA)) {
+                switch (bpp) {
+                    case 8:
+                        pixelFormat = Format.Alpha8;
+                        break;
+                    case 16:
+                        pixelFormat = Format.Alpha16;
+                        break;
+                    default:
+                        throw new IOException("Unsupported Alpha BPP: " + bpp);
+                }
+                grayscaleOrAlpha = true;
+            } else {
+                throw new IOException("Unknown PixelFormat in DDS file");
+            }
+
+            int size = (bpp / 8 * width);
+
+            if (is(flags, DDSD_LINEARSIZE)) {
+                if (pitchOrSize == 0) {
+                    logger.warning("Linear size said to contain valid value but does not");
+                    pitchOrSize = size;
+                } else if (pitchOrSize != size) {
+                    logger.log(Level.WARNING, "Expected size = {0}, real = {1}",
+                            new Object[]{size, pitchOrSize});
+                }
+            } else {
+                pitchOrSize = size;
+            }
+        }
+    }
+
+    /**
+     * Computes the sizes of each mipmap level in bytes, and stores it in sizes_[].
+     */
+    private void loadSizes() {
+        int mipWidth = width;
+        int mipHeight = height;
+
+        sizes = new int[mipMapCount];
+        int outBpp = pixelFormat.getBitsPerPixel();
+        for (int i = 0; i < mipMapCount; i++) {
+            int size;
+            if (compressed) {
+                size = ((mipWidth + 3) / 4) * ((mipHeight + 3) / 4) * outBpp * 2;
+            } else {
+                size = mipWidth * mipHeight * outBpp / 8;
+            }
+
+            sizes[i] = ((size + 3) / 4) * 4;
+
+            mipWidth = Math.max(mipWidth / 2, 1);
+            mipHeight = Math.max(mipHeight / 2, 1);
+        }
+    }
+
+    /**
+     * Flips the given image data on the Y axis.
+     * @param data Data array containing image data (without mipmaps)
+     * @param scanlineSize Size of a single scanline = width * bytesPerPixel
+     * @param height Height of the image in pixels
+     * @return The new data flipped by the Y axis
+     */
+    public byte[] flipData(byte[] data, int scanlineSize, int height) {
+        byte[] newData = new byte[data.length];
+
+        for (int y = 0; y < height; y++) {
+            System.arraycopy(data, y * scanlineSize,
+                    newData, (height - y - 1) * scanlineSize,
+                    scanlineSize);
+        }
+
+        return newData;
+    }
+
+    /**
+     * Reads a grayscale image with mipmaps from the InputStream
+     * @param flip Flip the loaded image by Y axis
+     * @param totalSize Total size of the image in bytes including the mipmaps
+     * @return A ByteBuffer containing the grayscale image data with mips.
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readGrayscale2D(boolean flip, int totalSize) throws IOException {
+        ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
+
+        if (bpp == 8) {
+            logger.finest("Source image format: R8");
+        }
+
+        assert bpp == pixelFormat.getBitsPerPixel();
+
+        int mipWidth = width;
+        int mipHeight = height;
+
+        for (int mip = 0; mip < mipMapCount; mip++) {
+            byte[] data = new byte[sizes[mip]];
+            in.readFully(data);
+            if (flip) {
+                data = flipData(data, mipWidth * bpp / 8, mipHeight);
+            }
+            buffer.put(data);
+
+            mipWidth = Math.max(mipWidth / 2, 1);
+            mipHeight = Math.max(mipHeight / 2, 1);
+        }
+
+        return buffer;
+    }
+
+    /**
+     * Reads an uncompressed RGB or RGBA image.
+     *
+     * @param flip Flip the image on the Y axis
+     * @param totalSize Size of the image in bytes including mipmaps
+     * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException {
+        int redCount = count(redMask),
+                blueCount = count(blueMask),
+                greenCount = count(greenMask),
+                alphaCount = count(alphaMask);
+
+        if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
+            if (alphaMask == 0xFF000000 && bpp == 32) {
+                logger.finest("Data source format: BGRA8");
+            } else if (bpp == 24) {
+                logger.finest("Data source format: BGR8");
+            }
+        }
+
+        int sourcebytesPP = bpp / 8;
+        int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
+
+        ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
+
+        int mipWidth = width;
+        int mipHeight = height;
+
+        int offset = 0;
+        byte[] b = new byte[sourcebytesPP];
+        for (int mip = 0; mip < mipMapCount; mip++) {
+            for (int y = 0; y < mipHeight; y++) {
+                for (int x = 0; x < mipWidth; x++) {
+                    in.readFully(b);
+
+                    int i = byte2int(b);
+
+                    byte red = (byte) (((i & redMask) >> redCount));
+                    byte green = (byte) (((i & greenMask) >> greenCount));
+                    byte blue = (byte) (((i & blueMask) >> blueCount));
+                    byte alpha = (byte) (((i & alphaMask) >> alphaCount));
+
+                    if (flip) {
+                        dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
+                    }
+                    //else
+                    //    dataBuffer.position(offset + (y * width + x) * targetBytesPP);
+
+                    if (alphaMask == 0) {
+                        dataBuffer.put(red).put(green).put(blue);
+                    } else {
+                        dataBuffer.put(red).put(green).put(blue).put(alpha);
+                    }
+                }
+            }
+
+            offset += mipWidth * mipHeight * targetBytesPP;
+
+            mipWidth = Math.max(mipWidth / 2, 1);
+            mipHeight = Math.max(mipHeight / 2, 1);
+        }
+
+        return dataBuffer;
+    }
+
+    /**
+     * Reads a DXT compressed image from the InputStream
+     *
+     * @param totalSize Total size of the image in bytes, including mipmaps
+     * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readDXT2D(boolean flip, int totalSize) throws IOException {
+        logger.finest("Source image format: DXT");
+
+        ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
+
+        int mipWidth = width;
+        int mipHeight = height;
+
+        for (int mip = 0; mip < mipMapCount; mip++) {
+            if (flip) {
+                byte[] data = new byte[sizes[mip]];
+                in.readFully(data);
+                ByteBuffer wrapped = ByteBuffer.wrap(data);
+                wrapped.rewind();
+                ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
+                buffer.put(flipped);
+            } else {
+                byte[] data = new byte[sizes[mip]];
+                in.readFully(data);
+                buffer.put(data);
+            }
+
+            mipWidth = Math.max(mipWidth / 2, 1);
+            mipHeight = Math.max(mipHeight / 2, 1);
+        }
+        buffer.rewind();
+
+        return buffer;
+    }
+
+    /**
+     * Reads a grayscale image with mipmaps from the InputStream
+     * @param flip Flip the loaded image by Y axis
+     * @param totalSize Total size of the image in bytes including the mipmaps
+     * @return A ByteBuffer containing the grayscale image data with mips.
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException {
+        ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
+
+        if (bpp == 8) {
+            logger.finest("Source image format: R8");
+        }
+
+        assert bpp == pixelFormat.getBitsPerPixel();
+
+
+        for (int i = 0; i < depth; i++) {
+            int mipWidth = width;
+            int mipHeight = height;
+
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                byte[] data = new byte[sizes[mip]];
+                in.readFully(data);
+                if (flip) {
+                    data = flipData(data, mipWidth * bpp / 8, mipHeight);
+                }
+                buffer.put(data);
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+        }
+        buffer.rewind();
+        return buffer;
+    }
+
+    /**
+     * Reads an uncompressed RGB or RGBA image.
+     *
+     * @param flip Flip the image on the Y axis
+     * @param totalSize Size of the image in bytes including mipmaps
+     * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException {
+        int redCount = count(redMask),
+                blueCount = count(blueMask),
+                greenCount = count(greenMask),
+                alphaCount = count(alphaMask);
+
+        if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) {
+            if (alphaMask == 0xFF000000 && bpp == 32) {
+                logger.finest("Data source format: BGRA8");
+            } else if (bpp == 24) {
+                logger.finest("Data source format: BGR8");
+            }
+        }
+
+        int sourcebytesPP = bpp / 8;
+        int targetBytesPP = pixelFormat.getBitsPerPixel() / 8;
+
+        ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth);
+
+        for (int k = 0; k < depth; k++) {
+            //   ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
+            int mipWidth = width;
+            int mipHeight = height;
+            int offset = k * totalSize;
+            byte[] b = new byte[sourcebytesPP];
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                for (int y = 0; y < mipHeight; y++) {
+                    for (int x = 0; x < mipWidth; x++) {
+                        in.readFully(b);
+
+                        int i = byte2int(b);
+
+                        byte red = (byte) (((i & redMask) >> redCount));
+                        byte green = (byte) (((i & greenMask) >> greenCount));
+                        byte blue = (byte) (((i & blueMask) >> blueCount));
+                        byte alpha = (byte) (((i & alphaMask) >> alphaCount));
+
+                        if (flip) {
+                            dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
+                        }
+                        //else
+                        //    dataBuffer.position(offset + (y * width + x) * targetBytesPP);
+
+                        if (alphaMask == 0) {
+                            dataBuffer.put(red).put(green).put(blue);
+                        } else {
+                            dataBuffer.put(red).put(green).put(blue).put(alpha);
+                        }
+                    }
+                }
+
+                offset += (mipWidth * mipHeight * targetBytesPP);
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+        }
+        dataBuffer.rewind();
+        return dataBuffer;
+    }
+
+    /**
+     * Reads a DXT compressed image from the InputStream
+     *
+     * @param totalSize Total size of the image in bytes, including mipmaps
+     * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_
+     * @throws java.io.IOException If an error occured while reading from InputStream
+     */
+    public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException {
+        logger.finest("Source image format: DXT");
+
+        ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth);
+
+        for (int i = 0; i < depth; i++) {
+            ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
+            int mipWidth = width;
+            int mipHeight = height;
+            for (int mip = 0; mip < mipMapCount; mip++) {
+                if (flip) {
+                    byte[] data = new byte[sizes[mip]];
+                    in.readFully(data);
+                    ByteBuffer wrapped = ByteBuffer.wrap(data);
+                    wrapped.rewind();
+                    ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat);
+                    flipped.rewind();
+                    buffer.put(flipped);
+                } else {
+                    byte[] data = new byte[sizes[mip]];
+                    in.readFully(data);
+                    buffer.put(data);
+                }
+
+                mipWidth = Math.max(mipWidth / 2, 1);
+                mipHeight = Math.max(mipHeight / 2, 1);
+            }
+            buffer.rewind();
+            bufferAll.put(buffer);
+        }
+
+        return bufferAll;
+    }
+
+    /**
+     * Reads the image data from the InputStream in the required format.
+     * If the file contains a cubemap image, it is loaded as 6 ByteBuffers
+     * (potentially containing mipmaps if they were specified), otherwise
+     * a single ByteBuffer is returned for a 2D image.
+     *
+     * @param flip Flip the image data or not.
+     *        For cubemaps, each of the cubemap faces is flipped individually.
+     *        If the image is DXT compressed, no flipping is done.
+     * @return An ArrayList containing a single ByteBuffer for a 2D image, or 6 ByteBuffers for a cubemap.
+     *         The cubemap ByteBuffer order is PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ.
+     *
+     * @throws java.io.IOException If an error occured while reading from the stream.
+     */
+    public ArrayList<ByteBuffer> readData(boolean flip) throws IOException {
+        int totalSize = 0;
+
+        for (int i = 0; i < sizes.length; i++) {
+            totalSize += sizes[i];
+        }
+
+        ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
+        if (depth > 1 && !texture3D) {
+            for (int i = 0; i < depth; i++) {
+                if (compressed) {
+                    allMaps.add(readDXT2D(flip, totalSize));
+                } else if (grayscaleOrAlpha) {
+                    allMaps.add(readGrayscale2D(flip, totalSize));
+                } else {
+                    allMaps.add(readRGB2D(flip, totalSize));
+                }
+            }
+        } else if (texture3D) {
+            if (compressed) {
+                allMaps.add(readDXT3D(flip, totalSize));
+            } else if (grayscaleOrAlpha) {
+                allMaps.add(readGrayscale3D(flip, totalSize));
+            } else {
+                allMaps.add(readRGB3D(flip, totalSize));
+            }
+
+        } else {
+            if (compressed) {
+                allMaps.add(readDXT2D(flip, totalSize));
+            } else if (grayscaleOrAlpha) {
+                allMaps.add(readGrayscale2D(flip, totalSize));
+            } else {
+                allMaps.add(readRGB2D(flip, totalSize));
+            }
+        }
+
+        return allMaps;
+    }
+
+    /**
+     * Checks if flags contains the specified mask
+     */
+    private static boolean is(int flags, int mask) {
+        return (flags & mask) == mask;
+    }
+
+    /**
+     * Counts the amount of bits needed to shift till bitmask n is at zero
+     * @param n Bitmask to test
+     */
+    private static int count(int n) {
+        if (n == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while ((n & 0x1) == 0) {
+            n = n >> 1;
+            i++;
+            if (i > 32) {
+                throw new RuntimeException(Integer.toHexString(n));
+            }
+        }
+
+        return i;
+    }
+
+    /**
+     * Converts a 1 to 4 sized byte array to an integer
+     */
+    private static int byte2int(byte[] b) {
+        if (b.length == 1) {
+            return b[0] & 0xFF;
+        } else if (b.length == 2) {
+            return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8);
+        } else if (b.length == 3) {
+            return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16);
+        } else if (b.length == 4) {
+            return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16) | ((b[3] & 0xFF) << 24);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Converts a int representing a FourCC into a String
+     */
+    private static String string(int value) {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append((char) (value & 0xFF));
+        buf.append((char) ((value & 0xFF00) >> 8));
+        buf.append((char) ((value & 0xFF0000) >> 16));
+        buf.append((char) ((value & 0xFF00000) >> 24));
+
+        return buf.toString();
+    }
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/DXTFlipper.java b/engine/src/core-plugins/com/jme3/texture/plugins/DXTFlipper.java
new file mode 100644
index 0000000..ff90f03
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/DXTFlipper.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DXTFlipper is a utility class used to flip along Y axis DXT compressed textures.
+ * 
+ * @author Kirill Vainer
+ */
+public class DXTFlipper {
+
+    private static final ByteBuffer bb = ByteBuffer.allocate(8);
+
+    static {
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    private static long readCode5(long data, int x, int y){
+        long shift =   (4 * y + x) * 3;
+        long mask = 0x7;
+        mask <<= shift;
+        long code = data & mask;
+        code >>= shift;
+        return code;
+    }
+
+    private static long writeCode5(long data, int x, int y, long code){
+        long shift =  (4 * y + x) * 3;
+        long mask = 0x7;
+        code = (code & mask) << shift;
+        mask <<= shift;
+        mask = ~mask;
+        data &= mask;
+        data |= code; // write new code
+        return data;
+    }
+
+    private static void flipDXT5Block(byte[] block, int h){
+        if (h == 1)
+            return;
+
+        byte c0 = block[0];
+        byte c1 = block[1];
+
+        bb.clear();
+        bb.put(block, 2, 6).flip();
+        bb.clear();
+        long l = bb.getLong();
+        long n = l;
+
+        if (h == 2){
+            n = writeCode5(n, 0, 0, readCode5(l, 0, 1));
+            n = writeCode5(n, 1, 0, readCode5(l, 1, 1));
+            n = writeCode5(n, 2, 0, readCode5(l, 2, 1));
+            n = writeCode5(n, 3, 0, readCode5(l, 3, 1));
+
+            n = writeCode5(n, 0, 1, readCode5(l, 0, 0));
+            n = writeCode5(n, 1, 1, readCode5(l, 1, 0));
+            n = writeCode5(n, 2, 1, readCode5(l, 2, 0));
+            n = writeCode5(n, 3, 1, readCode5(l, 3, 0));
+        }else{
+            n = writeCode5(n, 0, 0, readCode5(l, 0, 3));
+            n = writeCode5(n, 1, 0, readCode5(l, 1, 3));
+            n = writeCode5(n, 2, 0, readCode5(l, 2, 3));
+            n = writeCode5(n, 3, 0, readCode5(l, 3, 3));
+
+            n = writeCode5(n, 0, 1, readCode5(l, 0, 2));
+            n = writeCode5(n, 1, 1, readCode5(l, 1, 2));
+            n = writeCode5(n, 2, 1, readCode5(l, 2, 2));
+            n = writeCode5(n, 3, 1, readCode5(l, 3, 2));
+
+            n = writeCode5(n, 0, 2, readCode5(l, 0, 1));
+            n = writeCode5(n, 1, 2, readCode5(l, 1, 1));
+            n = writeCode5(n, 2, 2, readCode5(l, 2, 1));
+            n = writeCode5(n, 3, 2, readCode5(l, 3, 1));
+
+            n = writeCode5(n, 0, 3, readCode5(l, 0, 0));
+            n = writeCode5(n, 1, 3, readCode5(l, 1, 0));
+            n = writeCode5(n, 2, 3, readCode5(l, 2, 0));
+            n = writeCode5(n, 3, 3, readCode5(l, 3, 0));
+        }
+            
+        bb.clear();
+        bb.putLong(n);
+        bb.clear();
+        bb.get(block, 2, 6).flip();
+
+        assert c0 == block[0] && c1 == block[1];
+    }
+
+    private static void flipDXT3Block(byte[] block, int h){
+        if (h == 1)
+            return;
+
+        // first row
+        byte tmp0 = block[0];
+        byte tmp1 = block[1];
+
+        if (h == 2){
+            block[0] = block[2];
+            block[1] = block[3];
+
+            block[2] = tmp0;
+            block[3] = tmp1;
+        }else{
+            // write last row to first row
+            block[0] = block[6];
+            block[1] = block[7];
+
+            // write first row to last row
+            block[6] = tmp0;
+            block[7] = tmp1;
+
+            // 2nd row
+            tmp0 = block[2];
+            tmp1 = block[3];
+
+            // write 3rd row to 2nd
+            block[2] = block[4];
+            block[3] = block[5];
+
+            // write 2nd row to 3rd
+            block[4] = tmp0;
+            block[5] = tmp1;
+        }
+    }
+
+    /**
+     * Flips a DXT color block or a DXT3 alpha block
+     * @param block
+     * @param h
+     */
+    private static void flipDXT1orDXTA3Block(byte[] block, int h){
+        byte tmp;
+        switch (h){
+            case 1:
+                return;
+            case 2:
+                // keep header intact (the two colors)
+                // header takes 4 bytes
+
+                // flip only two top rows
+                tmp = block[4+1];
+                block[4+1] = block[4+0];
+                block[4+0] = tmp;
+                return;
+            default:
+                // keep header intact (the two colors)
+                // header takes 4 bytes
+
+                // flip first & fourth row
+                tmp = block[4+3];
+                block[4+3] = block[4+0];
+                block[4+0] = tmp;
+
+                // flip second and third row
+                tmp = block[4+2];
+                block[4+2] = block[4+1];
+                block[4+1] = tmp;
+                return;
+        }
+    }
+
+    public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){
+        int blocksX = (int) FastMath.ceil((float)w / 4f);
+        int blocksY = (int) FastMath.ceil((float)h / 4f);
+
+        int type;
+        switch (format){
+            case DXT1:
+            case DXT1A:
+                type = 1;
+                break;
+            case DXT3:
+                type = 2;
+                break;
+            case DXT5:
+                type = 3;
+                break;
+            case LATC:
+                type = 4;
+                break;
+            case LTC:
+                type = 5;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        // DXT1 uses 8 bytes per block,
+        // DXT3, DXT5, LATC use 16 bytes per block
+        int bpb = type == 1 || type == 5 ? 8 : 16;
+
+        ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);
+
+        if (h == 1){
+            retImg.put(img);
+            retImg.rewind();
+            return retImg;
+        }else if (h == 2){
+            byte[] colorBlock = new byte[8];
+            byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null;
+            for (int x = 0; x < blocksX; x++){
+                // prepeare for block reading
+                int blockByteOffset = x * bpb;
+                img.position(blockByteOffset);
+                img.limit(blockByteOffset + bpb);
+
+                img.get(colorBlock);
+                if (type == 4 || type == 5)
+                    flipDXT5Block(colorBlock, h);
+                else
+                    flipDXT1orDXTA3Block(colorBlock, h);
+
+                // write block (no need to flip block indexes, only pixels
+                // inside block
+                retImg.put(colorBlock);
+
+                if (alphaBlock != null){
+                    img.get(alphaBlock);
+                    switch (type){
+                        case 2:
+                            flipDXT3Block(alphaBlock, h); break;
+                        case 3:
+                        case 4: 
+                            flipDXT5Block(alphaBlock, h);
+                            break;
+                    }
+                    retImg.put(alphaBlock);
+                }
+            }
+            retImg.rewind();
+            return retImg;
+        }else if (h >= 4){
+            byte[] colorBlock = new byte[8];
+            byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null;
+            for (int y = 0; y < blocksY; y++){
+                for (int x = 0; x < blocksX; x++){
+                    // prepeare for block reading
+                    int blockIdx = y * blocksX + x;
+                    int blockByteOffset = blockIdx * bpb;
+
+                    img.position(blockByteOffset);
+                    img.limit(blockByteOffset + bpb);
+
+                    blockIdx = (blocksY - y - 1) * blocksX + x;
+                    blockByteOffset = blockIdx * bpb;
+
+                    retImg.position(blockByteOffset);
+                    retImg.limit(blockByteOffset + bpb);
+
+                    if (alphaBlock != null){
+                        img.get(alphaBlock);
+                        switch (type){
+                            case 2:
+                                flipDXT3Block(alphaBlock, h);
+                                break;
+                            case 3:
+                            case 4:
+                                flipDXT5Block(alphaBlock, h);
+                                break;
+                        }
+                        retImg.put(alphaBlock);
+                    }
+
+                    img.get(colorBlock);
+                    if (type == 4 || type == 5)
+                        flipDXT5Block(colorBlock, h);
+                    else
+                        flipDXT1orDXTA3Block(colorBlock, h);
+                    
+                    retImg.put(colorBlock);
+                }
+            }
+            retImg.limit(retImg.capacity());
+            retImg.position(0);
+            return retImg;
+        }else{
+            return null;
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java
new file mode 100644
index 0000000..4758ecf
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HDRLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(HDRLoader.class.getName());
+
+    private boolean writeRGBE = false;
+    private ByteBuffer rleTempBuffer;
+    private ByteBuffer dataStore;
+    private final float[] tempF = new float[3];
+
+    public HDRLoader(boolean writeRGBE){
+        this.writeRGBE = writeRGBE;
+    }
+
+    public HDRLoader(){
+    }
+    
+    public static void convertFloatToRGBE(byte[] rgbe, float red, float green, float blue){
+        double max = red;
+        if (green > max) max = green;
+        if (blue > max) max = blue;
+        if (max < 1.0e-32){
+            rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
+        }else{
+            double exp = Math.ceil( Math.log10(max) / Math.log10(2) );
+            double divider = Math.pow(2.0, exp);
+            rgbe[0] = (byte) ((red   / divider) * 255.0);
+            rgbe[1] = (byte) ((green / divider) * 255.0);
+            rgbe[2] = (byte) ((blue  / divider) * 255.0);
+            rgbe[3] = (byte) (exp + 128.0);
+      }
+    }
+
+    public static void convertRGBEtoFloat(byte[] rgbe, float[] rgbf){
+        int R = rgbe[0] & 0xFF, 
+            G = rgbe[1] & 0xFF,
+            B = rgbe[2] & 0xFF, 
+            E = rgbe[3] & 0xFF;
+        
+        float e = (float) Math.pow(2f, E - (128 + 8) );
+        rgbf[0] = R * e;
+        rgbf[1] = G * e;
+        rgbf[2] = B * e;
+    }
+
+    public static void convertRGBEtoFloat2(byte[] rgbe, float[] rgbf){
+        int R = rgbe[0] & 0xFF,
+            G = rgbe[1] & 0xFF,
+            B = rgbe[2] & 0xFF,
+            E = rgbe[3] & 0xFF;
+
+        float e = (float) Math.pow(2f, E - 128);
+        rgbf[0] = (R / 256.0f) * e;
+        rgbf[1] = (G / 256.0f) * e;
+        rgbf[2] = (B / 256.0f) * e;
+    }
+
+    public static void convertRGBEtoFloat3(byte[] rgbe, float[] rgbf){
+        int R = rgbe[0] & 0xFF,
+            G = rgbe[1] & 0xFF,
+            B = rgbe[2] & 0xFF,
+            E = rgbe[3] & 0xFF;
+
+        float e = (float) Math.pow(2f, E - (128 + 8) );
+        rgbf[0] = R * e;
+        rgbf[1] = G * e;
+        rgbf[2] = B * e;
+    }
+
+    private short flip(int in){
+        return (short) ((in << 8 & 0xFF00) | (in >> 8));
+    }
+    
+    private void writeRGBE(byte[] rgbe){
+        if (writeRGBE){
+            dataStore.put(rgbe);
+        }else{
+            convertRGBEtoFloat(rgbe, tempF);
+            dataStore.putShort(FastMath.convertFloatToHalf(tempF[0]))
+                     .putShort(FastMath.convertFloatToHalf(tempF[1])).
+                      putShort(FastMath.convertFloatToHalf(tempF[2]));
+        }
+    }
+    
+    private String readString(InputStream is) throws IOException{
+        StringBuilder sb = new StringBuilder();
+        while (true){
+            int i = is.read();
+            if (i == 0x0a || i == -1) // new line or EOF
+                return sb.toString();
+            
+            sb.append((char)i);
+        }
+    }
+    
+    private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{
+        // must deocde RLE data into temp buffer before converting to float
+        if (rleTempBuffer == null){
+            rleTempBuffer = BufferUtils.createByteBuffer(width * 4);
+        }else{
+            rleTempBuffer.clear();
+            if (rleTempBuffer.remaining() < width * 4)
+                rleTempBuffer = BufferUtils.createByteBuffer(width * 4);
+        }
+        
+	// read each component seperately
+        for (int i = 0; i < 4; i++) {
+            // read WIDTH bytes for the channel
+            for (int j = 0; j < width;) {
+                int code = in.read();
+                if (code > 128) { // run
+                    code -= 128;
+                    int val = in.read();
+                    while ((code--) != 0) {
+                        rleTempBuffer.put( (j++) * 4 + i , (byte)val);
+                        //scanline[j++][i] = val;
+                    }
+                } else {	// non-run
+                    while ((code--) != 0) {
+                        int val = in.read();
+                        rleTempBuffer.put( (j++) * 4 + i, (byte)val);
+                        //scanline[j++][i] = in.read();
+                    }
+                }
+            }
+        }
+        
+        rleTempBuffer.rewind();
+        byte[] rgbe = new byte[4];
+//        float[] temp = new float[3];
+            
+        // decode temp buffer into float data
+        for (int i = 0; i < width; i++){
+            rleTempBuffer.get(rgbe);
+            writeRGBE(rgbe);
+        }
+        
+        return true;
+    }
+    
+    private boolean decodeScanlineUncompressed(InputStream in, int width) throws IOException{
+        byte[] rgbe = new byte[4];
+        
+        for (int i = 0; i < width; i+=3){
+            if (in.read(rgbe) < 1)
+                return false;
+
+            writeRGBE(rgbe);
+        }
+        return true;
+    }
+    
+    private void decodeScanline(InputStream in, int width) throws IOException{
+        if (width < 8 || width > 0x7fff){
+            // too short/long for RLE compression
+            decodeScanlineUncompressed(in, width);
+        }
+        
+        // check format
+        byte[] data = new byte[4];
+        in.read(data);
+        if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0){
+            // not RLE data
+            decodeScanlineUncompressed(in, width-1);
+        }else{
+            // check scanline width
+            int readWidth = (data[2] & 0xFF) << 8 | (data[3] & 0xFF);
+            if (readWidth != width)
+                throw new IOException("Illegal scanline width in HDR file: "+width+" != "+readWidth);
+            
+            // RLE data
+            decodeScanlineRLE(in, width);
+        }
+    }
+
+    public Image load(InputStream in, boolean flipY) throws IOException{
+        float gamma = -1f;
+        float exposure = -1f;
+        float[] colorcorr = new float[]{ -1f, -1f, -1f };
+
+        int width = -1, height = -1;
+        boolean verifiedFormat = false;
+
+        while (true){
+            String ln = readString(in);
+            ln = ln.trim();
+            if (ln.startsWith("#") || ln.equals("")){
+                if (ln.equals("#?RADIANCE") || ln.equals("#?RGBE"))
+                    verifiedFormat = true;
+
+                continue; // comment or empty statement
+            } else if (ln.startsWith("+") || ln.startsWith("-")){
+                // + or - mark image resolution and start of data
+                String[] resData = ln.split("\\s");
+                if (resData.length != 4){
+                    throw new IOException("Invalid resolution string in HDR file");
+                }
+
+                if (!resData[0].equals("-Y") || !resData[2].equals("+X")){
+                    logger.warning("Flipping/Rotating attributes ignored!");
+                }
+
+                //if (resData[0].endsWith("X")){
+                    // first width then height
+                //    width = Integer.parseInt(resData[1]);
+                //    height = Integer.parseInt(resData[3]);
+                //}else{
+                    width = Integer.parseInt(resData[3]);
+                    height = Integer.parseInt(resData[1]);
+                //}
+
+                break;
+            } else {
+                // regular command
+                int index = ln.indexOf("=");
+                if (index < 1){
+                    logger.log(Level.FINE, "Ignored string: {0}", ln);
+                    continue;
+                }
+
+                String var = ln.substring(0, index).trim().toLowerCase();
+                String value = ln.substring(index+1).trim().toLowerCase();
+                if (var.equals("format")){
+                    if (!value.equals("32-bit_rle_rgbe") && !value.equals("32-bit_rle_xyze")){
+                        throw new IOException("Unsupported format in HDR picture");
+                    }
+                }else if (var.equals("exposure")){
+                    exposure = Float.parseFloat(value);
+                }else if (var.equals("gamma")){
+                    gamma = Float.parseFloat(value);
+                }else{
+                    logger.log(Level.WARNING, "HDR Command ignored: {0}", ln);
+                }
+            }
+        }
+
+        assert width != -1 && height != -1;
+
+        if (!verifiedFormat)
+            logger.warning("Unsure if specified image is Radiance HDR");
+
+        // some HDR images can get pretty big
+        System.gc();
+
+        // each pixel times size of component times # of components
+        Format pixelFormat;
+        if (writeRGBE){
+            pixelFormat = Format.RGBA8;
+        }else{
+            pixelFormat = Format.RGB16F;
+        }
+
+        dataStore = BufferUtils.createByteBuffer(width * height * pixelFormat.getBitsPerPixel());
+
+        int bytesPerPixel = pixelFormat.getBitsPerPixel() / 8;
+        int scanLineBytes = bytesPerPixel * width;
+        for (int y = height - 1; y >= 0; y--) {
+            if (flipY)
+                dataStore.position(scanLineBytes * y);
+
+            decodeScanline(in, width);
+        }
+        in.close();
+
+        dataStore.rewind();
+        return new Image(pixelFormat, width, height, dataStore);
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey))
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+
+        boolean flip = ((TextureKey) info.getKey()).isFlipY();
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            Image img = load(in, flip);
+            return img;
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/ImageFlipper.java b/engine/src/core-plugins/com/jme3/texture/plugins/ImageFlipper.java
new file mode 100644
index 0000000..53978e9
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/ImageFlipper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.texture.Image;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+
+/**
+ * ImageFlipper is a utility class used to flip images across the Y axis.
+ * Due to the standard of where the image origin is between OpenGL and
+ * other software, this class is required.
+ * 
+ * @author Kirill Vainer
+ */
+public class ImageFlipper {
+
+    public static void flipImage(Image img, int index){
+        if (img.getFormat().isCompressed())
+            throw new UnsupportedOperationException("Flipping compressed " +
+                                                    "images is unsupported.");
+
+        int w = img.getWidth();
+        int h = img.getHeight();
+        int halfH = h / 2;
+
+        // bytes per pixel
+        int bpp = img.getFormat().getBitsPerPixel() / 8;
+        int scanline = w * bpp;
+
+        ByteBuffer data = img.getData(index);
+        ByteBuffer temp = BufferUtils.createByteBuffer(scanline);
+        
+        data.rewind();
+        for (int y = 0; y < halfH; y++){
+            int oppY = h - y - 1;
+            // read in scanline
+            data.position(y * scanline);
+            data.limit(data.position() + scanline);
+
+            temp.rewind();
+            temp.put(data);
+
+        }
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/PFMLoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/PFMLoader.java
new file mode 100644
index 0000000..082dc83
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/PFMLoader.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.logging.Logger;
+
+public class PFMLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(PFMLoader.class.getName());
+
+    private String readString(InputStream is) throws IOException{
+        StringBuilder sb = new StringBuilder();
+        while (true){
+            int i = is.read();
+            if (i == 0x0a || i == -1) // new line or EOF
+                return sb.toString();
+
+            sb.append((char)i);
+        }
+    }
+
+    private void flipScanline(byte[] scanline){
+        for (int i = 0; i < scanline.length; i+=4){
+            // flip first and fourth bytes
+            byte tmp = scanline[i+3];
+            scanline[i+3] = scanline[i+0];
+            scanline[i+0] = tmp;
+
+            // flip second and third bytes
+            tmp = scanline[i+2];
+            scanline[i+2] = scanline[i+1];
+            scanline[i+1] = tmp;
+        }
+    }
+    
+    private Image load(InputStream in, boolean needYFlip) throws IOException{
+        Format format = null;
+
+        String fmtStr = readString(in);
+        if (fmtStr.equals("PF")){
+            format = Format.RGB32F;
+        }else if (fmtStr.equals("Pf")){
+            format = Format.Luminance32F;
+        }else{
+            throw new IOException("File is not PFM format");
+        }
+
+        String sizeStr = readString(in);
+        int spaceIdx = sizeStr.indexOf(" ");
+        if (spaceIdx <= 0 || spaceIdx >= sizeStr.length() - 1)
+            throw new IOException("Invalid size syntax in PFM file");
+
+        int width = Integer.parseInt(sizeStr.substring(0,spaceIdx));
+        int height = Integer.parseInt(sizeStr.substring(spaceIdx+1));
+
+        if (width <= 0 || height <= 0)
+            throw new IOException("Invalid size specified in PFM file");
+        
+        String scaleStr = readString(in);
+        float scale = Float.parseFloat(scaleStr);
+        ByteOrder order = scale < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
+        boolean needEndienFlip = order != ByteOrder.nativeOrder();
+
+        // make sure all unneccessary stuff gets deleted from heap
+        // before allocating large amount of memory
+        System.gc();
+
+        int bytesPerPixel = format.getBitsPerPixel() / 8;
+        int scanLineBytes = bytesPerPixel * width;
+
+        ByteBuffer imageData = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+        byte[] scanline = new byte[width * bytesPerPixel];
+
+        for (int y = height - 1; y >= 0; y--) {
+            if (!needYFlip)
+                imageData.position(scanLineBytes * y);
+
+            int read = 0;
+            int off = 0;
+            do {
+                read = in.read(scanline, off, scanline.length - off);
+                off += read;
+            } while (read > 0);
+
+            if (needEndienFlip){
+                flipScanline(scanline);
+            }
+
+            imageData.put(scanline);
+        }
+        imageData.rewind();
+
+        return new Image(format, width, height, imageData);
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey))
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            return load(in, ((TextureKey)info.getKey()).isFlipY());
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+        
+    }
+
+}
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java
new file mode 100644
index 0000000..bbd51d6
--- /dev/null
+++ b/engine/src/core-plugins/com/jme3/texture/plugins/TGALoader.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * <code>TextureManager</code> provides static methods for building a
+ * <code>Texture</code> object. Typically, the information supplied is the
+ * filename and the texture properties.
+ * 
+ * @author Mark Powell
+ * @author Joshua Slack - cleaned, commented, added ability to read 16bit true color and color-mapped TGAs.
+ * @author Kirill Vainer - ported to jME3
+ * @version $Id: TGALoader.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public final class TGALoader implements AssetLoader {
+
+    // 0 - no image data in file
+    public static final int TYPE_NO_IMAGE = 0;
+
+    // 1 - uncompressed, color-mapped image
+    public static final int TYPE_COLORMAPPED = 1;
+
+    // 2 - uncompressed, true-color image
+    public static final int TYPE_TRUECOLOR = 2;
+
+    // 3 - uncompressed, black and white image
+    public static final int TYPE_BLACKANDWHITE = 3;
+
+    // 9 - run-length encoded, color-mapped image
+    public static final int TYPE_COLORMAPPED_RLE = 9;
+
+    // 10 - run-length encoded, true-color image
+    public static final int TYPE_TRUECOLOR_RLE = 10;
+
+    // 11 - run-length encoded, black and white image
+    public static final int TYPE_BLACKANDWHITE_RLE = 11;
+
+    public Object load(AssetInfo info) throws IOException{
+        if (!(info.getKey() instanceof TextureKey))
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+
+        boolean flip = ((TextureKey)info.getKey()).isFlipY();
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            Image img = load(in, flip);
+            return img;
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+    }
+
+    /**
+     * <code>loadImage</code> is a manual image loader which is entirely
+     * independent of AWT. OUT: RGB888 or RGBA8888 Image object
+     * 
+     * @return <code>Image</code> object that contains the
+     *         image, either as a RGB888 or RGBA8888
+     * @param flip
+     *            Flip the image vertically
+     * @param exp32
+     *            Add a dummy Alpha channel to 24b RGB image.
+     * @param fis
+     *            InputStream of an uncompressed 24b RGB or 32b RGBA TGA
+     * @throws java.io.IOException
+     */
+    public static Image load(InputStream in, boolean flip) throws IOException {
+        boolean flipH = false;
+
+        // open a stream to the file
+        DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
+
+        // ---------- Start Reading the TGA header ---------- //
+        // length of the image id (1 byte)
+        int idLength = dis.readUnsignedByte();
+
+        // Type of color map (if any) included with the image
+        // 0 - no color map data is included
+        // 1 - a color map is included
+        int colorMapType = dis.readUnsignedByte();
+
+        // Type of image being read:
+        int imageType = dis.readUnsignedByte();
+
+        // Read Color Map Specification (5 bytes)
+        // Index of first color map entry (if we want to use it, uncomment and remove extra read.)
+//        short cMapStart = flipEndian(dis.readShort());
+        dis.readShort();
+        // number of entries in the color map
+        short cMapLength = flipEndian(dis.readShort());
+        // number of bits per color map entry
+        int cMapDepth = dis.readUnsignedByte();
+
+        // Read Image Specification (10 bytes)
+        // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
+//        int xOffset = flipEndian(dis.readShort());
+        dis.readShort();
+        // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
+//        int yOffset = flipEndian(dis.readShort());
+        dis.readShort();
+        // width of image - in pixels
+        int width = flipEndian(dis.readShort());
+        // height of image - in pixels
+        int height = flipEndian(dis.readShort());
+        // bits per pixel in image.
+        int pixelDepth = dis.readUnsignedByte();
+        int imageDescriptor = dis.readUnsignedByte();
+        if ((imageDescriptor & 32) != 0) // bit 5 : if 1, flip top/bottom ordering
+            flip = !flip;
+        if ((imageDescriptor & 16) != 0) // bit 4 : if 1, flip left/right ordering
+            flipH = !flipH;
+        
+        // ---------- Done Reading the TGA header ---------- //
+        
+        // Skip image ID
+        if (idLength > 0)
+            in.skip(idLength);
+        
+        ColorMapEntry[] cMapEntries = null;
+        if (colorMapType != 0) {
+            // read the color map.
+            int bytesInColorMap = (cMapDepth * cMapLength) >> 3;
+            int bitsPerColor = Math.min(cMapDepth/3 , 8);
+            
+            byte[] cMapData = new byte[bytesInColorMap];
+            in.read(cMapData);
+            
+            // Only go to the trouble of constructing the color map
+            // table if this is declared a color mapped image.
+            if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) {
+                cMapEntries = new ColorMapEntry[cMapLength];
+                int alphaSize = cMapDepth - (3*bitsPerColor);
+                float scalar = 255f / (FastMath.pow(2, bitsPerColor) - 1);
+                float alphaScalar = 255f / (FastMath.pow(2, alphaSize) - 1);
+                for (int i = 0; i < cMapLength; i++) {
+                    ColorMapEntry entry = new ColorMapEntry();
+                    int offset = cMapDepth * i;
+                    entry.red = (byte)(int)(getBitsAsByte(cMapData, offset, bitsPerColor) * scalar);
+                    entry.green = (byte)(int)(getBitsAsByte(cMapData, offset+bitsPerColor, bitsPerColor) * scalar);
+                    entry.blue = (byte)(int)(getBitsAsByte(cMapData, offset+(2*bitsPerColor), bitsPerColor) * scalar);
+                    if (alphaSize <= 0)
+                        entry.alpha = (byte)255;
+                    else
+                        entry.alpha = (byte)(int)(getBitsAsByte(cMapData, offset+(3*bitsPerColor), alphaSize) * alphaScalar);
+                    
+                    cMapEntries[i] = entry;
+                }
+            }
+        }
+        
+        
+        // Allocate image data array
+        Format format;
+        byte[] rawData = null;
+        int dl;
+        if (pixelDepth == 32) {
+            rawData = new byte[width * height * 4];
+            dl = 4;
+        } else {
+            rawData = new byte[width * height * 3];
+            dl = 3;
+        }
+        int rawDataIndex = 0;
+
+        if (imageType == TYPE_TRUECOLOR) {
+            byte red = 0;
+            byte green = 0;
+            byte blue = 0;
+            byte alpha = 0;
+            
+            // Faster than doing a 16-or-24-or-32 check on each individual pixel,
+            // just make a seperate loop for each.
+            if (pixelDepth == 16) {
+                byte[] data = new byte[2];
+                float scalar = 255f/31f;
+                for (int i = 0; i <= (height - 1); i++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+                    for (int j = 0; j < width; j++) {
+                        data[1] = dis.readByte();
+                        data[0] = dis.readByte();
+                        rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 1, 5) * scalar);
+                        rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 6, 5) * scalar);
+                        rawData[rawDataIndex++] = (byte)(int)(getBitsAsByte(data, 11, 5) * scalar);
+                        if (dl == 4) {
+                            // create an alpha channel
+                            alpha = getBitsAsByte(data, 0, 1);
+                            if (alpha == 1) alpha = (byte)255;
+                            rawData[rawDataIndex++] = alpha;
+                        }
+                    }
+                }
+
+                format = dl == 4 ? Format.RGBA8 : Format.RGB8;
+            } else if (pixelDepth == 24){
+                for (int y = 0; y < height; y++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - y) * width * dl;
+                    else
+                        rawDataIndex = y * width * dl;
+
+                    dis.readFully(rawData, rawDataIndex, width * dl);
+//                    for (int x = 0; x < width; x++) {
+                        //read scanline
+//                        blue = dis.readByte();
+//                        green = dis.readByte();
+//                        red = dis.readByte();
+//                        rawData[rawDataIndex++] = red;
+//                        rawData[rawDataIndex++] = green;
+//                        rawData[rawDataIndex++] = blue;
+//                    }
+                }
+                format = Format.BGR8;
+            } else if (pixelDepth == 32){
+                for (int i = 0; i <= (height - 1); i++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+
+                    for (int j = 0; j < width; j++) {
+                        blue = dis.readByte();
+                        green = dis.readByte();
+                        red = dis.readByte();
+                        alpha = dis.readByte();
+                        rawData[rawDataIndex++] = red;
+                        rawData[rawDataIndex++] = green;
+                        rawData[rawDataIndex++] = blue;
+                        rawData[rawDataIndex++] = alpha;
+                    }
+                }
+                format = Format.RGBA8;
+            }else{
+                throw new IOException("Unsupported TGA true color depth: "+pixelDepth);
+            }
+        } else if( imageType == TYPE_TRUECOLOR_RLE ) {
+            byte red = 0;
+            byte green = 0;
+            byte blue = 0;
+            byte alpha = 0;
+            // Faster than doing a 16-or-24-or-32 check on each individual pixel,
+            // just make a seperate loop for each.
+            if( pixelDepth == 32 ){
+                for( int i = 0; i <= ( height - 1 ); ++i ){
+                    if( !flip ){
+                        rawDataIndex = ( height - 1 - i ) * width * dl;
+                    }
+
+                    for( int j = 0; j < width; ++j ){
+                        // Get the number of pixels the next chunk covers (either packed or unpacked)
+                        int count = dis.readByte();
+                        if( ( count & 0x80 ) != 0 ){
+                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                            count &= 0x07f;
+                            j += count;
+                            blue = dis.readByte();
+                            green = dis.readByte();
+                            red = dis.readByte();
+                            alpha = dis.readByte();
+                            while( count-- >= 0 ){
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                                rawData[rawDataIndex++] = alpha;
+                            }
+                        } else{
+                            // Its not RLE packed, but the next <count> pixels are raw.
+                            j += count;
+                            while( count-- >= 0 ){
+                                blue = dis.readByte();
+                                green = dis.readByte();
+                                red = dis.readByte();
+                                alpha = dis.readByte();
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                                rawData[rawDataIndex++] = alpha;
+                            }
+                        }
+                    }
+                }
+                format = Format.RGBA8;
+            } else if( pixelDepth == 24 ){
+                for( int i = 0; i <= ( height - 1 ); i++ ){
+                    if( !flip ){
+                        rawDataIndex = ( height - 1 - i ) * width * dl;
+                    }
+                    for( int j = 0; j < width; ++j ){
+                        // Get the number of pixels the next chunk covers (either packed or unpacked)
+                        int count = dis.readByte();
+                        if( ( count & 0x80 ) != 0 ){
+                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                            count &= 0x07f;
+                            j += count;
+                            blue = dis.readByte();
+                            green = dis.readByte();
+                            red = dis.readByte();
+                            while( count-- >= 0 ){
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                            }
+                        } else{
+                            // Its not RLE packed, but the next <count> pixels are raw.
+                            j += count;
+                            while( count-- >= 0 ){
+                                blue = dis.readByte();
+                                green = dis.readByte();
+                                red = dis.readByte();
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                            }
+                        }
+                    }
+                }
+                format = Format.RGB8;
+            } else if( pixelDepth == 16 ){
+                byte[] data = new byte[ 2 ];
+                float scalar = 255f / 31f;
+                for( int i = 0; i <= ( height - 1 ); i++ ){
+                    if( !flip ){
+                        rawDataIndex = ( height - 1 - i ) * width * dl;
+                    }
+                    for( int j = 0; j < width; j++ ){
+                        // Get the number of pixels the next chunk covers (either packed or unpacked)
+                        int count = dis.readByte();
+                        if( ( count & 0x80 ) != 0 ){
+                            // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+                            count &= 0x07f;
+                            j += count;
+                            data[1] = dis.readByte();
+                            data[0] = dis.readByte();
+                            blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar );
+                            green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar );
+                            red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar );
+                            while( count-- >= 0 ){
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                            }
+                        } else{
+                            // Its not RLE packed, but the next <count> pixels are raw.
+                            j += count;
+                            while( count-- >= 0 ){
+                                data[1] = dis.readByte();
+                                data[0] = dis.readByte();
+                                blue = (byte) (int) ( getBitsAsByte( data, 1, 5 ) * scalar );
+                                green = (byte) (int) ( getBitsAsByte( data, 6, 5 ) * scalar );
+                                red = (byte) (int) ( getBitsAsByte( data, 11, 5 ) * scalar );
+                                rawData[rawDataIndex++] = red;
+                                rawData[rawDataIndex++] = green;
+                                rawData[rawDataIndex++] = blue;
+                            }
+                        }
+                    }
+                }
+                format = Format.RGB8;
+            } else{
+                throw new IOException( "Unsupported TGA true color depth: " + pixelDepth );
+            }
+
+        } else if( imageType == TYPE_COLORMAPPED ){
+            int bytesPerIndex = pixelDepth / 8;
+            
+            if (bytesPerIndex == 1) {
+                for (int i = 0; i <= (height - 1); i++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+                    for (int j = 0; j < width; j++) {
+                        int index = dis.readUnsignedByte();
+                        if (index >= cMapEntries.length || index < 0)
+                            throw new IOException("TGA: Invalid color map entry referenced: "+index);
+
+                        ColorMapEntry entry = cMapEntries[index];
+                        rawData[rawDataIndex++] = entry.red;
+                        rawData[rawDataIndex++] = entry.green;
+                        rawData[rawDataIndex++] = entry.blue;
+                        if (dl == 4) {
+                            rawData[rawDataIndex++] = entry.alpha;
+                        }
+    
+                    }
+                }
+            } else if (bytesPerIndex == 2) {
+                for (int i = 0; i <= (height - 1); i++) {
+                    if (!flip)
+                        rawDataIndex = (height - 1 - i) * width * dl;
+                    for (int j = 0; j < width; j++) {
+                        int index = flipEndian(dis.readShort());
+                        if (index >= cMapEntries.length || index < 0)
+                            throw new IOException("TGA: Invalid color map entry referenced: "+index);
+
+                        ColorMapEntry entry = cMapEntries[index];
+                        rawData[rawDataIndex++] = entry.red;
+                        rawData[rawDataIndex++] = entry.green;
+                        rawData[rawDataIndex++] = entry.blue;
+                        if (dl == 4) {
+                            rawData[rawDataIndex++] = entry.alpha;
+                        }
+                    }
+                }
+            } else {
+                throw new IOException("TGA: unknown colormap indexing size used: "+bytesPerIndex);
+            }
+
+            format = dl == 4 ? Format.RGBA8 : Format.RGB8;
+        } else {
+            throw new IOException("Grayscale TGA not supported");
+        }
+        
+        
+        in.close();
+        // Get a pointer to the image memory
+        ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length);
+        scratch.clear();
+        scratch.put(rawData);
+        scratch.rewind();
+        // Create the Image object
+        Image textureImage = new Image();
+        textureImage.setFormat(format);
+        textureImage.setWidth(width);
+        textureImage.setHeight(height);
+        textureImage.setData(scratch);
+        return textureImage;
+    }
+
+    private static byte getBitsAsByte(byte[] data, int offset, int length) {
+        int offsetBytes = offset / 8;
+        int indexBits = offset % 8;
+        int rVal = 0;
+        
+        // start at data[offsetBytes]...  spill into next byte as needed.
+        for (int i = length; --i >=0;) {
+            byte b = data[offsetBytes];
+            int test = indexBits == 7 ? 1 : 2 << (6-indexBits);
+            if ((b & test) != 0) {
+                if (i == 0)
+                    rVal++;
+                else
+                    rVal += (2 << i-1);
+            }
+            indexBits++;
+            if (indexBits == 8) {
+                indexBits = 0;
+                offsetBytes++;
+            }
+        }
+        
+        return (byte)rVal;
+    }
+
+    /**
+     * <code>flipEndian</code> is used to flip the endian bit of the header
+     * file.
+     * 
+     * @param signedShort
+     *            the bit to flip.
+     * @return the flipped bit.
+     */
+    private static short flipEndian(short signedShort) {
+        int input = signedShort & 0xFFFF;
+        return (short) (input << 8 | (input & 0xFF00) >>> 8);
+    }
+
+    static class ColorMapEntry {
+        byte red, green, blue, alpha;
+        
+        @Override
+        public String toString() {
+            return "entry: "+red+","+green+","+blue+","+alpha;
+        }
+    }
+}
diff --git a/engine/src/core/checkers/quals/DefaultLocation.java b/engine/src/core/checkers/quals/DefaultLocation.java
new file mode 100644
index 0000000..90dc78a
--- /dev/null
+++ b/engine/src/core/checkers/quals/DefaultLocation.java
@@ -0,0 +1,25 @@
+package checkers.quals;
+
+/**
+ * Specifies the locations to which a {@link DefaultQualifier} annotation applies.
+ *
+ * @see DefaultQualifier
+ */
+public enum DefaultLocation {
+
+    /** Apply default annotations to all unannotated types. */
+    ALL,
+
+    /** Apply default annotations to all unannotated types except the raw types
+     * of locals. */
+    ALL_EXCEPT_LOCALS,
+
+    /** Apply default annotations to unannotated upper bounds:  both
+     * explicit ones in <tt>extends</tt> clauses, and implicit upper bounds
+     * when no explicit <tt>extends</tt> or <tt>super</tt> clause is
+     * present. */
+    // Especially useful for parameterized classes that provide a lot of
+    // static methods with the same generic parameters as the class.
+    UPPER_BOUNDS;
+
+}
diff --git a/engine/src/core/checkers/quals/DefaultQualifier.java b/engine/src/core/checkers/quals/DefaultQualifier.java
new file mode 100644
index 0000000..5caf5da
--- /dev/null
+++ b/engine/src/core/checkers/quals/DefaultQualifier.java
@@ -0,0 +1,44 @@
+package checkers.quals;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.*;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Applied to a declaration of a package, type, method, variable, etc.,
+ * specifies that the given annotation should be the default.  The default is
+ * applied to all types within the declaration for which no other
+ * annotation is explicitly written.
+ * If multiple DefaultQualifier annotations are in scope, the innermost one
+ * takes precedence.
+ * DefaultQualifier takes precedence over {@link DefaultQualifierInHierarchy}.
+ * <p>
+ *
+ * If you wish to write multiple @DefaultQualifier annotations (for
+ * unrelated type systems, or with different {@code locations} fields) at
+ * the same location, use {@link DefaultQualifiers}.
+ *
+ * @see DefaultLocation
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({CONSTRUCTOR, METHOD, FIELD, LOCAL_VARIABLE, PARAMETER, TYPE})
+public @interface DefaultQualifier {
+
+    /**
+     * The name of the default annotation.  It may be a short name like
+     * "NonNull", if an appropriate import statement exists.  Otherwise, it
+     * should be fully-qualified, like "checkers.nullness.quals.NonNull".
+     * <p>
+     *
+     * To prevent affecting other type systems, always specify an annotation
+     * in your own type hierarchy.  (For example, do not set
+     * "checkers.quals.Unqualified" as the default.)
+     */
+    String value();
+
+    /** @return the locations to which the annotation should be applied */
+    DefaultLocation[] locations() default {DefaultLocation.ALL};
+}
diff --git a/engine/src/core/checkers/quals/DefaultQualifierInHierarchy.java b/engine/src/core/checkers/quals/DefaultQualifierInHierarchy.java
new file mode 100644
index 0000000..949846d
--- /dev/null
+++ b/engine/src/core/checkers/quals/DefaultQualifierInHierarchy.java
@@ -0,0 +1,29 @@
+package checkers.quals;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the annotated qualifier is the default qualifier in the
+ * qualifier hierarchy:  it applies if the programmer writes no explicit
+ * qualifier.
+ * <p>
+ *
+ * The {@link DefaultQualifier} annotation, which targets Java code elements,
+ * takes precedence over {@code DefaultQualifierInHierarchy}.
+ * <p>
+ *
+ * Each type qualifier hierarchy may have at most one qualifier marked as
+ * {@code DefaultQualifierInHierarchy}.
+ *
+ * @see checkers.quals.DefaultQualifier
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ANNOTATION_TYPE)
+public @interface DefaultQualifierInHierarchy {
+
+}
diff --git a/engine/src/core/checkers/quals/DefaultQualifiers.java b/engine/src/core/checkers/quals/DefaultQualifiers.java
new file mode 100644
index 0000000..e483e63
--- /dev/null
+++ b/engine/src/core/checkers/quals/DefaultQualifiers.java
@@ -0,0 +1,35 @@
+package checkers.quals;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.*;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies the annotations to be included in a type without having to provide
+ * them explicitly.
+ *
+ * This annotation permits specifying multiple default qualifiers for more
+ * than one type system.  It is necessary because Java forbids multiple
+ * annotations of the same name at a single location.
+ *
+ * Example:
+ * <!-- &nbsp; is a hack that prevents @ from being the first charater on the line, which confuses Javadoc -->
+ * <code><pre>
+ * &nbsp; @DefaultQualifiers({
+ * &nbsp;     @DefaultQualifier("NonNull"),
+ * &nbsp;     @DefaultQualifier(value = "Interned", locations = ALL_EXCEPT_LOCALS),
+ * &nbsp;     @DefaultQualifier("Tainted")
+ * &nbsp; })
+ * </pre></code>
+ *
+ * @see DefaultQualifier
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({CONSTRUCTOR, METHOD, FIELD, LOCAL_VARIABLE, PARAMETER, TYPE})
+public @interface DefaultQualifiers {
+    /** The default qualifier settings */
+    DefaultQualifier[] value() default { };
+}
diff --git a/engine/src/core/checkers/quals/Dependent.java b/engine/src/core/checkers/quals/Dependent.java
new file mode 100644
index 0000000..88d3b39
--- /dev/null
+++ b/engine/src/core/checkers/quals/Dependent.java
@@ -0,0 +1,38 @@
+package checkers.quals;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Refines the qualified type of the annotated field or variable based on the
+ * qualified type of the receiver.  The annotation declares a relationship
+ * between multiple type qualifier hierarchies.
+ *
+ * <p><b>Example:</b>
+ * Consider a field, {@code lock}, that is only initialized if the
+ * enclosing object (the receiver), is marked as {@code ThreadSafe}.
+ * Such a field can be declared as:
+ *
+ * <pre><code>
+ *   private @Nullable @Dependent(result=NonNull.class, when=ThreadSafe.class)
+ *     Lock lock;
+ * </code></pre>
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+//@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+public @interface Dependent {
+
+    /**
+     * The class of the refined qualifier to be applied.
+     */
+    Class<? extends Annotation> result();
+
+    /**
+     * The qualifier class of the receiver that causes the {@code result}
+     * qualifier to be applied.
+     */
+    Class<? extends Annotation> when();
+}
diff --git a/engine/src/core/checkers/quals/SubtypeOf.java b/engine/src/core/checkers/quals/SubtypeOf.java
new file mode 100644
index 0000000..ffc8be0
--- /dev/null
+++ b/engine/src/core/checkers/quals/SubtypeOf.java
@@ -0,0 +1,59 @@
+package checkers.quals;
+
+import java.lang.annotation.*;
+
+/**
+ * A meta-annotation to specify all the qualifiers that the given qualifier
+ * is a subtype of.  This provides a declarative way to specify the type
+ * qualifier hierarchy.  (Alternatively, the hierarchy can be defined
+ * procedurally by subclassing {@link checkers.types.QualifierHierarchy} or
+ * {@link checkers.types.TypeHierarchy}.)
+ *
+ * <p>
+ * Example:
+ * <pre> @SubtypeOf( { Nullable.class } )
+ * public @interface NonNull { }
+ * </pre>
+ *
+ * <p>
+ *
+ * If a qualified type is a subtype of the same type without any qualifier,
+ * then use <code>Unqualified.class</code> in place of a type qualifier
+ * class.  For example, to express that <code>@Encrypted <em>C</em></code>
+ * is a subtype of <code><em>C</em></code> (for every class
+ * <code><em>C</em></code>), and likewise for <code>@Interned</code>, write:
+ *
+ * <pre> @SubtypeOf(Unqualified.class)
+ * public @interface Encrypted { }
+ *
+ * &#64;SubtypeOf(Unqualified.class)
+ * public @interface Interned { }
+ * </pre>
+ *
+ * <p>
+ *
+ * For the root type qualifier in the qualifier hierarchy (i.e., the
+ * qualifier that is a supertype of all other qualifiers in the given
+ * hierarchy), use an empty set of values:
+ *
+ * <pre> @SubtypeOf( { } )
+ * public @interface Nullable { }
+ *
+ * &#64;SubtypeOf( {} )
+ * public @interface ReadOnly { }
+ * </pre>
+ *
+ * <p>
+ * Together, all the @SubtypeOf meta-annotations fully describe the type
+ * qualifier hierarchy.
+ * No @SubtypeOf meta-annotation is needed on (or can be written on) the
+ * Unqualified pseudo-qualifier, whose position in the hierarchy is
+ * inferred from the meta-annotations on the explicit qualifiers.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface SubtypeOf {
+    /** An array of the supertype qualifiers of the annotated qualifier **/
+    Class<? extends Annotation>[] value();
+}
diff --git a/engine/src/core/checkers/quals/TypeQualifier.java b/engine/src/core/checkers/quals/TypeQualifier.java
new file mode 100644
index 0000000..25c4f7b
--- /dev/null
+++ b/engine/src/core/checkers/quals/TypeQualifier.java
@@ -0,0 +1,16 @@
+package checkers.quals;
+
+import java.lang.annotation.*;
+
+/**
+ * A meta-annotation indicating that the annotated annotation is a type
+ * qualifier.
+ *
+ * Examples of such qualifiers: {@code @ReadOnly}, {@code @NonNull}
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface TypeQualifier {
+
+}
diff --git a/engine/src/core/checkers/quals/Unqualified.java b/engine/src/core/checkers/quals/Unqualified.java
new file mode 100644
index 0000000..a210b70
--- /dev/null
+++ b/engine/src/core/checkers/quals/Unqualified.java
@@ -0,0 +1,16 @@
+package checkers.quals;
+
+import java.lang.annotation.Target;
+
+/**
+ * A special annotation intended solely for representing an unqualified type in
+ * the qualifier hierarchy, as an argument to {@link SubtypeOf#value()},
+ * in the type qualifiers declarations.
+ *
+ * <p>
+ * Programmers cannot write this in source code.
+ */
+@TypeQualifier
+@SubtypeOf({})
+@Target({}) // empty target prevents programmers from writing this in a program
+public @interface Unqualified { }
diff --git a/engine/src/core/checkers/quals/Unused.java b/engine/src/core/checkers/quals/Unused.java
new file mode 100644
index 0000000..726df8b
--- /dev/null
+++ b/engine/src/core/checkers/quals/Unused.java
@@ -0,0 +1,43 @@
+package checkers.quals;
+
+import static java.lang.annotation.ElementType.FIELD;
+import java.lang.annotation.*;
+
+/**
+ * Declares that the field may not be accessed if the receiver is of the
+ * specified qualifier type (or any supertype).
+ *
+ * This property is verified by the checker that type-checks the {@code
+ * when} element value qualifier.
+ *
+ * <p><b>Example</b>
+ * Consider a class, {@code Table}, with a locking field, {@code lock}.  The
+ * lock is used when a {@code Table} instance is shared across threads.  When
+ * running in a local thread, the {@code lock} field ought not to be used.
+ *
+ * You can declare this behavior in the following way:
+ *
+ * <pre><code>
+ * class Table {
+ *   private @Unused(when=LocalToThread.class) final Lock lock;
+ *   ...
+ * }
+ * </code></pre>
+ *
+ * The checker for {@code @LocalToThread} would issue an error for the following code:
+ *
+ * <pre>  @LocalToThread Table table = ...;
+ *   ... table.lock ...;
+ * </pre>
+ *
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({FIELD})
+public @interface Unused {
+    /**
+     * The field that is annotated with @Unused may not be accessed via a
+     * receiver that is annotated with the "when" annotation.
+     */
+    Class<? extends Annotation> when();
+}
diff --git a/engine/src/core/checkers/quals/package-info.java b/engine/src/core/checkers/quals/package-info.java
new file mode 100644
index 0000000..5ae80a7
--- /dev/null
+++ b/engine/src/core/checkers/quals/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * Contains the basic annotations to be used by all type systems
+ * and meta-annotations to qualify annotations (qualifiers).
+ *
+ * They may serve as documentation for the type qualifiers, and aid the
+ * Checker Framework to infer the relations between the type qualifiers.
+ *
+ * @checker.framework.manual #writing-a-checker Writing a checker
+ */
+package checkers.quals;
diff --git a/engine/src/core/com/jme3/animation/AnimChannel.java b/engine/src/core/com/jme3/animation/AnimChannel.java
new file mode 100644
index 0000000..a2fae7c
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimChannel.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.util.TempVars;
+import java.util.BitSet;
+
+/**
+ * <code>AnimChannel</code> provides controls, such as play, pause,
+ * fast forward, etc, for an animation. The animation
+ * channel may influence the entire model or specific bones of the model's
+ * skeleton. A single model may have multiple animation channels influencing
+ * various parts of its body. For example, a character model may have an
+ * animation channel for its feet, and another for its torso, and
+ * the animations for each channel are controlled independently.
+ * 
+ * @author Kirill Vainer
+ */
+public final class AnimChannel {
+
+    private static final float DEFAULT_BLEND_TIME = 0.15f;
+    
+    private AnimControl control;
+
+    private BitSet affectedBones;
+
+    private Animation animation;
+    private Animation blendFrom;
+    private float time;
+    private float speed;
+    private float timeBlendFrom;
+    private float speedBlendFrom;
+
+    private LoopMode loopMode, loopModeBlendFrom;
+    
+    private float blendAmount = 1f;
+    private float blendRate   = 0;
+
+    private static float clampWrapTime(float t, float max, LoopMode loopMode){
+        if (max == Float.POSITIVE_INFINITY)
+            return t;
+        
+        if (t < 0f){
+            //float tMod = -(-t % max);
+            switch (loopMode){
+                case DontLoop:
+                    return 0;
+                case Cycle:
+                    return t;
+                case Loop:
+                    return max - t;
+            }
+        }else if (t > max){
+            switch (loopMode){
+                case DontLoop:
+                    return max;
+                case Cycle:
+                    return /*-max;*/-(2f * max - t);
+                case Loop:
+                    return t - max;
+            }
+        }
+
+        return t;
+    }
+
+    AnimChannel(AnimControl control){
+        this.control = control;
+    }
+
+    /**
+     * Returns the parent control of this AnimChannel.
+     * 
+     * @return the parent control of this AnimChannel.
+     * @see AnimControl
+     */
+    public AnimControl getControl() {
+        return control;
+    }
+    
+    /**
+     * @return The name of the currently playing animation, or null if
+     * none is assigned.
+     *
+     * @see AnimChannel#setAnim(java.lang.String) 
+     */
+    public String getAnimationName() {
+        return animation != null ? animation.getName() : null;
+    }
+
+    /**
+     * @return The loop mode currently set for the animation. The loop mode
+     * determines what will happen to the animation once it finishes
+     * playing.
+     * 
+     * For more information, see the LoopMode enum class.
+     * @see LoopMode
+     * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
+     */
+    public LoopMode getLoopMode() {
+        return loopMode;
+    }
+
+    /**
+     * @param loopMode Set the loop mode for the channel. The loop mode
+     * determines what will happen to the animation once it finishes
+     * playing.
+     *
+     * For more information, see the LoopMode enum class.
+     * @see LoopMode
+     */
+    public void setLoopMode(LoopMode loopMode) {
+        this.loopMode = loopMode;
+    }
+
+    /**
+     * @return The speed that is assigned to the animation channel. The speed
+     * is a scale value starting from 0.0, at 1.0 the animation will play
+     * at its default speed.
+     *
+     * @see AnimChannel#setSpeed(float)
+     */
+    public float getSpeed() {
+        return speed;
+    }
+
+    /**
+     * @param speed Set the speed of the animation channel. The speed
+     * is a scale value starting from 0.0, at 1.0 the animation will play
+     * at its default speed.
+     */
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * @return The time of the currently playing animation. The time
+     * starts at 0 and continues on until getAnimMaxTime().
+     *
+     * @see AnimChannel#setTime(float)
+     */
+    public float getTime() {
+        return time;
+    }
+
+    /**
+     * @param time Set the time of the currently playing animation, the time
+     * is clamped from 0 to {@link #getAnimMaxTime()}. 
+     */
+    public void setTime(float time) {
+        this.time = FastMath.clamp(time, 0, getAnimMaxTime());
+    }
+
+    /**
+     * @return The length of the currently playing animation, or zero
+     * if no animation is playing.
+     *
+     * @see AnimChannel#getTime()
+     */
+    public float getAnimMaxTime(){
+        return animation != null ? animation.getLength() : 0f;
+    }
+
+    /**
+     * Set the current animation that is played by this AnimChannel.
+     * <p>
+     * This resets the time to zero, and optionally blends the animation
+     * over <code>blendTime</code> seconds with the currently playing animation.
+     * Notice that this method will reset the control's speed to 1.0.
+     *
+     * @param name The name of the animation to play
+     * @param blendTime The blend time over which to blend the new animation
+     * with the old one. If zero, then no blending will occur and the new
+     * animation will be applied instantly.
+     */
+    public void setAnim(String name, float blendTime){
+        if (name == null)
+            throw new NullPointerException();
+
+        if (blendTime < 0f)
+            throw new IllegalArgumentException("blendTime cannot be less than zero");
+
+        Animation anim = control.animationMap.get(name);
+        if (anim == null)
+            throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
+
+        control.notifyAnimChange(this, name);
+
+        if (animation != null && blendTime > 0f){
+            // activate blending
+            blendFrom = animation;
+            timeBlendFrom = time;
+            speedBlendFrom = speed;
+            loopModeBlendFrom = loopMode;
+            blendAmount = 0f;
+            blendRate   = 1f / blendTime;
+        }
+
+        animation = anim;
+        time = 0;
+        speed = 1f;
+        loopMode = LoopMode.Loop;
+    }
+
+    /**
+     * Set the current animation that is played by this AnimChannel.
+     * <p>
+     * See {@link #setAnim(java.lang.String, float)}.
+     * The blendTime argument by default is 150 milliseconds.
+     * 
+     * @param name The name of the animation to play
+     */
+    public void setAnim(String name){
+        setAnim(name, DEFAULT_BLEND_TIME);
+    }
+
+    /**
+     * Add all the bones of the model's skeleton to be
+     * influenced by this animation channel.
+     */
+    public void addAllBones() {
+        affectedBones = null;
+    }
+
+    /**
+     * Add a single bone to be influenced by this animation channel.
+     */
+    public void addBone(String name) {
+        addBone(control.getSkeleton().getBone(name));
+    }
+
+    /**
+     * Add a single bone to be influenced by this animation channel.
+     */
+    public void addBone(Bone bone) {
+        int boneIndex = control.getSkeleton().getBoneIndex(bone);
+        if(affectedBones == null) {
+            affectedBones = new BitSet(control.getSkeleton().getBoneCount());
+        }
+        affectedBones.set(boneIndex);
+    }
+
+    /**
+     * Add bones to be influenced by this animation channel starting from the
+     * given bone name and going toward the root bone.
+     */
+    public void addToRootBone(String name) {
+        addToRootBone(control.getSkeleton().getBone(name));
+    }
+
+    /**
+     * Add bones to be influenced by this animation channel starting from the
+     * given bone and going toward the root bone.
+     */
+    public void addToRootBone(Bone bone) {
+        addBone(bone);
+        while (bone.getParent() != null) {
+            bone = bone.getParent();
+            addBone(bone);
+        }
+    }
+
+    /**
+     * Add bones to be influenced by this animation channel, starting
+     * from the given named bone and going toward its children.
+     */
+    public void addFromRootBone(String name) {
+        addFromRootBone(control.getSkeleton().getBone(name));
+    }
+
+    /**
+     * Add bones to be influenced by this animation channel, starting
+     * from the given bone and going toward its children.
+     */
+    public void addFromRootBone(Bone bone) {
+        addBone(bone);
+        if (bone.getChildren() == null)
+            return;
+        for (Bone childBone : bone.getChildren()) {
+            addBone(childBone);
+            addFromRootBone(childBone);
+        }
+    }
+
+    BitSet getAffectedBones(){
+        return affectedBones;
+    }
+
+    void update(float tpf, TempVars vars) {
+        if (animation == null)
+            return;
+
+        if (blendFrom != null){
+            blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
+            //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones);
+            timeBlendFrom += tpf * speedBlendFrom;
+            timeBlendFrom = clampWrapTime(timeBlendFrom,
+                                          blendFrom.getLength(),
+                                          loopModeBlendFrom);
+            if (timeBlendFrom < 0){
+                timeBlendFrom = -timeBlendFrom;
+                speedBlendFrom = -speedBlendFrom;
+            }
+
+            blendAmount += tpf * blendRate;
+            if (blendAmount > 1f){
+                blendAmount = 1f;
+                blendFrom = null;
+            }
+        }
+
+        animation.setTime(time, blendAmount, control, this, vars);
+        //animation.setTime(time, control.skeleton, blendAmount, affectedBones);
+        time += tpf * speed;
+
+        if (animation.getLength() > 0){
+            if (time >= animation.getLength()) {
+                control.notifyAnimCycleDone(this, animation.getName());
+            } else if (time < 0) {
+                control.notifyAnimCycleDone(this, animation.getName());
+            } 
+        }
+
+        time = clampWrapTime(time, animation.getLength(), loopMode);
+        if (time < 0){
+            time = -time;
+            speed = -speed;
+        }
+
+        
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/AnimControl.java b/engine/src/core/com/jme3/animation/AnimControl.java
new file mode 100644
index 0000000..590801c
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimControl.java
@@ -0,0 +1,377 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.animation;

+

+import com.jme3.export.*;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.ViewPort;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.control.AbstractControl;

+import com.jme3.scene.control.Control;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.HashMap;

+

+/**

+ * <code>AnimControl</code> is a Spatial control that allows manipulation

+ * of skeletal animation.

+ *

+ * The control currently supports:

+ * 1) Animation blending/transitions

+ * 2) Multiple animation channels

+ * 3) Multiple skins

+ * 4) Animation event listeners

+ * 5) Animated model cloning

+ * 6) Animated model binary import/export

+ *

+ * Planned:

+ * 1) Hardware skinning

+ * 2) Morph/Pose animation

+ * 3) Attachments

+ * 4) Add/remove skins

+ *

+ * @author Kirill Vainer

+ */

+public final class AnimControl extends AbstractControl implements Cloneable {

+

+    /**

+     * Skeleton object must contain corresponding data for the targets' weight buffers.

+     */

+    Skeleton skeleton;

+    /** only used for backward compatibility */

+    @Deprecated

+    private SkeletonControl skeletonControl;

+    /**

+     * List of animations

+     */

+    HashMap<String, Animation> animationMap;

+    /**

+     * Animation channels

+     */

+    private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();

+    /**

+     * Animation event listeners

+     */

+    private transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();

+

+    /**

+     * Creates a new animation control for the given skeleton.

+     * The method {@link AnimControl#setAnimations(java.util.HashMap) }

+     * must be called after initialization in order for this class to be useful.

+     *

+     * @param skeleton The skeleton to animate

+     */

+    public AnimControl(Skeleton skeleton) {

+        this.skeleton = skeleton;

+        reset();

+    }

+

+    /**

+     * Serialization only. Do not use.

+     */

+    public AnimControl() {

+    }

+

+    /**

+     * Internal use only.

+     */

+    public Control cloneForSpatial(Spatial spatial) {

+        try {

+            AnimControl clone = (AnimControl) super.clone();

+            clone.spatial = spatial;

+            clone.channels = new ArrayList<AnimChannel>();

+            clone.listeners = new ArrayList<AnimEventListener>();

+

+            if (skeleton != null) {

+                clone.skeleton = new Skeleton(skeleton);

+            }

+

+            // animationMap is reference-copied, animation data should be shared

+            // to reduce memory usage.

+

+            return clone;

+        } catch (CloneNotSupportedException ex) {

+            throw new AssertionError();

+        }

+    }

+

+    /**

+     * @param animations Set the animations that this <code>AnimControl</code>

+     * will be capable of playing. The animations should be compatible

+     * with the skeleton given in the constructor.

+     */

+    public void setAnimations(HashMap<String, Animation> animations) {

+        animationMap = animations;

+    }

+

+    /**

+     * Retrieve an animation from the list of animations.

+     * @param name The name of the animation to retrieve.

+     * @return The animation corresponding to the given name, or null, if no

+     * such named animation exists.

+     */

+    public Animation getAnim(String name) {

+        if (animationMap == null) {

+            animationMap = new HashMap<String, Animation>();

+        }

+        return animationMap.get(name);

+    }

+

+    /**

+     * Adds an animation to be available for playing to this

+     * <code>AnimControl</code>.

+     * @param anim The animation to add.

+     */

+    public void addAnim(Animation anim) {

+        if (animationMap == null) {

+            animationMap = new HashMap<String, Animation>();

+        }

+        animationMap.put(anim.getName(), anim);

+    }

+

+    /**

+     * Remove an animation so that it is no longer available for playing.

+     * @param anim The animation to remove.

+     */

+    public void removeAnim(Animation anim) {

+        if (!animationMap.containsKey(anim.getName())) {

+            throw new IllegalArgumentException("Given animation does not exist "

+                    + "in this AnimControl");

+        }

+

+        animationMap.remove(anim.getName());

+    }

+

+    /**

+     * Create a new animation channel, by default assigned to all bones

+     * in the skeleton.

+     * 

+     * @return A new animation channel for this <code>AnimControl</code>.

+     */

+    public AnimChannel createChannel() {

+        AnimChannel channel = new AnimChannel(this);

+        channels.add(channel);

+        return channel;

+    }

+

+    /**

+     * Return the animation channel at the given index.

+     * @param index The index, starting at 0, to retrieve the <code>AnimChannel</code>.

+     * @return The animation channel at the given index, or throws an exception

+     * if the index is out of bounds.

+     *

+     * @throws IndexOutOfBoundsException If no channel exists at the given index.

+     */

+    public AnimChannel getChannel(int index) {

+        return channels.get(index);

+    }

+

+    /**

+     * @return The number of channels that are controlled by this

+     * <code>AnimControl</code>.

+     *

+     * @see AnimControl#createChannel()

+     */

+    public int getNumChannels() {

+        return channels.size();

+    }

+

+    /**

+     * Clears all the channels that were created.

+     *

+     * @see AnimControl#createChannel()

+     */

+    public void clearChannels() {

+        channels.clear();

+    }

+

+    /**

+     * @return The skeleton of this <code>AnimControl</code>.

+     */

+    public Skeleton getSkeleton() {

+        return skeleton;

+    }

+

+    /**

+     * Adds a new listener to receive animation related events.

+     * @param listener The listener to add.

+     */

+    public void addListener(AnimEventListener listener) {

+        if (listeners.contains(listener)) {

+            throw new IllegalArgumentException("The given listener is already "

+                    + "registed at this AnimControl");

+        }

+

+        listeners.add(listener);

+    }

+

+    /**

+     * Removes the given listener from listening to events.

+     * @param listener

+     * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)

+     */

+    public void removeListener(AnimEventListener listener) {

+        if (!listeners.remove(listener)) {

+            throw new IllegalArgumentException("The given listener is not "

+                    + "registed at this AnimControl");

+        }

+    }

+

+    /**

+     * Clears all the listeners added to this <code>AnimControl</code>

+     *

+     * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)

+     */

+    public void clearListeners() {

+        listeners.clear();

+    }

+

+    void notifyAnimChange(AnimChannel channel, String name) {

+        for (int i = 0; i < listeners.size(); i++) {

+            listeners.get(i).onAnimChange(this, channel, name);

+        }

+    }

+

+    void notifyAnimCycleDone(AnimChannel channel, String name) {

+        for (int i = 0; i < listeners.size(); i++) {

+            listeners.get(i).onAnimCycleDone(this, channel, name);

+        }

+    }

+

+    /**

+     * Internal use only.

+     */

+    @Override

+    public void setSpatial(Spatial spatial) {

+        if (spatial == null && skeletonControl != null) {

+            this.spatial.removeControl(skeletonControl);

+        }

+

+        super.setSpatial(spatial);

+

+        //Backward compatibility.

+        if (spatial != null && skeletonControl != null) {

+            spatial.addControl(skeletonControl);

+        }

+    }

+

+    final void reset() {

+        if (skeleton != null) {

+            skeleton.resetAndUpdate();

+        }

+    }

+

+    /**

+     * @return The names of all animations that this <code>AnimControl</code>

+     * can play.

+     */

+    public Collection<String> getAnimationNames() {

+        return animationMap.keySet();

+    }

+

+    /**

+     * Returns the length of the given named animation.

+     * @param name The name of the animation

+     * @return The length of time, in seconds, of the named animation.

+     */

+    public float getAnimationLength(String name) {

+        Animation a = animationMap.get(name);

+        if (a == null) {

+            throw new IllegalArgumentException("The animation " + name

+                    + " does not exist in this AnimControl");

+        }

+

+        return a.getLength();

+    }

+

+    /**

+     * Internal use only.

+     */

+    @Override

+    protected void controlUpdate(float tpf) {

+        if (skeleton != null) {

+            skeleton.reset(); // reset skeleton to bind pose

+        }

+

+        TempVars vars = TempVars.get();

+        for (int i = 0; i < channels.size(); i++) {

+            channels.get(i).update(tpf, vars);

+        }

+        vars.release();

+

+        if (skeleton != null) {

+            skeleton.updateWorldVectors();

+        }

+    }

+

+    /**

+     * Internal use only.

+     */

+    @Override

+    protected void controlRender(RenderManager rm, ViewPort vp) {

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(skeleton, "skeleton", null);

+        oc.writeStringSavableMap(animationMap, "animations", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule in = im.getCapsule(this);

+        skeleton = (Skeleton) in.readSavable("skeleton", null);

+        animationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);

+

+        if (im.getFormatVersion() == 0) {

+            // Changed for backward compatibility with j3o files generated 

+            // before the AnimControl/SkeletonControl split.

+

+            // If we find a target mesh array the AnimControl creates the 

+            // SkeletonControl for old files and add it to the spatial.        

+            // When backward compatibility won't be needed anymore this can deleted        

+            Savable[] sav = in.readSavableArray("targets", null);

+            if (sav != null) {

+                Mesh[] targets = new Mesh[sav.length];

+                System.arraycopy(sav, 0, targets, 0, sav.length);

+                skeletonControl = new SkeletonControl(targets, skeleton);

+                spatial.addControl(skeletonControl);

+            }

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/animation/AnimEventListener.java b/engine/src/core/com/jme3/animation/AnimEventListener.java
new file mode 100644
index 0000000..22a0b98
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimEventListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+/**
+ * <code>AnimEventListener</code> allows user code to receive various
+ * events regarding an AnimControl. For example, when an animation cycle is done.
+ * 
+ * @author Kirill Vainer
+ */
+public interface AnimEventListener {
+
+    /**
+     * Invoked when an animation "cycle" is done. For non-looping animations,
+     * this event is invoked when the animation is finished playing. For
+     * looping animations, this even is invoked each time the animation is restarted.
+     *
+     * @param control The control to which the listener is assigned.
+     * @param channel The channel being altered
+     * @param animName The new animation that is done.
+     */
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName);
+
+    /**
+     * Invoked when a animation is set to play by the user on the given channel.
+     *
+     * @param control The control to which the listener is assigned.
+     * @param channel The channel being altered
+     * @param animName The new animation name set.
+     */
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName);
+
+}
diff --git a/engine/src/core/com/jme3/animation/Animation.java b/engine/src/core/com/jme3/animation/Animation.java
new file mode 100644
index 0000000..0fb505a
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Animation.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * The animation class updates the animation target with the tracks of a given type.
+ * 
+ * @author Kirill Vainer, Marcin Roguski (Kaelthas)
+ */
+public class Animation implements Savable, Cloneable {
+    
+    /** 
+     * The name of the animation. 
+     */
+    private String name;
+    
+    /** 
+     * The length of the animation. 
+     */
+    private float length;
+    
+    /** 
+     * The tracks of the animation. 
+     */
+    private Track[] tracks;
+    
+    /**
+     * Serialization-only. Do not use.
+     */
+    public Animation() {}
+    
+    /**
+     * Creates a new <code>Animation</code> with the given name and length.
+     * 
+     * @param name The name of the animation.
+     * @param length Length in seconds of the animation.
+     */
+    public Animation(String name, float length) {
+        this.name = name;
+        this.length = length;
+    }
+    
+    /**
+     * The name of the bone animation
+     * @return name of the bone animation
+     */
+    public String getName() {
+    	return name;
+    }
+    
+    /**
+     * Returns the length in seconds of this animation
+     * 
+     * @return the length in seconds of this animation
+     */
+    public float getLength() {
+    	return length;
+    }
+    
+    /**
+     * This method sets the current time of the animation.
+     * This method behaves differently for every known track type.
+     * Override this method if you have your own type of track.
+     * 
+     * @param time the time of the animation
+     * @param blendAmount the blend amount factor
+     * @param control the animation control
+     * @param channel the animation channel
+     */
+    void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) {
+        if (tracks == null)
+            return;
+        
+        for (int i = 0; i < tracks.length; i++){
+            tracks[i].setTime(time, blendAmount, control, channel, vars);
+        }
+        
+        /*
+        if (tracks != null && tracks.length > 0) {
+            Track<?> trackInstance = tracks[0];
+            
+            if (trackInstance instanceof SpatialTrack) {
+                Spatial spatial = control.getSpatial();
+                if (spatial != null) {
+                    ((SpatialTrack) tracks[0]).setTime(time, spatial, blendAmount);
+                }
+            } else if (trackInstance instanceof BoneTrack) {
+                BitSet affectedBones = channel.getAffectedBones();
+                Skeleton skeleton = control.getSkeleton();
+                for (int i = 0; i < tracks.length; ++i) {
+                    if (affectedBones == null || affectedBones.get(((BoneTrack) tracks[i]).getTargetIndex())) {
+                        ((BoneTrack) tracks[i]).setTime(time, skeleton, blendAmount);
+                    }
+                }
+            } else if (trackInstance instanceof PoseTrack) {
+                Spatial spatial = control.getSpatial();
+                List<Mesh> meshes = new ArrayList<Mesh>();
+                this.getMeshes(spatial, meshes);
+                if (meshes.size() > 0) {
+                    Mesh[] targets = meshes.toArray(new Mesh[meshes.size()]);
+                    for (int i = 0; i < tracks.length; ++i) {
+                        ((PoseTrack) tracks[i]).setTime(time, targets, blendAmount);
+                    }
+                }
+            }
+        }
+        */
+    }
+    
+    /**
+     * Set the {@link Track}s to be used by this animation.
+     * <p>
+     * The array should be organized so that the appropriate Track can
+     * be retrieved based on a bone index. 
+     * 
+     * @param tracks The tracks to set.
+     */
+    public void setTracks(Track[] tracks){
+        this.tracks = tracks;
+    }
+    
+    /**
+     * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }.
+     * 
+     * @return the tracks set previously
+     */
+    public Track[] getTracks() {
+    	return tracks;
+    }
+    
+    /**
+     * This method creates a clone of the current object.
+     * @return a clone of the current object
+     */
+   @Override
+   public Animation clone() {
+        try {
+            Animation result = (Animation) super.clone();
+            result.tracks = tracks.clone();
+            for (int i = 0; i < tracks.length; ++i) {
+                result.tracks[i] = this.tracks[i].clone();
+            }
+            return result;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
+    }
+    
+   @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule out = ex.getCapsule(this);
+        out.write(name, "name", null);
+        out.write(length, "length", 0f);
+        out.write(tracks, "tracks", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        name = in.readString("name", null);
+        length = in.readFloat("length", 0f);
+        
+        Savable[] arr = in.readSavableArray("tracks", null);
+        tracks = new Track[arr.length];
+        System.arraycopy(arr, 0, tracks, 0, arr.length);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/AnimationFactory.java b/engine/src/core/com/jme3/animation/AnimationFactory.java
new file mode 100644
index 0000000..b994ccb
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimationFactory.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+
+/**
+ * A convenience class to easily setup a spatial keyframed animation
+ * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale.
+ * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames.
+ * <br><br>
+ * Usage is : <br>
+ * - Create the AnimationHelper<br>
+ * - add some keyFrames<br>
+ * - call the buildAnimation() method that will retruna new Animation<br>
+ * - add the generated Animation to any existing AnimationControl<br>
+ * <br><br>
+ * Note that the first keyFrame (index 0) is defaulted with the identy transforms.
+ * If you want to change that you have to replace this keyFrame with any transform you want.
+ * 
+ * @author Nehon
+ */
+public class AnimationFactory {
+
+    /**
+     * step for splitting rotation that have a n ange above PI/2
+     */
+    private final static float EULER_STEP = FastMath.QUARTER_PI * 3;
+
+    /**
+     * enum to determine the type of interpolation
+     */
+    private enum Type {
+
+        Translation, Rotation, Scale;
+    }
+
+    /**
+     * Inner Rotation type class to kep track on a rotation Euler angle
+     */
+    protected class Rotation {
+
+        /**
+         * The rotation Quaternion
+         */
+        Quaternion rotation = new Quaternion();
+        /**
+         * This rotation expressed in Euler angles
+         */
+        Vector3f eulerAngles = new Vector3f();
+        /**
+         * the index of the parent key frame is this keyFrame is a splitted rotation
+         */
+        int masterKeyFrame = -1;
+
+        public Rotation() {
+            rotation.loadIdentity();
+        }
+
+        void set(Quaternion rot) {
+            rotation.set(rot);
+            float[] a = new float[3];
+            rotation.toAngles(a);
+            eulerAngles.set(a[0], a[1], a[2]);
+        }
+
+        void set(float x, float y, float z) {
+            float[] a = {x, y, z};
+            rotation.fromAngles(a);
+            eulerAngles.set(x, y, z);
+        }
+    }
+    /**
+     * Name of the animation
+     */
+    protected String name;
+    /**
+     * frames per seconds
+     */
+    protected int fps;
+    /**
+     * Animation duration in seconds
+     */
+    protected float duration;
+    /**
+     * total number of frames
+     */
+    protected int totalFrames;
+    /**
+     * time per frame
+     */
+    protected float tpf;
+    /**
+     * Time array for this animation
+     */
+    protected float[] times;
+    /**
+     * Translation array for this animation
+     */
+    protected Vector3f[] translations;
+    /**
+     * rotation array for this animation
+     */
+    protected Quaternion[] rotations;
+    /**
+     * scales array for this animation
+     */
+    protected Vector3f[] scales;
+    /**
+     * The map of keyFrames to compute the animation. The key is the index of the frame
+     */
+    protected Vector3f[] keyFramesTranslation;
+    protected Vector3f[] keyFramesScale;
+    protected Rotation[] keyFramesRotation;
+
+    /**
+     * Creates and AnimationHelper
+     * @param duration the desired duration for the resulting animation
+     * @param name the name of the resulting animation
+     */
+    public AnimationFactory(float duration, String name) {
+        this(duration, name, 30);
+    }
+
+    /**
+     * Creates and AnimationHelper
+     * @param duration the desired duration for the resulting animation
+     * @param name the name of the resulting animation
+     * @param fps the number of frames per second for this animation (default is 30)
+     */
+    public AnimationFactory(float duration, String name, int fps) {
+        this.name = name;
+        this.duration = duration;
+        this.fps = fps;
+        totalFrames = (int) (fps * duration) + 1;
+        tpf = 1 / (float) fps;
+        times = new float[totalFrames];
+        translations = new Vector3f[totalFrames];
+        rotations = new Quaternion[totalFrames];
+        scales = new Vector3f[totalFrames];
+        keyFramesTranslation = new Vector3f[totalFrames];
+        keyFramesTranslation[0] = new Vector3f();
+        keyFramesScale = new Vector3f[totalFrames];
+        keyFramesScale[0] = new Vector3f(1, 1, 1);
+        keyFramesRotation = new Rotation[totalFrames];
+        keyFramesRotation[0] = new Rotation();
+
+    }
+
+    /**
+     * Adds a key frame for the given Transform at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param transform the transforms to use for this keyFrame
+     */
+    public void addTimeTransform(float time, Transform transform) {
+        addKeyFrameTransform((int) (time / tpf), transform);
+    }
+
+    /**
+     * Adds a key frame for the given Transform at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param transform the transforms to use for this keyFrame
+     */
+    public void addKeyFrameTransform(int keyFrameIndex, Transform transform) {
+        addKeyFrameTranslation(keyFrameIndex, transform.getTranslation());
+        addKeyFrameScale(keyFrameIndex, transform.getScale());
+        addKeyFrameRotation(keyFrameIndex, transform.getRotation());
+    }
+
+    /**
+     * Adds a key frame for the given translation at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param translation the translation to use for this keyFrame
+     */
+    public void addTimeTranslation(float time, Vector3f translation) {
+        addKeyFrameTranslation((int) (time / tpf), translation);
+    }
+
+    /**
+     * Adds a key frame for the given translation at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param translation the translation to use for this keyFrame
+     */
+    public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) {
+        Vector3f t = getTranslationForFrame(keyFrameIndex);
+        t.set(translation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given time<br>
+     * This can't be used if the interpolated angle is higher than PI (180°)<br>
+     * Use {@link addTimeRotationAngles(float time, float x, float y, float z)}  instead that uses Euler angles rotations.<br>     * 
+     * @param time the time at which the keyFrame must be inserted
+     * @param rotation the rotation Quaternion to use for this keyFrame
+     * @see #addTimeRotationAngles(float time, float x, float y, float z) 
+     */
+    public void addTimeRotation(float time, Quaternion rotation) {
+        addKeyFrameRotation((int) (time / tpf), rotation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given keyFrame index<br>
+     * This can't be used if the interpolated angle is higher than PI (180°)<br>
+     * Use {@link addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations.
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param rotation the rotation Quaternion to use for this keyFrame
+     * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) 
+     */
+    public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) {
+        Rotation r = getRotationForFrame(keyFrameIndex);
+        r.set(rotation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given time.<br>
+     * Rotation is expressed by Euler angles values in radians.<br>
+     * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+     * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+     * 
+     * @param time the time at which the keyFrame must be inserted
+     * @param x the rotation around the x axis (aka yaw) in radians
+     * @param y the rotation around the y axis (aka roll) in radians
+     * @param z the rotation around the z axis (aka pitch) in radians
+     */
+    public void addTimeRotationAngles(float time, float x, float y, float z) {
+        addKeyFrameRotationAngles((int) (time / tpf), x, y, z);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given key frame index.<br>
+     * Rotation is expressed by Euler angles values in radians.<br>
+     * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+     * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+     * 
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param x the rotation around the x axis (aka yaw) in radians
+     * @param y the rotation around the y axis (aka roll) in radians
+     * @param z the rotation around the z axis (aka pitch) in radians
+     */
+    public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) {
+        Rotation r = getRotationForFrame(keyFrameIndex);
+        r.set(x, y, z);
+
+        // if the delta of euler angles is higher than PI, we create intermediate keyframes
+        // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI
+        int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation);
+        //previous rotation keyframe
+        Rotation prevRot = keyFramesRotation[prev];
+        //the maximum delta angle (x,y or z)
+        float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y));
+        delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z));
+        //if delta > PI we have to create intermediates key frames
+        if (delta >= FastMath.PI) {
+            //frames delta
+            int dF = keyFrameIndex - prev;
+            //angle per frame for x,y ,z
+            float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF;
+            float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF;
+            float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF;
+
+            // the keyFrame step
+            int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP);
+            // the current keyFrame
+            int cursor = prev + keyStep;
+            while (cursor < keyFrameIndex) {
+                //for each step we create a new rotation by interpolating the angles
+                Rotation dr = getRotationForFrame(cursor);
+                dr.masterKeyFrame = keyFrameIndex;
+                dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle);
+                cursor += keyStep;
+            }
+
+        }
+
+    }
+
+    /**
+     * Adds a key frame for the given scale at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param scale the scale to use for this keyFrame
+     */
+    public void addTimeScale(float time, Vector3f scale) {
+        addKeyFrameScale((int) (time / tpf), scale);
+    }
+
+    /**
+     * Adds a key frame for the given scale at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param scale the scale to use for this keyFrame
+     */
+    public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) {
+        Vector3f s = getScaleForFrame(keyFrameIndex);
+        s.set(scale);
+    }
+
+    /**
+     * returns the translation for a given frame index
+     * creates the translation if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the translation
+     */
+    private Vector3f getTranslationForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Vector3f v = keyFramesTranslation[keyFrameIndex];
+        if (v == null) {
+            v = new Vector3f();
+            keyFramesTranslation[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * returns the scale for a given frame index
+     * creates the scale if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the scale
+     */
+    private Vector3f getScaleForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Vector3f v = keyFramesScale[keyFrameIndex];
+        if (v == null) {
+            v = new Vector3f();
+            keyFramesScale[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * returns the rotation for a given frame index
+     * creates the rotation if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the rotation
+     */
+    private Rotation getRotationForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Rotation v = keyFramesRotation[keyFrameIndex];
+        if (v == null) {
+            v = new Rotation();
+            keyFramesRotation[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * Creates an Animation based on the keyFrames previously added to the helper.
+     * @return the generated animation 
+     */
+    public Animation buildAnimation() {
+        interpolateTime();
+        interpolate(keyFramesTranslation, Type.Translation);
+        interpolate(keyFramesRotation, Type.Rotation);
+        interpolate(keyFramesScale, Type.Scale);
+
+        SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales);
+
+        //creating the animation
+        Animation spatialAnimation = new Animation(name, duration);
+        spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack});
+
+        return spatialAnimation;
+    }
+
+    /**
+     * interpolates time values
+     */
+    private void interpolateTime() {
+        for (int i = 0; i < totalFrames; i++) {
+            times[i] = i * tpf;
+        }
+    }
+
+    /**
+     * Interpolates over the key frames for the given keyFrame array and the given type of transform
+     * @param keyFrames the keyFrames array
+     * @param type the type of transforms
+     */
+    private void interpolate(Object[] keyFrames, Type type) {
+        int i = 0;
+        while (i < totalFrames) {
+            //fetching the next keyFrame index transform in the array
+            int key = getNextKeyFrame(i, keyFrames);
+            if (key != -1) {
+                //computing the frame span to interpolate over
+                int span = key - i;
+                //interating over the frames
+                for (int j = i; j <= key; j++) {
+                    // computing interpolation value
+                    float val = (float) (j - i) / (float) span;
+                    //interpolationg depending on the transform type
+                    switch (type) {
+                        case Translation:
+                            translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+                            break;
+                        case Rotation:
+                            Quaternion rot = new Quaternion();
+                            rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val);
+                            break;
+                        case Scale:
+                            scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+                            break;
+                    }
+                }
+                //jumping to the next keyFrame
+                i = key;
+            } else {
+                //No more key frame, filling the array witht he last transform computed.
+                for (int j = i; j < totalFrames; j++) {
+
+                    switch (type) {
+                        case Translation:
+                            translations[j] = ((Vector3f) keyFrames[i]).clone();
+                            break;
+                        case Rotation:
+                            rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone();
+                            break;
+                        case Scale:
+                            scales[j] = ((Vector3f) keyFrames[i]).clone();
+                            break;
+                    }
+                }
+                //we're done
+                i = totalFrames;
+            }
+        }
+    }
+
+    /**
+     * Get the index of the next keyFrame that as a transform
+     * @param index the start index
+     * @param keyFrames the keyFrames array
+     * @return the index of the next keyFrame
+     */
+    private int getNextKeyFrame(int index, Object[] keyFrames) {
+        for (int i = index + 1; i < totalFrames; i++) {
+            if (keyFrames[i] != null) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Get the index of the previous keyFrame that as a transform
+     * @param index the start index
+     * @param keyFrames the keyFrames array
+     * @return the index of the previous keyFrame
+     */
+    private int getPreviousKeyFrame(int index, Object[] keyFrames) {
+        for (int i = index - 1; i >= 0; i--) {
+            if (keyFrames[i] != null) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/Bone.java b/engine/src/core/com/jme3/animation/Bone.java
new file mode 100644
index 0000000..e7e9e2b
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Bone.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.scene.Node;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * <code>Bone</code> describes a bone in the bone-weight skeletal animation
+ * system. A bone contains a name and an index, as well as relevant
+ * transformation data.
+ *
+ * @author Kirill Vainer
+ */
+public final class Bone implements Savable {
+
+    private String name;
+    private Bone parent;
+    private final ArrayList<Bone> children = new ArrayList<Bone>();
+    /**
+     * If enabled, user can control bone transform with setUserTransforms.
+     * Animation transforms are not applied to this bone when enabled.
+     */
+    private boolean userControl = false;
+    /**
+     * The attachment node.
+     */
+    private Node attachNode;
+    /**
+     * Initial transform is the local bind transform of this bone.
+     * PARENT SPACE -> BONE SPACE
+     */
+    private Vector3f initialPos;
+    private Quaternion initialRot;
+    private Vector3f initialScale;
+    /**
+     * The inverse world bind transform.
+     * BONE SPACE -> MODEL SPACE
+     */
+    private Vector3f worldBindInversePos;
+    private Quaternion worldBindInverseRot;
+    private Vector3f worldBindInverseScale;
+    /**
+     * The local animated transform combined with the local bind transform and parent world transform
+     */
+    private Vector3f localPos = new Vector3f();
+    private Quaternion localRot = new Quaternion();
+    private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f);
+    /**
+     * MODEL SPACE -> BONE SPACE (in animated state)
+     */
+    private Vector3f worldPos = new Vector3f();
+    private Quaternion worldRot = new Quaternion();
+    private Vector3f worldScale = new Vector3f();
+    //used for getCombinedTransform 
+    private Transform tmpTransform = new Transform();
+
+    /**
+     * Creates a new bone with the given name.
+     * 
+     * @param name Name to give to this bone
+     */
+    public Bone(String name) {
+        if (name == null)
+            throw new IllegalArgumentException("Name cannot be null");
+        
+        this.name = name;
+
+        initialPos = new Vector3f();
+        initialRot = new Quaternion();
+        initialScale = new Vector3f(1, 1, 1);
+
+        worldBindInversePos = new Vector3f();
+        worldBindInverseRot = new Quaternion();
+        worldBindInverseScale = new Vector3f();
+    }
+
+    /**
+     * Special-purpose copy constructor. 
+     * <p>
+     * Only copies the name and bind pose from the original.
+     * <p>
+     * WARNING: Local bind pose and world inverse bind pose transforms shallow 
+     * copied. Modifying that data on the original bone will cause it to
+     * be recomputed on any cloned bones.
+     * <p>
+     * The rest of the data is <em>NOT</em> copied, as it will be
+     * generated automatically when the bone is animated.
+     * 
+     * @param source The bone from which to copy the data.
+     */
+    Bone(Bone source) {
+        this.name = source.name;
+
+        userControl = source.userControl;
+
+        initialPos = source.initialPos;
+        initialRot = source.initialRot;
+        initialScale = source.initialScale;
+
+        worldBindInversePos = source.worldBindInversePos;
+        worldBindInverseRot = source.worldBindInverseRot;
+        worldBindInverseScale = source.worldBindInverseScale;
+
+        // parent and children will be assigned manually..
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Bone() {
+    }
+
+    /**
+     * Returns the name of the bone, set in the constructor.
+     * 
+     * @return The name of the bone, set in the constructor.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns parent bone of this bone, or null if it is a root bone.
+     * @return The parent bone of this bone, or null if it is a root bone.
+     */
+    public Bone getParent() {
+        return parent;
+    }
+
+    /**
+     * Returns all the children bones of this bone.
+     * 
+     * @return All the children bones of this bone.
+     */
+    public ArrayList<Bone> getChildren() {
+        return children;
+    }
+
+    /**
+     * Returns the local position of the bone, relative to the parent bone.
+     * 
+     * @return The local position of the bone, relative to the parent bone.
+     */
+    public Vector3f getLocalPosition() {
+        return localPos;
+    }
+
+    /**
+     * Returns the local rotation of the bone, relative to the parent bone.
+     * 
+     * @return The local rotation of the bone, relative to the parent bone.
+     */
+    public Quaternion getLocalRotation() {
+        return localRot;
+    }
+
+    /**
+     * Returns the local scale of the bone, relative to the parent bone.
+     * 
+     * @return The local scale of the bone, relative to the parent bone.
+     */
+    public Vector3f getLocalScale() {
+        return localScale;
+    }
+
+    /**
+     * Returns the position of the bone in model space.
+     * 
+     * @return The position of the bone in model space.
+     */
+    public Vector3f getModelSpacePosition() {
+        return worldPos;
+    }
+
+    /**
+     * Returns the rotation of the bone in model space.
+     * 
+     * @return The rotation of the bone in model space.
+     */
+    public Quaternion getModelSpaceRotation() {
+        return worldRot;
+    }
+
+    /**
+     * Returns the scale of the bone in model space.
+     * 
+     * @return The scale of the bone in model space.
+     */
+    public Vector3f getModelSpaceScale() {
+        return worldScale;
+    }
+
+    /**
+     * Returns the inverse world bind pose position.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the inverse world bind pose position.
+     */
+    public Vector3f getWorldBindInversePosition() {
+        return worldBindInversePos;
+    }
+
+    /**
+     * Returns the inverse world bind pose rotation.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the inverse world bind pose rotation.
+     */
+    public Quaternion getWorldBindInverseRotation() {
+        return worldBindInverseRot;
+    }
+
+    /**
+     * Returns the inverse world bind pose scale.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the inverse world bind pose scale.
+     */
+    public Vector3f getWorldBindInverseScale() {
+        return worldBindInverseScale;
+    }
+
+    /**
+     * Returns the world bind pose position.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the world bind pose position.
+     */
+    public Vector3f getWorldBindPosition() {
+        return initialPos;
+    }
+
+    /**
+     * Returns the world bind pose rotation.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the world bind pose rotation.
+     */
+    public Quaternion getWorldBindRotation() {
+        return initialRot;
+    }
+
+    /**
+     * Returns the world bind pose scale.
+     * <p>
+     * The bind pose transform of the bone is its "default"
+     * transform with no animation applied.
+     * 
+     * @return the world bind pose scale.
+     */
+    public Vector3f getWorldBindScale() {
+        return initialScale;
+    }
+
+    /**
+     * If enabled, user can control bone transform with setUserTransforms.
+     * Animation transforms are not applied to this bone when enabled.
+     */
+    public void setUserControl(boolean enable) {
+        userControl = enable;
+    }
+
+    /**
+     * Add a new child to this bone. Shouldn't be used by user code.
+     * Can corrupt skeleton.
+     * 
+     * @param bone The bone to add
+     */
+    public void addChild(Bone bone) {
+        children.add(bone);
+        bone.parent = this;
+    }
+
+    /**
+     * Updates the world transforms for this bone, and, possibly the attach node
+     * if not null.
+     * <p>
+     * The world transform of this bone is computed by combining the parent's
+     * world transform with this bones' local transform.
+     */
+    public final void updateWorldVectors() {
+        if (parent != null) {
+            //rotation
+            parent.worldRot.mult(localRot, worldRot);
+
+            //scale
+            //For scale parent scale is not taken into account!
+            // worldScale.set(localScale);
+            parent.worldScale.mult(localScale, worldScale);
+
+            //translation
+            //scale and rotation of parent affect bone position            
+            parent.worldRot.mult(localPos, worldPos);
+            worldPos.multLocal(parent.worldScale);
+            worldPos.addLocal(parent.worldPos);
+        } else {
+            worldRot.set(localRot);
+            worldPos.set(localPos);
+            worldScale.set(localScale);
+        }
+
+        if (attachNode != null) {
+            attachNode.setLocalTranslation(worldPos);
+            attachNode.setLocalRotation(worldRot);
+            attachNode.setLocalScale(worldScale);
+        }
+    }
+
+    /**
+     * Updates world transforms for this bone and it's children.
+     */
+    final void update() {
+        this.updateWorldVectors();
+
+        for (int i = children.size() - 1; i >= 0; i--) {
+            children.get(i).update();
+        }
+    }
+
+    /**
+     * Saves the current bone state as its binding pose, including its children.
+     */
+    void setBindingPose() {
+        initialPos.set(localPos);
+        initialRot.set(localRot);
+        initialScale.set(localScale);
+
+        if (worldBindInversePos == null) {
+            worldBindInversePos = new Vector3f();
+            worldBindInverseRot = new Quaternion();
+            worldBindInverseScale = new Vector3f();
+        }
+
+        // Save inverse derived position/scale/orientation, used for calculate offset transform later
+        worldBindInversePos.set(worldPos);
+        worldBindInversePos.negateLocal();
+
+        worldBindInverseRot.set(worldRot);
+        worldBindInverseRot.inverseLocal();
+
+        worldBindInverseScale.set(Vector3f.UNIT_XYZ);
+        worldBindInverseScale.divideLocal(worldScale);
+
+        for (Bone b : children) {
+            b.setBindingPose();
+        }
+    }
+
+    /**
+     * Reset the bone and it's children to bind pose.
+     */
+    final void reset() {
+        if (!userControl) {
+            localPos.set(initialPos);
+            localRot.set(initialRot);
+            localScale.set(initialScale);
+        }
+
+        for (int i = children.size() - 1; i >= 0; i--) {
+            children.get(i).reset();
+        }
+    }
+
+     /**
+     * Stores the skinning transform in the specified Matrix4f.
+     * The skinning transform applies the animation of the bone to a vertex.
+     * 
+     * This assumes that the world transforms for the entire bone hierarchy
+     * have already been computed, otherwise this method will return undefined
+     * results.
+     * 
+     * @param outTransform
+     */
+    void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) {
+        // Computing scale
+        Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3);
+
+        // Computing rotation
+        Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1);
+
+        // Computing translation
+        // Translation depend on rotation and scale
+        Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2);
+
+        // Populating the matrix
+        outTransform.loadIdentity();
+        outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4));
+    }
+
+    /**
+     * Sets user transform.
+     */
+    public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+        if (!userControl) {
+            throw new IllegalStateException("User control must be on bone to allow user transforms");
+        }
+
+        localPos.set(initialPos);
+        localRot.set(initialRot);
+        localScale.set(initialScale);
+
+        localPos.addLocal(translation);
+        localRot = localRot.mult(rotation);
+        localScale.multLocal(scale);
+    }
+
+    /**
+     * Must update all bones in skeleton for this to work.
+     * @param translation
+     * @param rotation
+     */
+    public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
+        if (!userControl) {
+            throw new IllegalStateException("User control must be on bone to allow user transforms");
+        }
+
+        // TODO: add scale here ???
+        worldPos.set(translation);
+        worldRot.set(rotation);
+        
+        //if there is an attached Node we need to set it's local transforms too.
+        if(attachNode != null){
+            attachNode.setLocalTranslation(translation);
+            attachNode.setLocalRotation(rotation);
+        }
+    }
+
+    /**
+     * Returns the local transform of this bone combined with the given position and rotation
+     * @param position a position
+     * @param rotation a rotation
+     */
+    public Transform getCombinedTransform(Vector3f position, Quaternion rotation) {
+        rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position);
+        tmpTransform.setRotation(rotation).getRotation().multLocal(localRot);
+        return tmpTransform;
+    }
+
+    /**
+     * Returns the attachment node.
+     * Attach models and effects to this node to make
+     * them follow this bone's motions.
+     */
+    Node getAttachmentsNode() {
+        if (attachNode == null) {
+            attachNode = new Node(name + "_attachnode");
+            attachNode.setUserData("AttachedBone", this);
+        }
+        return attachNode;
+    }
+
+    /**
+     * Used internally after model cloning.
+     * @param attachNode
+     */
+    void setAttachmentsNode(Node attachNode) {
+        this.attachNode = attachNode;
+    }
+
+    /**
+     * Sets the local animation transform of this bone.
+     * Bone is assumed to be in bind pose when this is called.
+     */
+    void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+        if (userControl) {
+            return;
+        }
+
+//        localPos.addLocal(translation);
+//        localRot.multLocal(rotation);
+        //localRot = localRot.mult(rotation);
+
+        localPos.set(initialPos).addLocal(translation);
+        localRot.set(initialRot).multLocal(rotation);
+
+        if (scale != null) {
+            localScale.set(initialScale).multLocal(scale);
+        }
+    }
+
+    void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
+        if (userControl) {
+            return;
+        }
+
+        TempVars vars = TempVars.get();
+//        assert vars.lock();
+
+        Vector3f tmpV = vars.vect1;
+        Vector3f tmpV2 = vars.vect2;
+        Quaternion tmpQ = vars.quat1;
+
+        //location
+        tmpV.set(initialPos).addLocal(translation);
+        localPos.interpolate(tmpV, weight);
+
+        //rotation
+        tmpQ.set(initialRot).multLocal(rotation);
+        localRot.nlerp(tmpQ, weight);
+
+        //scale
+        if (scale != null) {
+            tmpV2.set(initialScale).multLocal(scale);
+            localScale.interpolate(tmpV2, weight);
+        }
+
+
+        vars.release();
+    }
+
+    /**
+     * Sets local bind transform for bone.
+     * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them.
+     */
+    public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+        initialPos.set(translation);
+        initialRot.set(rotation);
+        //ogre.xml can have null scale values breaking this if the check is removed
+        if (scale != null) {
+            initialScale.set(scale);
+        }
+
+        localPos.set(translation);
+        localRot.set(rotation);
+        if (scale != null) {
+            localScale.set(scale);
+        }
+    }
+
+    private String toString(int depth) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < depth; i++) {
+            sb.append('-');
+        }
+
+        sb.append(name).append(" bone\n");
+        for (Bone child : children) {
+            sb.append(child.toString(depth + 1));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String toString() {
+        return this.toString(0);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule input = im.getCapsule(this);
+
+        name = input.readString("name", null);
+        initialPos = (Vector3f) input.readSavable("initialPos", null);
+        initialRot = (Quaternion) input.readSavable("initialRot", null);
+        initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
+        attachNode = (Node) input.readSavable("attachNode", null);
+
+        localPos.set(initialPos);
+        localRot.set(initialRot);
+
+        ArrayList<Bone> childList = input.readSavableArrayList("children", null);
+        for (int i = childList.size() - 1; i >= 0; i--) {
+            this.addChild(childList.get(i));
+        }
+
+        // NOTE: Parent skeleton will call update() then setBindingPose()
+        // after Skeleton has been de-serialized.
+        // Therefore, worldBindInversePos and worldBindInverseRot
+        // will be reconstructed based on that information.
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule output = ex.getCapsule(this);
+
+        output.write(name, "name", null);
+        output.write(attachNode, "attachNode", null);
+        output.write(initialPos, "initialPos", null);
+        output.write(initialRot, "initialRot", null);
+        output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
+        output.writeSavableArrayList(children, "children", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/BoneAnimation.java b/engine/src/core/com/jme3/animation/BoneAnimation.java
new file mode 100644
index 0000000..f5ffd40
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/BoneAnimation.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+/**
+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
+ */
+@Deprecated
+public final class BoneAnimation extends Animation {
+
+    @Deprecated
+    public BoneAnimation(String name, float length) {
+        super(name, length);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/BoneTrack.java b/engine/src/core/com/jme3/animation/BoneTrack.java
new file mode 100644
index 0000000..0d28ddc
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/BoneTrack.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.BitSet;
+
+/**
+ * Contains a list of transforms and times for each keyframe.
+ * 
+ * @author Kirill Vainer
+ */
+public final class BoneTrack implements Track {
+
+    /**
+     * Bone index in the skeleton which this track effects.
+     */
+    private int targetBoneIndex;
+    
+    /**
+     * Transforms and times for track.
+     */
+    private CompactVector3Array translations;
+    private CompactQuaternionArray rotations;
+    private CompactVector3Array scales;
+    private float[] times;
+    
+    /**
+     * Serialization-only. Do not use.
+     */
+    public BoneTrack() {
+    }
+
+    /**
+     * Creates a bone track for the given bone index
+     * @param targetBoneIndex the bone index
+     * @param times a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations the rotation of the bone for each frame
+     */
+    public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations) {
+        this.targetBoneIndex = targetBoneIndex;
+        this.setKeyframes(times, translations, rotations);
+    }
+
+    /**
+     * Creates a bone track for the given bone index
+     * @param targetBoneIndex the bone index
+     * @param times a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations the rotation of the bone for each frame
+     * @param scales the scale of the bone for each frame
+     */
+    public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+    	this.targetBoneIndex = targetBoneIndex;
+        this.setKeyframes(times, translations, rotations, scales);
+    }
+
+    /**
+     * Creates a bone track for the given bone index
+     * @param targetBoneIndex the bone's index
+     */
+    public BoneTrack(int targetBoneIndex) {
+        this.targetBoneIndex = targetBoneIndex;
+    }
+
+    /**
+     * @return the bone index of this bone track.
+     */
+    public int getTargetBoneIndex() {
+        return targetBoneIndex;
+    }
+
+    /**
+     * return the array of rotations of this track
+     * @return 
+     */
+    public Quaternion[] getRotations() {
+        return rotations.toObjectArray();
+    }
+
+    /**
+     * returns the array of scales for this track
+     * @return 
+     */
+    public Vector3f[] getScales() {
+        return scales == null ? null : scales.toObjectArray();
+    }
+
+    /**
+     * returns the arrays of time for this track
+     * @return 
+     */
+    public float[] getTimes() {
+        return times;
+    }
+
+    /**
+     * returns the array of translations of this track
+     * @return 
+     */
+    public Vector3f[] getTranslations() {
+        return translations.toObjectArray();
+    }
+
+    /**
+     * Set the translations and rotations for this bone track
+     * @param times a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations the rotation of the bone for each frame
+     */
+    public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations) {
+        if (times.length == 0) {
+            throw new RuntimeException("BoneTrack with no keyframes!");
+        }
+
+        assert times.length == translations.length && times.length == rotations.length;
+
+        this.times = times;
+        this.translations = new CompactVector3Array();
+        this.translations.add(translations);
+        this.translations.freeze();
+        this.rotations = new CompactQuaternionArray();
+        this.rotations.add(rotations);
+        this.rotations.freeze();
+    }
+
+    /**
+     * Set the translations, rotations and scales for this bone track
+     * @param times a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations the rotation of the bone for each frame
+     * @param scales the scale of the bone for each frame
+     */
+    public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+        this.setKeyframes(times, translations, rotations);
+        assert times.length == scales.length;
+        if (scales != null) {
+            this.scales = new CompactVector3Array();
+            this.scales.add(scales);
+            this.scales.freeze();
+        }
+    }
+
+    /**
+     * 
+     * Modify the bone which this track modifies in the skeleton to contain
+     * the correct animation transforms for a given time.
+     * The transforms can be interpolated in some method from the keyframes.
+     *
+     * @param time the current time of the animation
+     * @param weight the weight of the animation
+     * @param control
+     * @param channel
+     * @param vars
+     */
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+        BitSet affectedBones = channel.getAffectedBones();
+        if (affectedBones != null && !affectedBones.get(targetBoneIndex)) {
+            return;
+        }
+        
+        Bone target = control.getSkeleton().getBone(targetBoneIndex);
+
+        Vector3f tempV = vars.vect1;
+        Vector3f tempS = vars.vect2;
+        Quaternion tempQ = vars.quat1;
+        Vector3f tempV2 = vars.vect3;
+        Vector3f tempS2 = vars.vect4;
+        Quaternion tempQ2 = vars.quat2;
+        
+        int lastFrame = times.length - 1;
+        if (time < 0 || lastFrame == 0) {
+            rotations.get(0, tempQ);
+            translations.get(0, tempV);
+            if (scales != null) {
+                scales.get(0, tempS);
+            }
+        } else if (time >= times[lastFrame]) {
+            rotations.get(lastFrame, tempQ);
+            translations.get(lastFrame, tempV);
+            if (scales != null) {
+                scales.get(lastFrame, tempS);
+            }
+        } else {
+            int startFrame = 0;
+            int endFrame = 1;
+            // use lastFrame so we never overflow the array
+            int i;
+            for (i = 0; i < lastFrame && times[i] < time; i++) {
+                startFrame = i;
+                endFrame = i + 1;
+            }
+
+            float blend = (time - times[startFrame])
+                    / (times[endFrame] - times[startFrame]);
+
+            rotations.get(startFrame, tempQ);
+            translations.get(startFrame, tempV);
+            if (scales != null) {
+                scales.get(startFrame, tempS);
+            }
+            rotations.get(endFrame, tempQ2);
+            translations.get(endFrame, tempV2);
+            if (scales != null) {
+                scales.get(endFrame, tempS2);
+            }
+            tempQ.nlerp(tempQ2, blend);
+            tempV.interpolate(tempV2, blend);
+            tempS.interpolate(tempS2, blend);
+        }
+
+        if (weight != 1f) {
+            target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight);
+        } else {
+            target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
+        }
+    }
+    
+    /**
+     * @return the length of the track
+     */
+    public float getLength() {
+        return times == null ? 0 : times[times.length - 1] - times[0];
+    }
+
+    /**
+     * This method creates a clone of the current object.
+     * @return a clone of the current object
+     */
+    @Override
+    public BoneTrack clone() {
+        int tablesLength = times.length;
+
+        float[] times = this.times.clone();
+        Vector3f[] sourceTranslations = this.getTranslations();
+        Quaternion[] sourceRotations = this.getRotations();
+        Vector3f[] sourceScales = this.getScales();
+
+        Vector3f[] translations = new Vector3f[tablesLength];
+        Quaternion[] rotations = new Quaternion[tablesLength];
+        Vector3f[] scales = new Vector3f[tablesLength];
+        for (int i = 0; i < tablesLength; ++i) {
+            translations[i] = sourceTranslations[i].clone();
+            rotations[i] = sourceRotations[i].clone();
+            scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);
+        }
+        
+        // Need to use the constructor here because of the final fields used in this class
+        return new BoneTrack(targetBoneIndex, times, translations, rotations, scales);
+    }
+    
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(targetBoneIndex, "boneIndex", 0);
+        oc.write(translations, "translations", null);
+        oc.write(rotations, "rotations", null);
+        oc.write(times, "times", null);
+        oc.write(scales, "scales", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        targetBoneIndex = ic.readInt("boneIndex", 0);
+
+        translations = (CompactVector3Array) ic.readSavable("translations", null);
+        rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
+        times = ic.readFloatArray("times", null);
+        scales = (CompactVector3Array) ic.readSavable("scales", null);
+
+        //Backward compatibility for old j3o files generated before revision 6807
+        if (im.getFormatVersion() == 0){
+            if (translations == null) {
+                Savable[] sav = ic.readSavableArray("translations", null);
+                if (sav != null) {
+                    translations = new CompactVector3Array();
+                    Vector3f[] transCopy = new Vector3f[sav.length];
+                    System.arraycopy(sav, 0, transCopy, 0, sav.length);
+                    translations.add(transCopy);
+                    translations.freeze();
+                }
+            }
+            if (rotations == null) {
+                Savable[] sav = ic.readSavableArray("rotations", null);
+                if (sav != null) {
+                    rotations = new CompactQuaternionArray();
+                    Quaternion[] rotCopy = new Quaternion[sav.length];
+                    System.arraycopy(sav, 0, rotCopy, 0, sav.length);
+                    rotations.add(rotCopy);
+                    rotations.freeze();
+                }
+            }
+        }
+    }
+
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/CompactArray.java b/engine/src/core/com/jme3/animation/CompactArray.java
new file mode 100644
index 0000000..6a2a9be
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactArray.java
@@ -0,0 +1,270 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.animation;

+

+import java.lang.reflect.Array;

+import java.util.HashMap;

+import java.util.Map;

+

+/**

+ * Object is indexed and stored in primitive float[]

+ * @author Lim, YongHoon

+ * @param <T>

+ */

+public abstract class CompactArray<T> {

+

+    private Map<T, Integer> indexPool = new HashMap<T, Integer>();

+    protected int[] index;

+    protected float[] array;

+    private boolean invalid;

+

+    /**

+     * Creates a compact array

+     */

+    public CompactArray() {

+    }

+

+    /**

+     * create array using serialized data

+     * @param compressedArray

+     * @param index

+     */

+    public CompactArray(float[] compressedArray, int[] index) {

+        this.array = compressedArray;

+        this.index = index;

+    }

+

+    /**

+     * Add objects.

+     * They are serialized automatically when get() method is called.

+     * @param objArray

+     */

+    public void add(T... objArray) {

+        if (objArray == null || objArray.length == 0) {

+            return;

+        }

+        invalid = true;

+        int base = 0;

+        if (index == null) {

+            index = new int[objArray.length];

+        } else {

+            if (indexPool.isEmpty()) {

+                throw new RuntimeException("Internal is already fixed");

+            }

+            base = index.length;

+

+            int[] tmp = new int[base + objArray.length];

+            System.arraycopy(index, 0, tmp, 0, index.length);

+            index = tmp;

+            //index = Arrays.copyOf(index, base+objArray.length);

+        }

+        for (int j = 0; j < objArray.length; j++) {

+            T obj = objArray[j];

+            if (obj == null) {

+                index[base + j] = -1;

+            } else {

+                Integer i = indexPool.get(obj);

+                if (i == null) {

+                    i = indexPool.size();

+                    indexPool.put(obj, i);

+                }

+                index[base + j] = i;

+            }

+        }

+    }

+

+    /**

+     * release objects.

+     * add() method call is not allowed anymore.

+     */

+    public void freeze() {

+        serialize();

+        indexPool.clear();

+    }

+

+    /**

+     * @param index

+     * @param value

+     */

+    public final void set(int index, T value) {

+        int j = getCompactIndex(index);

+        serialize(j, value);

+    }

+

+    /**

+     * returns the object for the given index

+     * @param index the index

+     * @param store an object to store the result 

+     * @return 

+     */

+    public final T get(int index, T store) {

+        serialize();

+        int j = getCompactIndex(index);

+        return deserialize(j, store);

+    }

+

+    /**

+     * return a float array of serialized data

+     * @return 

+     */

+    public final float[] getSerializedData() {

+        serialize();

+        return array;

+    }

+

+    /**

+     * serialize this compact array

+     */

+    public final void serialize() {

+        if (invalid) {

+            int newSize = indexPool.size() * getTupleSize();

+            if (array == null || Array.getLength(array) < newSize) {

+                array = ensureCapacity(array, newSize);

+                for (Map.Entry<T, Integer> entry : indexPool.entrySet()) {

+                    int i = entry.getValue();

+                    T obj = entry.getKey();

+                    serialize(i, obj);

+                }

+            }

+            invalid = false;

+        }

+    }

+

+    /**

+     * @return compacted array's primitive size

+     */

+    protected final int getSerializedSize() {

+        return Array.getLength(getSerializedData());

+    }

+

+    /**

+     * Ensure the capacity for the given array and the given size

+     * @param arr the array

+     * @param size the size

+     * @return 

+     */

+    protected float[] ensureCapacity(float[] arr, int size) {

+        if (arr == null) {

+            return new float[size];

+        } else if (arr.length >= size) {

+            return arr;

+        } else {

+            float[] tmp = new float[size];

+            System.arraycopy(arr, 0, tmp, 0, arr.length);

+            return tmp;

+            //return Arrays.copyOf(arr, size);

+        }

+    }

+

+    /**

+     * retrun an array of indices for the given objects

+     * @param objArray

+     * @return 

+     */

+    public final int[] getIndex(T... objArray) {

+        int[] index = new int[objArray.length];

+        for (int i = 0; i < index.length; i++) {

+            T obj = objArray[i];

+            index[i] = obj != null ? indexPool.get(obj) : -1;

+        }

+        return index;

+    }

+

+    /**

+     * returns the corresponding index in the compact array

+     * @param objIndex

+     * @return object index in the compacted object array

+     */

+    public int getCompactIndex(int objIndex) {

+        return index != null ? index[objIndex] : objIndex;

+    }

+

+    /**

+     * @return uncompressed object size

+     */

+    public final int getTotalObjectSize() {

+        assert getSerializedSize() % getTupleSize() == 0;

+        return index != null ? index.length : getSerializedSize() / getTupleSize();

+    }

+

+    /**

+     * @return compressed object size

+     */

+    public final int getCompactObjectSize() {

+        assert getSerializedSize() % getTupleSize() == 0;

+        return getSerializedSize() / getTupleSize();

+    }

+

+    /**

+     * decompress and return object array

+     * @return decompress and return object array

+     */

+    public final T[] toObjectArray() {

+        try {

+            T[] compactArr = (T[]) Array.newInstance(getElementClass(), getSerializedSize() / getTupleSize());

+            for (int i = 0; i < compactArr.length; i++) {

+                compactArr[i] = getElementClass().newInstance();

+                deserialize(i, compactArr[i]);

+            }

+

+            T[] objArr = (T[]) Array.newInstance(getElementClass(), getTotalObjectSize());

+            for (int i = 0; i < objArr.length; i++) {

+                int compactIndex = getCompactIndex(i);

+                objArr[i] = compactArr[compactIndex];

+            }

+            return objArr;

+        } catch (Exception e) {

+            return null;

+        }

+    }

+

+    /**

+     * serialize object

+     * @param compactIndex compacted object index

+     * @param store

+     */

+    protected abstract void serialize(int compactIndex, T store);

+

+    /**

+     * deserialize object

+     * @param compactIndex compacted object index

+     * @param store

+     */

+    protected abstract T deserialize(int compactIndex, T store);

+

+    /**

+     * serialized size of one object element

+     */

+    protected abstract int getTupleSize();

+

+    protected abstract Class<T> getElementClass();

+}

diff --git a/engine/src/core/com/jme3/animation/CompactQuaternionArray.java b/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
new file mode 100644
index 0000000..7c56c41
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
@@ -0,0 +1,100 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.animation;

+

+import com.jme3.export.*;

+import com.jme3.math.Quaternion;

+import java.io.IOException;

+

+/**

+ * Serialize and compress {@link Quaternion}[] by indexing same values

+ * It is converted to float[]

+ * @author Lim, YongHoon

+ */

+public class CompactQuaternionArray extends CompactArray<Quaternion> implements Savable {

+

+    /**

+     * creates a compact Quaternion array

+     */

+    public CompactQuaternionArray() {

+    }

+

+    /**

+     * creates a compact Quaternion array

+     * @param dataArray the data array

+     * @param index  the indices array

+     */

+    public CompactQuaternionArray(float[] dataArray, int[] index) {

+        super(dataArray, index);

+    }

+

+    @Override

+    protected final int getTupleSize() {

+        return 4;

+    }

+

+    @Override

+    protected final Class<Quaternion> getElementClass() {

+        return Quaternion.class;

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        serialize();

+        OutputCapsule out = ex.getCapsule(this);

+        out.write(array, "array", null);

+        out.write(index, "index", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule in = im.getCapsule(this);

+        array = in.readFloatArray("array", null);

+        index = in.readIntArray("index", null);

+    }

+

+    @Override

+    protected void serialize(int i, Quaternion store) {

+        int j = i * getTupleSize();

+        array[j] = store.getX();

+        array[j + 1] = store.getY();

+        array[j + 2] = store.getZ();

+        array[j + 3] = store.getW();

+    }

+

+    @Override

+    protected Quaternion deserialize(int i, Quaternion store) {

+        int j = i * getTupleSize();

+        store.set(array[j], array[j + 1], array[j + 2], array[j + 3]);

+        return store;

+    }

+}

diff --git a/engine/src/core/com/jme3/animation/CompactVector3Array.java b/engine/src/core/com/jme3/animation/CompactVector3Array.java
new file mode 100644
index 0000000..cdfc6e4
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactVector3Array.java
@@ -0,0 +1,99 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.animation;

+

+import com.jme3.export.*;

+import com.jme3.math.Vector3f;

+import java.io.IOException;

+

+/**

+ * Serialize and compress Vector3f[] by indexing same values

+ * @author Lim, YongHoon

+ */

+public class CompactVector3Array extends CompactArray<Vector3f> implements Savable {

+

+    /**

+     * Creates a compact vector array

+     */

+    public CompactVector3Array() {

+    }

+

+    /**

+     * creates a compact vector array

+     * @param dataArray the data array

+     * @param index the indices

+     */

+    public CompactVector3Array(float[] dataArray, int[] index) {

+        super(dataArray, index);

+    }

+

+    @Override

+    protected final int getTupleSize() {

+        return 3;

+    }

+

+    @Override

+    protected final Class<Vector3f> getElementClass() {

+        return Vector3f.class;

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        serialize();

+        OutputCapsule out = ex.getCapsule(this);

+        out.write(array, "array", null);

+        out.write(index, "index", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule in = im.getCapsule(this);

+        array = in.readFloatArray("array", null);

+        index = in.readIntArray("index", null);

+    }

+    

+    @Override

+    protected void serialize(int i, Vector3f store) {

+        int j = i*getTupleSize();

+        array[j] = store.getX();

+        array[j+1] = store.getY();

+        array[j+2] = store.getZ();

+    }

+

+    @Override

+    protected Vector3f deserialize(int i, Vector3f store) {

+        int j = i*getTupleSize();

+        store.set(array[j], array[j+1], array[j+2]);

+        return store;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/animation/LoopMode.java b/engine/src/core/com/jme3/animation/LoopMode.java
new file mode 100644
index 0000000..b74461e
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/LoopMode.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+/**
+ * <code>LoopMode</code> determines how animations repeat, or if they
+ * do not repeat.
+ */
+public enum LoopMode {
+    /**
+     * The animation will play repeatedly, when it reaches the end
+     * the animation will play again from the beginning, and so on.
+     */
+    Loop,
+
+    /**
+     * The animation will not loop. It will play until the last frame, and then
+     * freeze at that frame. It is possible to decide to play a new animation
+     * when that happens by using a AnimEventListener.
+     */
+    DontLoop,
+
+    /**
+     * The animation will cycle back and forth. When reaching the end, the
+     * animation will play backwards from the last frame until it reaches
+     * the first frame.
+     */
+    Cycle,
+
+}
diff --git a/engine/src/core/com/jme3/animation/Pose.java b/engine/src/core/com/jme3/animation/Pose.java
new file mode 100644
index 0000000..9ff6ec8
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Pose.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A pose is a list of offsets that say where a mesh vertices should be for this pose.
+ */
+public final class Pose implements Savable, Cloneable {
+
+    private String name;
+    private int targetMeshIndex;
+
+    private Vector3f[] offsets;
+    private int[] indices;
+
+    private transient final Vector3f tempVec  = new Vector3f();
+    private transient final Vector3f tempVec2 = new Vector3f();
+
+    public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices){
+        this.name = name;
+        this.targetMeshIndex = targetMeshIndex;
+        this.offsets = offsets;
+        this.indices = indices;
+    }
+
+    public int getTargetMeshIndex(){
+        return targetMeshIndex;
+    }
+
+
+    /**
+     * Applies the offsets of this pose to the vertex buffer given by the blend factor.
+     *
+     * @param blend Blend factor, 0 = no change to vertex buffer, 1 = apply full offsets
+     * @param vertbuf Vertex buffer to apply this pose to
+     */
+    public void apply(float blend, FloatBuffer vertbuf){
+        for (int i = 0; i < indices.length; i++){
+            Vector3f offset = offsets[i];
+            int vertIndex   = indices[i];
+
+            tempVec.set(offset).multLocal(blend);
+
+            // acquire vertex
+            BufferUtils.populateFromBuffer(tempVec2, vertbuf, vertIndex);
+
+            // add offset multiplied by factor
+            tempVec2.addLocal(tempVec);
+
+            // write modified vertex
+            BufferUtils.setInBuffer(tempVec2, vertbuf, vertIndex);
+        }
+    }
+    
+    /**
+     * This method creates a clone of the current object.
+     * @return a clone of the current object
+     */
+    public Pose clone() {
+		try {
+			Pose result = (Pose) super.clone();
+            result.indices = this.indices.clone();
+            if(this.offsets!=null) {
+            	result.offsets = new Vector3f[this.offsets.length];
+            	for(int i=0;i<this.offsets.length;++i) {
+            		result.offsets[i] = this.offsets[i].clone();
+            	}
+            }
+    		return result;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+	}
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule out = e.getCapsule(this);
+        out.write(name, "name", "");
+        out.write(targetMeshIndex, "meshIndex", -1);
+        out.write(offsets, "offsets", null);
+        out.write(indices, "indices", null);
+    }
+
+    public void read(JmeImporter i) throws IOException {
+        InputCapsule in = i.getCapsule(this);
+        name = in.readString("name", "");
+        targetMeshIndex = in.readInt("meshIndex", -1);
+        offsets = (Vector3f[]) in.readSavableArray("offsets", null);
+        indices = in.readIntArray("indices", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/PoseTrack.java b/engine/src/core/com/jme3/animation/PoseTrack.java
new file mode 100644
index 0000000..bf1b0ab
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/PoseTrack.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A single track of pose animation associated with a certain mesh.
+ */
+@Deprecated
+public final class PoseTrack implements Track {
+    
+    private int targetMeshIndex;
+    private PoseFrame[] frames;
+    private float[] times;
+
+    public static class PoseFrame implements Savable, Cloneable {
+
+        Pose[] poses;
+        float[] weights;
+
+        public PoseFrame(Pose[] poses, float[] weights) {
+            this.poses = poses;
+            this.weights = weights;
+        }
+        
+        /**
+         * This method creates a clone of the current object.
+         * @return a clone of the current object
+         */
+        @Override
+        public PoseFrame clone() {
+            try {
+                PoseFrame result = (PoseFrame) super.clone();
+                result.weights = this.weights.clone();
+                if (this.poses != null) {
+                    result.poses = new Pose[this.poses.length];
+                    for (int i = 0; i < this.poses.length; ++i) {
+                        result.poses[i] = this.poses[i].clone();
+                    }
+                }
+                return result;
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError();
+            }
+        }
+
+        public void write(JmeExporter e) throws IOException {
+            OutputCapsule out = e.getCapsule(this);
+            out.write(poses, "poses", null);
+            out.write(weights, "weights", null);
+        }
+
+        public void read(JmeImporter i) throws IOException {
+            InputCapsule in = i.getCapsule(this);
+            poses = (Pose[]) in.readSavableArray("poses", null);
+            weights = in.readFloatArray("weights", null);
+        }
+    }
+
+    public PoseTrack(int targetMeshIndex, float[] times, PoseFrame[] frames){
+        this.targetMeshIndex = targetMeshIndex;
+        this.times = times;
+        this.frames = frames;
+    }
+    
+    private void applyFrame(Mesh target, int frameIndex, float weight){
+        PoseFrame frame = frames[frameIndex];
+        VertexBuffer pb = target.getBuffer(Type.Position);
+        for (int i = 0; i < frame.poses.length; i++){
+            Pose pose = frame.poses[i];
+            float poseWeight = frame.weights[i] * weight;
+
+            pose.apply(poseWeight, (FloatBuffer) pb.getData());
+        }
+
+        // force to re-upload data to gpu
+        pb.updateData(pb.getData());
+    }
+
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+        // TODO: When MeshControl is created, it will gather targets
+        // list automatically which is then retrieved here.
+        
+        /*
+        Mesh target = targets[targetMeshIndex];
+        if (time < times[0]) {
+            applyFrame(target, 0, weight);
+        } else if (time > times[times.length - 1]) {
+            applyFrame(target, times.length - 1, weight);
+        } else {
+            int startFrame = 0;
+            for (int i = 0; i < times.length; i++) {
+                if (times[i] < time) {
+                    startFrame = i;
+                }
+            }
+
+            int endFrame = startFrame + 1;
+            float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);
+            applyFrame(target, startFrame, blend * weight);
+            applyFrame(target, endFrame, (1f - blend) * weight);
+        }
+        */
+    }
+
+    /**
+     * @return the length of the track
+     */
+    public float getLength() {
+        return times == null ? 0 : times[times.length - 1] - times[0];
+    }
+    
+    /**
+     * This method creates a clone of the current object.
+     * @return a clone of the current object
+     */
+    @Override
+    public PoseTrack clone() {
+        try {
+            PoseTrack result = (PoseTrack) super.clone();
+            result.times = this.times.clone();
+            if (this.frames != null) {
+                result.frames = new PoseFrame[this.frames.length];
+                for (int i = 0; i < this.frames.length; ++i) {
+                    result.frames[i] = this.frames[i].clone();
+                }
+            }
+            return result;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+    
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule out = e.getCapsule(this);
+        out.write(targetMeshIndex, "meshIndex", 0);
+        out.write(frames, "frames", null);
+        out.write(times, "times", null);
+    }
+
+    @Override
+    public void read(JmeImporter i) throws IOException {
+        InputCapsule in = i.getCapsule(this);
+        targetMeshIndex = in.readInt("meshIndex", 0);
+        frames = (PoseFrame[]) in.readSavableArray("frames", null);
+        times = in.readFloatArray("times", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/Skeleton.java b/engine/src/core/com/jme3/animation/Skeleton.java
new file mode 100644
index 0000000..bc36542
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Skeleton.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Matrix4f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <code>Skeleton</code> is a convenience class for managing a bone hierarchy.
+ * Skeleton updates the world transforms to reflect the current local
+ * animated matrixes.
+ * 
+ * @author Kirill Vainer
+ */
+public final class Skeleton implements Savable {
+
+    private Bone[] rootBones;
+    private Bone[] boneList;
+    
+    /**
+     * Contains the skinning matrices, multiplying it by a vertex effected by a bone
+     * will cause it to go to the animated position.
+     */
+    private transient Matrix4f[] skinningMatrixes;
+
+    /**
+     * Creates a skeleton from a bone list. 
+     * The root bones are found automatically.
+     * <p>
+     * Note that using this constructor will cause the bones in the list
+     * to have their bind pose recomputed based on their local transforms.
+     * 
+     * @param boneList The list of bones to manage by this Skeleton
+     */
+    public Skeleton(Bone[] boneList) {
+        this.boneList = boneList;
+
+        List<Bone> rootBoneList = new ArrayList<Bone>();
+        for (int i = boneList.length - 1; i >= 0; i--) {
+            Bone b = boneList[i];
+            if (b.getParent() == null) {
+                rootBoneList.add(b);
+            }
+        }
+        rootBones = rootBoneList.toArray(new Bone[rootBoneList.size()]);
+
+        createSkinningMatrices();
+
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            Bone rootBone = rootBones[i];
+            rootBone.update();
+            rootBone.setBindingPose();
+        }
+    }
+
+    /**
+     * Special-purpose copy constructor.
+     * <p>
+     * Shallow copies bind pose data from the source skeleton, does not
+     * copy any other data.
+     * 
+     * @param source The source Skeleton to copy from
+     */
+    public Skeleton(Skeleton source) {
+        Bone[] sourceList = source.boneList;
+        boneList = new Bone[sourceList.length];
+        for (int i = 0; i < sourceList.length; i++) {
+            boneList[i] = new Bone(sourceList[i]);
+        }
+
+        rootBones = new Bone[source.rootBones.length];
+        for (int i = 0; i < rootBones.length; i++) {
+            rootBones[i] = recreateBoneStructure(source.rootBones[i]);
+        }
+        createSkinningMatrices();
+
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            rootBones[i].update();
+        }
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Skeleton() {
+    }
+
+    private void createSkinningMatrices() {
+        skinningMatrixes = new Matrix4f[boneList.length];
+        for (int i = 0; i < skinningMatrixes.length; i++) {
+            skinningMatrixes[i] = new Matrix4f();
+        }
+    }
+
+    private Bone recreateBoneStructure(Bone sourceRoot) {
+        Bone targetRoot = getBone(sourceRoot.getName());
+        List<Bone> children = sourceRoot.getChildren();
+        for (int i = 0; i < children.size(); i++) {
+            Bone sourceChild = children.get(i);
+            // find my version of the child
+            Bone targetChild = getBone(sourceChild.getName());
+            targetRoot.addChild(targetChild);
+            recreateBoneStructure(sourceChild);
+        }
+
+        return targetRoot;
+    }
+
+    /**
+     * Updates world transforms for all bones in this skeleton.
+     * Typically called after setting local animation transforms.
+     */
+    public void updateWorldVectors() {
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            rootBones[i].update();
+        }
+    }
+
+    /**
+     * Saves the current skeleton state as it's binding pose.
+     */
+    public void setBindingPose() {
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            rootBones[i].setBindingPose();
+        }
+    }
+
+    /**
+     * Reset the skeleton to bind pose.
+     */
+    public final void reset() {
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            rootBones[i].reset();
+        }
+    }
+
+    /**
+     * Reset the skeleton to bind pose and updates the bones
+     */
+    public final void resetAndUpdate() {
+        for (int i = rootBones.length - 1; i >= 0; i--) {
+            Bone rootBone = rootBones[i];
+            rootBone.reset();
+            rootBone.update();
+        }
+    }
+
+    /**
+     * returns the array of all root bones of this skeleton
+     * @return 
+     */
+    public Bone[] getRoots() {
+        return rootBones;
+    }
+
+    /**
+     * return a bone for the given index
+     * @param index
+     * @return 
+     */
+    public Bone getBone(int index) {
+        return boneList[index];
+    }
+
+    /**
+     * returns the bone with the given name
+     * @param name
+     * @return 
+     */
+    public Bone getBone(String name) {
+        for (int i = 0; i < boneList.length; i++) {
+            if (boneList[i].getName().equals(name)) {
+                return boneList[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * returns the bone index of the given bone
+     * @param bone
+     * @return 
+     */
+    public int getBoneIndex(Bone bone) {
+        for (int i = 0; i < boneList.length; i++) {
+            if (boneList[i] == bone) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * returns the bone index of the bone that has the given name
+     * @param name
+     * @return 
+     */
+    public int getBoneIndex(String name) {
+        for (int i = 0; i < boneList.length; i++) {
+            if (boneList[i].getName().equals(name)) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Compute the skining matrices for each bone of the skeleton that would be used to transform vertices of associated meshes
+     * @return 
+     */
+    public Matrix4f[] computeSkinningMatrices() {
+        TempVars vars = TempVars.get();
+        for (int i = 0; i < boneList.length; i++) {
+            boneList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2, vars.tempMat3);
+        }
+        vars.release();
+        return skinningMatrixes;
+    }
+
+    /**
+     * returns the number of bones of this skeleton
+     * @return 
+     */
+    public int getBoneCount() {
+        return boneList.length;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Skeleton - ").append(boneList.length).append(" bones, ").append(rootBones.length).append(" roots\n");
+        for (Bone rootBone : rootBones) {
+            sb.append(rootBone.toString());
+        }
+        return sb.toString();
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule input = im.getCapsule(this);
+
+        Savable[] boneRootsAsSav = input.readSavableArray("rootBones", null);
+        rootBones = new Bone[boneRootsAsSav.length];
+        System.arraycopy(boneRootsAsSav, 0, rootBones, 0, boneRootsAsSav.length);
+
+        Savable[] boneListAsSavable = input.readSavableArray("boneList", null);
+        boneList = new Bone[boneListAsSavable.length];
+        System.arraycopy(boneListAsSavable, 0, boneList, 0, boneListAsSavable.length);
+
+        createSkinningMatrices();
+
+        for (Bone rootBone : rootBones) {
+            rootBone.update();
+            rootBone.setBindingPose();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule output = ex.getCapsule(this);
+        output.write(rootBones, "rootBones", null);
+        output.write(boneList, "boneList", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/SkeletonControl.java b/engine/src/core/com/jme3/animation/SkeletonControl.java
new file mode 100644
index 0000000..3c8e117
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SkeletonControl.java
@@ -0,0 +1,549 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+/**
+ * The Skeleton control deforms a model according to a skeleton, 
+ * It handles the computation of the deformation matrices and performs 
+ * the transformations on the mesh
+ * 
+ * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ */
+public class SkeletonControl extends AbstractControl implements Cloneable {
+
+    /**
+     * The skeleton of the model
+     */
+    private Skeleton skeleton;
+    /**
+     * List of targets which this controller effects.
+     */
+    private Mesh[] targets;
+    /**
+     * Used to track when a mesh was updated. Meshes are only updated
+     * if they are visible in at least one camera.
+     */
+    private boolean wasMeshUpdated = false;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public SkeletonControl() {
+    }
+
+    /**
+     * Creates a skeleton control.
+     * The list of targets will be acquired automatically when
+     * the control is attached to a node.
+     * 
+     * @param skeleton the skeleton
+     */
+    public SkeletonControl(Skeleton skeleton) {
+        this.skeleton = skeleton;
+    }
+
+    /**
+     * Creates a skeleton control.
+     * 
+     * @param targets the meshes controlled by the skeleton
+     * @param skeleton the skeleton
+     */
+    @Deprecated
+    SkeletonControl(Mesh[] targets, Skeleton skeleton) {
+        this.skeleton = skeleton;
+        this.targets = targets;
+    }
+
+    private boolean isMeshAnimated(Mesh mesh) {
+        return mesh.getBuffer(Type.BindPosePosition) != null;
+    }
+
+    private Mesh[] findTargets(Node node) {
+        Mesh sharedMesh = null;
+        ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
+
+        for (Spatial child : node.getChildren()) {
+            if (!(child instanceof Geometry)) {
+                continue; // could be an attachment node, ignore.
+            }
+
+            Geometry geom = (Geometry) child;
+
+            // is this geometry using a shared mesh?
+            Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
+
+            if (childSharedMesh != null) {
+                // Don't bother with non-animated shared meshes
+                if (isMeshAnimated(childSharedMesh)) {
+                    // child is using shared mesh,
+                    // so animate the shared mesh but ignore child
+                    if (sharedMesh == null) {
+                        sharedMesh = childSharedMesh;
+                    } else if (sharedMesh != childSharedMesh) {
+                        throw new IllegalStateException("Two conflicting shared meshes for " + node);
+                    }
+                }
+            } else {
+                Mesh mesh = geom.getMesh();
+                if (isMeshAnimated(mesh)) {
+                    animatedMeshes.add(mesh);
+                }
+            }
+        }
+
+        if (sharedMesh != null) {
+            animatedMeshes.add(sharedMesh);
+        }
+
+        return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        super.setSpatial(spatial);
+        if (spatial != null) {
+            Node node = (Node) spatial;
+            targets = findTargets(node);
+        } else {
+            targets = null;
+        }
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        if (!wasMeshUpdated) {
+            resetToBind(); // reset morph meshes to bind pose
+
+            Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
+
+            // if hardware skinning is supported, the matrices and weight buffer
+            // will be sent by the SkinningShaderLogic object assigned to the shader
+            for (int i = 0; i < targets.length; i++) {
+                // NOTE: This assumes that code higher up
+                // Already ensured those targets are animated
+                // otherwise a crash will happen in skin update
+                //if (isMeshAnimated(targets[i])) {
+                softwareSkinUpdate(targets[i], offsetMatrices);
+                //}
+            }
+
+            wasMeshUpdated = true;
+        }
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        wasMeshUpdated = false;
+    }
+
+    void resetToBind() {
+        for (Mesh mesh : targets) {
+            if (isMeshAnimated(mesh)) {
+                VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
+                ByteBuffer bib = (ByteBuffer) bi.getData();
+                if (!bib.hasArray()) {
+                    mesh.prepareForAnim(true); // prepare for software animation
+                }
+                VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
+                VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
+                VertexBuffer pos = mesh.getBuffer(Type.Position);
+                VertexBuffer norm = mesh.getBuffer(Type.Normal);
+                FloatBuffer pb = (FloatBuffer) pos.getData();
+                FloatBuffer nb = (FloatBuffer) norm.getData();
+                FloatBuffer bpb = (FloatBuffer) bindPos.getData();
+                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+                pb.clear();
+                nb.clear();
+                bpb.clear();
+                bnb.clear();
+
+                //reseting bind tangents if there is a bind tangent buffer
+                VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
+                if (bindTangents != null) {
+                    VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+                    FloatBuffer tb = (FloatBuffer) tangents.getData();
+                    FloatBuffer btb = (FloatBuffer) bindTangents.getData();
+                    tb.clear();
+                    btb.clear();
+                    tb.put(btb).clear();
+                }
+
+
+                pb.put(bpb).clear();
+                nb.put(bnb).clear();
+            }
+        }
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        Node clonedNode = (Node) spatial;
+        AnimControl ctrl = spatial.getControl(AnimControl.class);
+        SkeletonControl clone = new SkeletonControl();
+        clone.setSpatial(clonedNode);
+
+        clone.skeleton = ctrl.getSkeleton();
+        // Fix animated targets for the cloned node
+        clone.targets = findTargets(clonedNode);
+
+        // Fix attachments for the cloned node
+        for (int i = 0; i < clonedNode.getQuantity(); i++) {
+            // go through attachment nodes, apply them to correct bone
+            Spatial child = clonedNode.getChild(i);
+            if (child instanceof Node) {
+                Node clonedAttachNode = (Node) child;
+                Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
+
+                if (originalBone != null) {
+                    Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
+
+                    clonedAttachNode.setUserData("AttachedBone", clonedBone);
+                    clonedBone.setAttachmentsNode(clonedAttachNode);
+                }
+            }
+        }
+
+        return clone;
+    }
+
+    /**
+     * 
+     * @param boneName the name of the bone
+     * @return the node attached to this bone    
+     */
+    public Node getAttachmentsNode(String boneName) {
+        Bone b = skeleton.getBone(boneName);
+        if (b == null) {
+            throw new IllegalArgumentException("Given bone name does not exist "
+                    + "in the skeleton.");
+        }
+
+        Node n = b.getAttachmentsNode();
+        Node model = (Node) spatial;
+        model.attachChild(n);
+        return n;
+    }
+
+    /**
+     * returns the skeleton of this control
+     * @return 
+     */
+    public Skeleton getSkeleton() {
+        return skeleton;
+    }
+
+    /**
+     * sets the skeleton for this control
+     * @param skeleton 
+     */
+//    public void setSkeleton(Skeleton skeleton) {
+//        this.skeleton = skeleton;
+//    }
+    /**
+     * returns the targets meshes of this control
+     * @return 
+     */
+    public Mesh[] getTargets() {
+        return targets;
+    }
+
+    /**
+     * sets the target  meshes of this control
+     * @param targets 
+     */
+//    public void setTargets(Mesh[] targets) {
+//        this.targets = targets;
+//    }
+    /**
+     * Update the mesh according to the given transformation matrices
+     * @param mesh then mesh
+     * @param offsetMatrices the transformation matrices to apply 
+     */
+    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+
+        VertexBuffer tb = mesh.getBuffer(Type.Tangent);
+        if (tb == null) {
+            //if there are no tangents use the classic skinning
+            applySkinning(mesh, offsetMatrices);
+        } else {
+            //if there are tangents use the skinning with tangents
+            applySkinningTangents(mesh, offsetMatrices, tb);
+        }
+
+
+    }
+
+    /**
+     * Method to apply skinning transforms to a mesh's buffers    
+     * @param mesh the mesh
+     * @param offsetMatrices the offset matices to apply
+     */
+    private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
+        int maxWeightsPerVert = mesh.getMaxNumWeights();
+        if (maxWeightsPerVert <= 0) {
+            throw new IllegalStateException("Max weights per vert is incorrectly set!");
+        }
+
+        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+        // NOTE: This code assumes the vertex buffer is in bind pose
+        // resetToBind() has been called this frame
+        VertexBuffer vb = mesh.getBuffer(Type.Position);
+        FloatBuffer fvb = (FloatBuffer) vb.getData();
+        fvb.rewind();
+
+        VertexBuffer nb = mesh.getBuffer(Type.Normal);
+        FloatBuffer fnb = (FloatBuffer) nb.getData();
+        fnb.rewind();
+
+        // get boneIndexes and weights for mesh
+        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        ib.rewind();
+        wb.rewind();
+
+        float[] weights = wb.array();
+        byte[] indices = ib.array();
+        int idxWeights = 0;
+
+        TempVars vars = TempVars.get();
+
+
+        float[] posBuf = vars.skinPositions;
+        float[] normBuf = vars.skinNormals;
+
+        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+        int bufLength = posBuf.length;
+        for (int i = iterations - 1; i >= 0; i--) {
+            // read next set of positions and normals from native buffer
+            bufLength = Math.min(posBuf.length, fvb.remaining());
+            fvb.get(posBuf, 0, bufLength);
+            fnb.get(normBuf, 0, bufLength);
+            int verts = bufLength / 3;
+            int idxPositions = 0;
+
+            // iterate vertices and apply skinning transform for each effecting bone
+            for (int vert = verts - 1; vert >= 0; vert--) {
+                float nmx = normBuf[idxPositions];
+                float vtx = posBuf[idxPositions++];
+                float nmy = normBuf[idxPositions];
+                float vty = posBuf[idxPositions++];
+                float nmz = normBuf[idxPositions];
+                float vtz = posBuf[idxPositions++];
+
+                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+
+                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+                    float weight = weights[idxWeights];
+                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+                }
+
+                idxWeights += fourMinusMaxWeights;
+
+                idxPositions -= 3;
+                normBuf[idxPositions] = rnx;
+                posBuf[idxPositions++] = rx;
+                normBuf[idxPositions] = rny;
+                posBuf[idxPositions++] = ry;
+                normBuf[idxPositions] = rnz;
+                posBuf[idxPositions++] = rz;
+            }
+
+            fvb.position(fvb.position() - bufLength);
+            fvb.put(posBuf, 0, bufLength);
+            fnb.position(fnb.position() - bufLength);
+            fnb.put(normBuf, 0, bufLength);
+        }
+
+        vars.release();
+
+        vb.updateData(fvb);
+        nb.updateData(fnb);
+
+    }
+
+    /**
+     * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with
+     * null checks that would slow down the process even if tangents don't have to be computed.
+     * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm
+     * @param maxWeightsPerVert maximum number of weights per vertex
+     * @param mesh the mesh
+     * @param offsetMatrices the offsetMaytrices to apply
+     * @param tb the tangent vertexBuffer
+     */
+    private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
+        int maxWeightsPerVert = mesh.getMaxNumWeights();
+
+        if (maxWeightsPerVert <= 0) {
+            throw new IllegalStateException("Max weights per vert is incorrectly set!");
+        }
+
+        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+        // NOTE: This code assumes the vertex buffer is in bind pose
+        // resetToBind() has been called this frame
+        VertexBuffer vb = mesh.getBuffer(Type.Position);
+        FloatBuffer fvb = (FloatBuffer) vb.getData();
+        fvb.rewind();
+
+        VertexBuffer nb = mesh.getBuffer(Type.Normal);
+
+        FloatBuffer fnb = (FloatBuffer) nb.getData();
+        fnb.rewind();
+
+
+        FloatBuffer ftb = (FloatBuffer) tb.getData();
+        ftb.rewind();
+
+   
+        // get boneIndexes and weights for mesh
+        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        ib.rewind();
+        wb.rewind();
+
+        float[] weights = wb.array();
+        byte[] indices = ib.array();
+        int idxWeights = 0;
+
+        TempVars vars = TempVars.get();
+
+
+        float[] posBuf = vars.skinPositions;
+        float[] normBuf = vars.skinNormals;
+        float[] tanBuf = vars.skinTangents;
+
+        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+        int bufLength = 0;       
+        int tanLength = 0;
+        for (int i = iterations - 1; i >= 0; i--) {
+            // read next set of positions and normals from native buffer
+            bufLength = Math.min(posBuf.length, fvb.remaining());
+            tanLength = Math.min(tanBuf.length, ftb.remaining());
+            fvb.get(posBuf, 0, bufLength);
+            fnb.get(normBuf, 0, bufLength);
+            ftb.get(tanBuf, 0, tanLength);
+            int verts = bufLength / 3;
+            int idxPositions = 0;
+            //tangents has their own index because of the 4 components
+            int idxTangents = 0;
+
+            // iterate vertices and apply skinning transform for each effecting bone
+            for (int vert = verts - 1; vert >= 0; vert--) {
+                float nmx = normBuf[idxPositions];
+                float vtx = posBuf[idxPositions++];
+                float nmy = normBuf[idxPositions];
+                float vty = posBuf[idxPositions++];
+                float nmz = normBuf[idxPositions];
+                float vtz = posBuf[idxPositions++];
+
+                float tnx = tanBuf[idxTangents++];
+                float tny = tanBuf[idxTangents++];
+                float tnz = tanBuf[idxTangents++];
+
+                //skipping the 4th component of the tangent since it doesn't have to be transformed
+                idxTangents++;
+
+                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
+
+                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+                    float weight = weights[idxWeights];
+                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+
+                    rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
+                    rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
+                    rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
+                }
+
+                idxWeights += fourMinusMaxWeights;
+
+                idxPositions -= 3;
+
+                normBuf[idxPositions] = rnx;
+                posBuf[idxPositions++] = rx;
+                normBuf[idxPositions] = rny;
+                posBuf[idxPositions++] = ry;
+                normBuf[idxPositions] = rnz;
+                posBuf[idxPositions++] = rz;
+
+                idxTangents -= 4;
+
+                tanBuf[idxTangents++] = rtx;
+                tanBuf[idxTangents++] = rty;
+                tanBuf[idxTangents++] = rtz;
+
+                //once again skipping the 4th component of the tangent
+                idxTangents++;
+            }
+
+            fvb.position(fvb.position() - bufLength);
+            fvb.put(posBuf, 0, bufLength);
+            fnb.position(fnb.position() - bufLength);
+            fnb.put(normBuf, 0, bufLength);
+            ftb.position(ftb.position() - tanLength);
+            ftb.put(tanBuf, 0, tanLength);
+        }
+
+        vars.release();
+
+        vb.updateData(fvb);
+        nb.updateData(fnb);
+        tb.updateData(ftb);
+
+
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(targets, "targets", null);
+        oc.write(skeleton, "skeleton", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule in = im.getCapsule(this);
+        Savable[] sav = in.readSavableArray("targets", null);
+        if (sav != null) {
+            targets = new Mesh[sav.length];
+            System.arraycopy(sav, 0, targets, 0, sav.length);
+        }
+        skeleton = (Skeleton) in.readSavable("skeleton", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/SpatialAnimation.java b/engine/src/core/com/jme3/animation/SpatialAnimation.java
new file mode 100644
index 0000000..54f5058
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SpatialAnimation.java
@@ -0,0 +1,11 @@
+package com.jme3.animation;

+

+/**

+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)

+ */

+@Deprecated

+public class SpatialAnimation extends Animation {

+    public SpatialAnimation(String name, float length) {

+        super(name, length);

+    }

+}

diff --git a/engine/src/core/com/jme3/animation/SpatialTrack.java b/engine/src/core/com/jme3/animation/SpatialTrack.java
new file mode 100644
index 0000000..5704317
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SpatialTrack.java
@@ -0,0 +1,242 @@
+package com.jme3.animation;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.util.Arrays;

+

+/**

+ * This class represents the track for spatial animation.

+ * 

+ * @author Marcin Roguski (Kaelthas)

+ */

+public class SpatialTrack implements Track {

+    

+    /** 

+     * Translations of the track. 

+     */

+    private CompactVector3Array translations;

+    

+    /** 

+     * Rotations of the track. 

+     */

+    private CompactQuaternionArray rotations;

+    

+    /**

+     * Scales of the track. 

+     */

+    private CompactVector3Array scales;

+    

+    /** 

+     * The times of the animations frames. 

+     */

+    private float[] times;

+

+    public SpatialTrack() {

+    }

+

+    /**

+     * Creates a spatial track for the given track data.

+     * 

+     * @param times

+     *            a float array with the time of each frame

+     * @param translations

+     *            the translation of the bone for each frame

+     * @param rotations

+     *            the rotation of the bone for each frame

+     * @param scales

+     *            the scale of the bone for each frame

+     */

+    public SpatialTrack(float[] times, Vector3f[] translations,

+                        Quaternion[] rotations, Vector3f[] scales) {

+        setKeyframes(times, translations, rotations, scales);

+    }

+

+    /**

+     * 

+     * Modify the spatial which this track modifies.

+     * 

+     * @param time

+     *            the current time of the animation

+     */

+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {

+        Spatial spatial = control.getSpatial();

+        

+        Vector3f tempV = vars.vect1;

+        Vector3f tempS = vars.vect2;

+        Quaternion tempQ = vars.quat1;

+        Vector3f tempV2 = vars.vect3;

+        Vector3f tempS2 = vars.vect4;

+        Quaternion tempQ2 = vars.quat2;

+        

+        int lastFrame = times.length - 1;

+        if (time < 0 || lastFrame == 0) {

+            if (rotations != null)

+                rotations.get(0, tempQ);

+            if (translations != null)

+                translations.get(0, tempV);

+            if (scales != null) {

+                scales.get(0, tempS);

+            }

+        } else if (time >= times[lastFrame]) {

+            if (rotations != null)

+                rotations.get(lastFrame, tempQ);

+            if (translations != null)

+                translations.get(lastFrame, tempV);

+            if (scales != null) {

+                scales.get(lastFrame, tempS);

+            }

+        } else {

+            int startFrame = 0;

+            int endFrame = 1;

+            // use lastFrame so we never overflow the array

+            for (int i = 0; i < lastFrame && times[i] < time; ++i) {

+                startFrame = i;

+                endFrame = i + 1;

+            }

+

+            float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);

+

+            if (rotations != null)

+                rotations.get(startFrame, tempQ);

+            if (translations != null)

+                translations.get(startFrame, tempV);

+            if (scales != null) {

+                scales.get(startFrame, tempS);

+            }

+            if (rotations != null)

+                rotations.get(endFrame, tempQ2);

+            if (translations != null)

+                translations.get(endFrame, tempV2);

+            if (scales != null) {

+                scales.get(endFrame, tempS2);

+            }

+            tempQ.nlerp(tempQ2, blend);

+            tempV.interpolate(tempV2, blend);

+            tempS.interpolate(tempS2, blend);

+        }

+        

+        if (translations != null)

+            spatial.setLocalTranslation(tempV);

+        if (rotations != null)

+            spatial.setLocalRotation(tempQ);

+        if (scales != null) {

+            spatial.setLocalScale(tempS);

+        }

+    }

+

+    /**

+     * Set the translations, rotations and scales for this track.

+     * 

+     * @param times

+     *            a float array with the time of each frame

+     * @param translations

+     *            the translation of the bone for each frame

+     * @param rotations

+     *            the rotation of the bone for each frame

+     * @param scales

+     *            the scale of the bone for each frame

+     */

+    public void setKeyframes(float[] times, Vector3f[] translations,

+                             Quaternion[] rotations, Vector3f[] scales) {

+        if (times.length == 0) {

+            throw new RuntimeException("BoneTrack with no keyframes!");

+        }

+

+        this.times = times;

+        if (translations != null) {

+            assert times.length == translations.length;

+            this.translations = new CompactVector3Array();

+            this.translations.add(translations);

+            this.translations.freeze();

+        }

+        if (rotations != null) {

+            assert times.length == rotations.length;

+            this.rotations = new CompactQuaternionArray();

+            this.rotations.add(rotations);

+            this.rotations.freeze();

+        }

+        if (scales != null) {

+            assert times.length == scales.length;

+            this.scales = new CompactVector3Array();

+            this.scales.add(scales);

+            this.scales.freeze();

+        }

+    }

+

+    /**

+     * @return the array of rotations of this track

+     */

+    public Quaternion[] getRotations() {

+            return rotations == null ? null : rotations.toObjectArray();

+    }

+

+    /**

+     * @return the array of scales for this track

+     */

+    public Vector3f[] getScales() {

+            return scales == null ? null : scales.toObjectArray();

+    }

+

+    /**

+     * @return the arrays of time for this track

+     */

+    public float[] getTimes() {

+            return times;

+    }

+

+    /**

+     * @return the array of translations of this track

+     */

+    public Vector3f[] getTranslations() {

+            return translations == null ? null : translations.toObjectArray();

+    }

+

+    /**

+     * @return the length of the track

+     */

+    public float getLength() {

+            return times == null ? 0 : times[times.length - 1] - times[0];

+    }

+

+    /**

+     * This method creates a clone of the current object.

+     * @return a clone of the current object

+     */

+    @Override

+    public SpatialTrack clone() {

+        int tablesLength = times.length;

+

+        float[] timesCopy = this.times.clone();

+        Vector3f[] translationsCopy = this.getTranslations() == null ? null : Arrays.copyOf(this.getTranslations(), tablesLength);

+        Quaternion[] rotationsCopy = this.getRotations() == null ? null : Arrays.copyOf(this.getRotations(), tablesLength);

+        Vector3f[] scalesCopy = this.getScales() == null ? null : Arrays.copyOf(this.getScales(), tablesLength);

+

+        //need to use the constructor here because of the final fields used in this class

+        return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);

+    }

+	

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(translations, "translations", null);

+        oc.write(rotations, "rotations", null);

+        oc.write(times, "times", null);

+        oc.write(scales, "scales", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        translations = (CompactVector3Array) ic.readSavable("translations", null);

+        rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);

+        times = ic.readFloatArray("times", null);

+        scales = (CompactVector3Array) ic.readSavable("scales", null);

+    }

+}

diff --git a/engine/src/core/com/jme3/animation/Track.java b/engine/src/core/com/jme3/animation/Track.java
new file mode 100644
index 0000000..a56807d
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Track.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.Savable;
+import com.jme3.util.TempVars;
+
+public interface Track extends Savable, Cloneable {
+
+    /**
+     * Sets the time of the animation.
+     * 
+     * Internally, the track will retrieve objects from the control
+     * and modify them according to the properties of the channel and the
+     * given parameters.
+     * 
+     * @param time The time in the animation
+     * @param weight The weight from 0 to 1 on how much to apply the track 
+     * @param control The control which the track should effect
+     * @param channel The channel which the track should effect
+     */
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars);
+
+    /**
+     * @return the length of the track
+     */
+    public float getLength();
+
+    /**
+     * This method creates a clone of the current object.
+     * @return a clone of the current object
+     */
+    public Track clone();
+}
diff --git a/engine/src/core/com/jme3/animation/package.html b/engine/src/core/com/jme3/animation/package.html
new file mode 100644
index 0000000..554bab9
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/package.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.animation</code> package contains various classes
+for managing animation inside a jME3 application. Currently, the majority
+of classes are for handling skeletal animation. The primary control class is
+the {@link com.jme3.animation.AnimControl}, through which animations can be played,
+looped, combined, transitioned, etc.
+
+<h3>Usage</h3>
+
+<p>
+<code>
+// Create or load a model with skeletal animation:<br>
+Spatial model = assetManager.loadModel("...");<br>
+<br>
+// Retrieve the AnimControl.<br>
+AnimControl animCtrl = model.getControl(AnimControl.class);<br>
+<br>
+// Create an animation channel, by default assigned to all bones.<br>
+AnimChannel animChan = animCtrl.createChannel();<br>
+<br>
+// Play an animation<br>
+animChan.setAnim("MyAnim");<br>
+</code>
+<br>
+<h3>Skeletal Animation System</h3>
+<br>
+<p>
+jME3 uses a system of bone-weights: A vertex is assigned up to 4 bones by which
+it is influenced and 4 weights that describe how much the bone influences the
+vertex. The maximum weight value being 1.0, and the requirement that all 4 weights
+for a given vertex <em>must</em> sum to 1.0. This data is specified
+for each skin/mesh that is influenced by the animation control via the
+{link com.jme3.scene.VertexBuffer}s <code>BoneWeight</code> and <code>BoneIndex</code>.
+The BoneIndex buffer must be of the format <code>UnsignedByte</code>, thus
+placing the limit of up to 256 bones for a skeleton. The BoneWeight buffer
+should be of the format <code>Float</code>. Both buffers should reference 4
+bones, even if the maximum number of bones any vertex is influenced is less or more
+than 4.<br>
+If a vertex is influenced by less than 4 bones, the indices following the last
+valid bone should be 0 and the weights following the last valid bone should be 0.0.
+The buffers are designed in such a way so as to permit hardware skinning.<br>
+<p>
+The {@link com.jme3.animation.Skeleton} class describes a bone heirarchy with one
+or more root bones having children, thus containing all bones of the skeleton.
+In addition to accessing the bones in the skeleton via the tree heirarchy, it
+is also possible to access bones via index. The index for any given bone is
+arbitrary and does not depend on the bone's location in the tree hierarchy.
+It is this index that is specified in the BoneIndex VertexBuffer mentioned above
+, and is also used to apply transformations to the bones through the animations.<br>
+<p>
+Every bone has a local and model space transformation. The local space
+transformation is relative to its parent, if it has one, otherwise it is relative
+to the model. The model space transformation is relative to model space.
+The bones additionally have a bind pose transformation, which describes
+the transformations for bones when no animated pose is applied to the skeleton.
+All bones <em>must</em> have a bind pose transformation before they can be
+animated. To set the bind pose for the skeleton, set the local (relative
+to parent) transformations for all the bones using the call
+{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion) }.
+Then call {@link com.jme3.animation.Skeleton#updateWorldVectors() } followed by
+{@link com.jme3.animation.Skeleton#setBindingPose() }. <br>
+<p>
+Animations are stored in a HashMap object, accessed by name. An animation
+is simply a list of tracks, each track describes a timeline with each keyframe
+having a transformation. For bone animations, every track is assigned to a bone,
+while for morph animations, every track is assigned to a mesh.<br>
+<p>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/app/AppTask.java b/engine/src/core/com/jme3/app/AppTask.java
new file mode 100644
index 0000000..1b1b68c
--- /dev/null
+++ b/engine/src/core/com/jme3/app/AppTask.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2009-2010 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.app;
+
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AppTask</code> is used in <code>AppTaskQueue</code> to manage tasks that have
+ * yet to be accomplished. The AppTask system is used to execute tasks either
+ * in the OpenGL/Render thread, or outside of it.
+ *
+ * @author Matthew D. Hicks, lazloh
+ */
+public class AppTask<V> implements Future<V> {
+    private static final Logger logger = Logger.getLogger(AppTask.class
+            .getName());
+
+    private final Callable<V> callable;
+
+    private V result;
+    private ExecutionException exception;
+    private boolean cancelled, finished;
+    private final ReentrantLock stateLock = new ReentrantLock();
+    private final Condition finishedCondition = stateLock.newCondition();
+
+    /**
+     * Create an <code>AppTask</code> that will execute the given 
+     * {@link Callable}.
+     * 
+     * @param callable The callable to be executed
+     */
+    public AppTask(Callable<V> callable) {
+        this.callable = callable;
+    }
+
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        stateLock.lock();
+        try {
+            if (result != null) {
+                return false;
+            }
+            cancelled = true;
+
+            finishedCondition.signalAll();
+
+            return true;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    public V get() throws InterruptedException, ExecutionException {
+        stateLock.lock();
+        try {
+            while (!isDone()) {
+                finishedCondition.await();
+            }
+            if (exception != null) {
+                throw exception;
+            }
+            return result;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+        stateLock.lock();
+        try {
+            if (!isDone()) {
+                finishedCondition.await(timeout, unit);
+            }
+            if (exception != null) {
+                throw exception;
+            }
+            if (result == null) {
+                throw new TimeoutException("Object not returned in time allocated.");
+            }
+            return result;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    public boolean isCancelled() {
+        stateLock.lock();
+        try {
+            return cancelled;
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    public boolean isDone() {
+        stateLock.lock();
+        try {
+            return finished || cancelled || (exception != null);
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    public Callable<V> getCallable() {
+        return callable;
+    }
+
+    public void invoke() {
+        try {
+            final V tmpResult = callable.call();
+
+            stateLock.lock();
+            try {
+                result = tmpResult;
+                finished = true;
+
+                finishedCondition.signalAll();
+            } finally {
+                stateLock.unlock();
+            }
+        } catch (Exception e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(), "invoke()", "Exception", e);
+
+            stateLock.lock();
+            try {
+                exception = new ExecutionException(e);
+
+                finishedCondition.signalAll();
+            } finally {
+                stateLock.unlock();
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/app/Application.java b/engine/src/core/com/jme3/app/Application.java
new file mode 100644
index 0000000..517ec61
--- /dev/null
+++ b/engine/src/core/com/jme3/app/Application.java
@@ -0,0 +1,642 @@
+/*

+ * Copyright (c) 2009-2010 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.app;

+

+import com.jme3.app.state.AppStateManager;

+import com.jme3.asset.AssetManager;

+import com.jme3.audio.AudioContext;

+import com.jme3.audio.AudioRenderer;

+import com.jme3.audio.Listener;

+import com.jme3.input.*;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.Renderer;

+import com.jme3.renderer.ViewPort;

+import com.jme3.system.JmeContext.Type;

+import com.jme3.system.*;

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.util.concurrent.Callable;

+import java.util.concurrent.ConcurrentLinkedQueue;

+import java.util.concurrent.Future;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * The <code>Application</code> class represents an instance of a

+ * real-time 3D rendering jME application.

+ *

+ * An <code>Application</code> provides all the tools that are commonly used in jME3

+ * applications.

+ *

+ * jME3 applications should extend this class and call start() to begin the

+ * application.

+ * 

+ */

+public class Application implements SystemListener {

+

+    private static final Logger logger = Logger.getLogger(Application.class.getName());

+

+    protected AssetManager assetManager;

+    

+    protected AudioRenderer audioRenderer;

+    protected Renderer renderer;

+    protected RenderManager renderManager;

+    protected ViewPort viewPort;

+    protected ViewPort guiViewPort;

+

+    protected JmeContext context;

+    protected AppSettings settings;

+    protected Timer timer = new NanoTimer();

+    protected Camera cam;

+    protected Listener listener;

+

+    protected boolean inputEnabled = true;

+    protected boolean pauseOnFocus = true;

+    protected float speed = 1f;

+    protected boolean paused = false;

+    protected MouseInput mouseInput;

+    protected KeyInput keyInput;

+    protected JoyInput joyInput;

+    protected TouchInput touchInput;

+    protected InputManager inputManager;

+    protected AppStateManager stateManager;

+

+    private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();

+

+    /**

+     * Create a new instance of <code>Application</code>.

+     */

+    public Application(){

+        initStateManager();

+    }

+

+    /**

+     * Returns true if pause on lost focus is enabled, false otherwise.

+     * 

+     * @return true if pause on lost focus is enabled

+     * 

+     * @see #setPauseOnLostFocus(boolean) 

+     */

+    public boolean isPauseOnLostFocus() {

+        return pauseOnFocus;

+    }

+

+    /**

+     * Enable or disable pause on lost focus.

+     * <p>

+     * By default, pause on lost focus is enabled.

+     * If enabled, the application will stop updating 

+     * when it loses focus or becomes inactive (e.g. alt-tab). 

+     * For online or real-time applications, this might not be preferable,

+     * so this feature should be set to disabled. For other applications,

+     * it is best to keep it on so that CPU usage is not used when

+     * not necessary. 

+     * 

+     * @param pauseOnLostFocus True to enable pause on lost focus, false

+     * otherwise.

+     */

+    public void setPauseOnLostFocus(boolean pauseOnLostFocus) {

+        this.pauseOnFocus = pauseOnLostFocus;

+    }

+

+    @Deprecated

+    public void setAssetManager(AssetManager assetManager){

+        if (this.assetManager != null)

+            throw new IllegalStateException("Can only set asset manager"

+                                          + " before initialization.");

+

+        this.assetManager = assetManager;

+    }

+

+    private void initAssetManager(){

+        if (settings != null){

+            String assetCfg = settings.getString("AssetConfigURL");

+            if (assetCfg != null){

+                URL url = null;

+                try {

+                    url = new URL(assetCfg);

+                } catch (MalformedURLException ex) {

+                }

+                if (url == null) {

+                    url = Application.class.getClassLoader().getResource(assetCfg);

+                    if (url == null) {

+                        logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);

+                        return;

+                    }

+                }

+                assetManager = JmeSystem.newAssetManager(url);

+            }

+        }

+        if (assetManager == null){

+            assetManager = JmeSystem.newAssetManager(

+                    Thread.currentThread().getContextClassLoader()

+                    .getResource("com/jme3/asset/Desktop.cfg"));

+        }

+    }

+

+    /**

+     * Set the display settings to define the display created.

+     * <p>

+     * Examples of display parameters include display pixel width and height,

+     * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.

+     * If this method is called while the application is already running, then

+     * {@link #restart() } must be called to apply the settings to the display.

+     *

+     * @param settings The settings to set.

+     */

+    public void setSettings(AppSettings settings){

+        this.settings = settings;

+        if (context != null && settings.useInput() != inputEnabled){

+            // may need to create or destroy input based

+            // on settings change

+            inputEnabled = !inputEnabled;

+            if (inputEnabled){

+                initInput();

+            }else{

+                destroyInput();

+            }

+        }else{

+            inputEnabled = settings.useInput();

+        }

+    }

+

+    /**

+     * Sets the Timer implementation that will be used for calculating

+     * frame times.  By default, Application will use the Timer as returned

+     * by the current JmeContext implementation.

+     */

+    public void setTimer(Timer timer){

+        this.timer = timer;

+        

+        if (timer != null) {

+            timer.reset();

+        }

+        

+        if (renderManager != null) {

+            renderManager.setTimer(timer);

+        }

+    }

+    

+    public Timer getTimer(){

+        return timer;

+    } 

+

+    private void initDisplay(){

+        // aquire important objects

+        // from the context

+        settings = context.getSettings();

+ 

+        // Only reset the timer if a user has not already provided one       

+        if (timer == null) {

+            timer = context.getTimer();

+        }

+       

+        renderer = context.getRenderer();

+    }

+

+    private void initAudio(){

+        if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){

+            audioRenderer = JmeSystem.newAudioRenderer(settings);

+            audioRenderer.initialize();

+            AudioContext.setAudioRenderer(audioRenderer);

+

+            listener = new Listener();

+            audioRenderer.setListener(listener);

+        }

+    }

+

+    /**

+     * Creates the camera to use for rendering. Default values are perspective

+     * projection with 45° field of view, with near and far values 1 and 1000

+     * units respectively.

+     */

+    private void initCamera(){

+        cam = new Camera(settings.getWidth(), settings.getHeight());

+

+        cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);

+        cam.setLocation(new Vector3f(0f, 0f, 10f));

+        cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);

+

+        renderManager = new RenderManager(renderer);

+        //Remy - 09/14/2010 setted the timer in the renderManager

+        renderManager.setTimer(timer);

+        viewPort = renderManager.createMainView("Default", cam);

+        viewPort.setClearFlags(true, true, true);

+

+        // Create a new cam for the gui

+        Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());

+        guiViewPort = renderManager.createPostView("Gui Default", guiCam);

+        guiViewPort.setClearFlags(false, false, false);

+    }

+

+    /**

+     * Initializes mouse and keyboard input. Also

+     * initializes joystick input if joysticks are enabled in the

+     * AppSettings.

+     */

+    private void initInput(){

+        mouseInput = context.getMouseInput();

+        if (mouseInput != null)

+            mouseInput.initialize();

+

+        keyInput = context.getKeyInput();

+        if (keyInput != null)

+            keyInput.initialize();

+        

+        touchInput = context.getTouchInput();

+        if (touchInput != null)

+            touchInput.initialize();

+

+        if (!settings.getBoolean("DisableJoysticks")){

+            joyInput = context.getJoyInput();

+            if (joyInput != null)

+                joyInput.initialize();

+        }

+

+        inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);

+    }

+

+    private void initStateManager(){

+        stateManager = new AppStateManager(this);

+    }

+

+    /**

+     * @return The {@link AssetManager asset manager} for this application.

+     */

+    public AssetManager getAssetManager(){

+        return assetManager;

+    }

+

+    /**

+     * @return the {@link InputManager input manager}.

+     */

+    public InputManager getInputManager(){

+        return inputManager;

+    }

+

+    /**

+     * @return the {@link AppStateManager app state manager}

+     */

+    public AppStateManager getStateManager() {

+        return stateManager;

+    }

+

+    /**

+     * @return the {@link RenderManager render manager}

+     */

+    public RenderManager getRenderManager() {

+        return renderManager;

+    }

+

+    /**

+     * @return The {@link Renderer renderer} for the application

+     */

+    public Renderer getRenderer(){

+        return renderer;

+    }

+

+    /**

+     * @return The {@link AudioRenderer audio renderer} for the application

+     */

+    public AudioRenderer getAudioRenderer() {

+        return audioRenderer;

+    }

+

+    /**

+     * @return The {@link Listener listener} object for audio

+     */

+    public Listener getListener() {

+        return listener;

+    }

+

+    /**

+     * @return The {@link JmeContext display context} for the application

+     */

+    public JmeContext getContext(){

+        return context;

+    }

+

+    /**

+     * @return The {@link Camera camera} for the application

+     */

+    public Camera getCamera(){

+        return cam;

+    }

+

+    /**

+     * Starts the application in {@link Type#Display display} mode.

+     * 

+     * @see #start(com.jme3.system.JmeContext.Type) 

+     */

+    public void start(){

+        start(JmeContext.Type.Display);

+    }

+

+    /**

+     * Starts the application. 

+     * Creating a rendering context and executing

+     * the main loop in a separate thread.

+     */

+    public void start(JmeContext.Type contextType){

+        if (context != null && context.isCreated()){

+            logger.warning("start() called when application already created!");

+            return;

+        }

+

+        if (settings == null){

+            settings = new AppSettings(true);

+        }

+        

+        logger.log(Level.FINE, "Starting application: {0}", getClass().getName());

+        context = JmeSystem.newContext(settings, contextType);

+        context.setSystemListener(this);

+        context.create(false);

+    }

+

+    /**

+     * Initializes the application's canvas for use.

+     * <p>

+     * After calling this method, cast the {@link #getContext() context} to 

+     * {@link JmeCanvasContext},

+     * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }

+     * and attach it to an AWT/Swing Frame.

+     * The rendering thread will start when the canvas becomes visible on

+     * screen, however if you wish to start the context immediately you

+     * may call {@link #startCanvas() } to force the rendering thread

+     * to start. 

+     * 

+     * @see JmeCanvasContext

+     * @see Type#Canvas

+     */

+    public void createCanvas(){

+        if (context != null && context.isCreated()){

+            logger.warning("createCanvas() called when application already created!");

+            return;

+        }

+

+        if (settings == null){

+            settings = new AppSettings(true);

+        }

+

+        logger.log(Level.FINE, "Starting application: {0}", getClass().getName());

+        context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);

+        context.setSystemListener(this);

+    }

+

+    /**

+     * Starts the rendering thread after createCanvas() has been called.

+     * <p>

+     * Same as calling startCanvas(false)

+     * 

+     * @see #startCanvas(boolean) 

+     */

+    public void startCanvas(){

+        startCanvas(false);

+    }

+

+    /**

+     * Starts the rendering thread after createCanvas() has been called.

+     * <p>

+     * Calling this method is optional, the canvas will start automatically

+     * when it becomes visible.

+     * 

+     * @param waitFor If true, the current thread will block until the 

+     * rendering thread is running

+     */

+    public void startCanvas(boolean waitFor){

+        context.create(waitFor);

+    }

+

+    /**

+     * Internal use only. 

+     */

+    public void reshape(int w, int h){

+        renderManager.notifyReshape(w, h);

+    }

+

+    /**

+     * Restarts the context, applying any changed settings.

+     * <p>

+     * Changes to the {@link AppSettings} of this Application are not 

+     * applied immediately; calling this method forces the context

+     * to restart, applying the new settings.

+     */

+    public void restart(){

+        context.setSettings(settings);

+        context.restart();

+    }

+

+    /**

+     * Requests the context to close, shutting down the main loop

+     * and making necessary cleanup operations.

+     * 

+     * Same as calling stop(false)

+     * 

+     * @see #stop(boolean) 

+     */

+    public void stop(){

+        stop(false);

+    }

+

+    /**

+     * Requests the context to close, shutting down the main loop

+     * and making necessary cleanup operations. 

+     * After the application has stopped, it cannot be used anymore.

+     */

+    public void stop(boolean waitFor){

+        logger.log(Level.FINE, "Closing application: {0}", getClass().getName());

+        context.destroy(waitFor);

+    }

+

+    /**

+     * Do not call manually.

+     * Callback from ContextListener.

+     * <p>

+     * Initializes the <code>Application</code>, by creating a display and

+     * default camera. If display settings are not specified, a default

+     * 640x480 display is created. Default values are used for the camera;

+     * perspective projection with 45° field of view, with near

+     * and far values 1 and 1000 units respectively.

+     */

+    public void initialize(){

+        if (assetManager == null){

+            initAssetManager();

+        }

+

+        initDisplay();

+        initCamera();

+        

+        if (inputEnabled){

+            initInput();

+        }

+        initAudio();

+

+        // update timer so that the next delta is not too large

+//        timer.update();

+        timer.reset();

+

+        // user code here..

+    }

+

+    /**

+     * Internal use only.

+     */

+    public void handleError(String errMsg, Throwable t){

+        logger.log(Level.SEVERE, errMsg, t);

+        // user should add additional code to handle the error.

+        stop(); // stop the application

+    }

+

+    /**

+     * Internal use only.

+     */

+    public void gainFocus(){

+        if (pauseOnFocus) {

+            paused = false;

+            context.setAutoFlushFrames(true);

+            if (inputManager != null) {

+                inputManager.reset();

+            }

+        }

+    }

+

+    /**

+     * Internal use only.

+     */

+    public void loseFocus(){

+        if (pauseOnFocus){

+            paused = true;

+            context.setAutoFlushFrames(false);

+        }

+    }

+

+    /**

+     * Internal use only.

+     */

+    public void requestClose(boolean esc){

+        context.destroy(false);

+    }

+

+    /**

+     * Enqueues a task/callable object to execute in the jME3

+     * rendering thread. 

+     * <p>

+     * Callables are executed right at the beginning of the main loop.

+     * They are executed even if the application is currently paused

+     * or out of focus.

+     */

+    public <V> Future<V> enqueue(Callable<V> callable) {

+        AppTask<V> task = new AppTask<V>(callable);

+        taskQueue.add(task);

+        return task;

+    }

+

+    /**

+     * Do not call manually.

+     * Callback from ContextListener.

+     */

+    public void update(){

+        // Make sure the audio renderer is available to callables

+        AudioContext.setAudioRenderer(audioRenderer);

+        

+        AppTask<?> task = taskQueue.poll();

+        toploop: do {

+            if (task == null) break;

+            while (task.isCancelled()) {

+                task = taskQueue.poll();

+                if (task == null) break toploop;

+            }

+            task.invoke();

+        } while (((task = taskQueue.poll()) != null));

+    

+        if (speed == 0 || paused)

+            return;

+

+        timer.update();

+

+        if (inputEnabled){

+            inputManager.update(timer.getTimePerFrame());

+        }

+

+        if (audioRenderer != null){

+            audioRenderer.update(timer.getTimePerFrame());

+        }

+

+        // user code here..

+    }

+

+    protected void destroyInput(){

+        if (mouseInput != null)

+            mouseInput.destroy();

+

+        if (keyInput != null)

+            keyInput.destroy();

+

+        if (joyInput != null)

+            joyInput.destroy();

+        

+        if (touchInput != null)

+            touchInput.destroy();        

+

+        inputManager = null;

+    }

+

+    /**

+     * Do not call manually.

+     * Callback from ContextListener.

+     */

+    public void destroy(){

+        stateManager.cleanup();

+        

+        destroyInput();

+        if (audioRenderer != null)

+            audioRenderer.cleanup();

+        

+        timer.reset();

+    }

+

+    /**

+     * @return The GUI viewport. Which is used for the on screen

+     * statistics and FPS.

+     */

+    public ViewPort getGuiViewPort() {

+        return guiViewPort;

+    }

+

+    public ViewPort getViewPort() {

+        return viewPort;

+    }

+

+}

diff --git a/engine/src/core/com/jme3/app/DebugKeysAppState.java b/engine/src/core/com/jme3/app/DebugKeysAppState.java
new file mode 100644
index 0000000..2317c28
--- /dev/null
+++ b/engine/src/core/com/jme3/app/DebugKeysAppState.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.util.BufferUtils;
+
+
+/**
+ *  Registers a few keys that will dump debug information
+ *  to the console.
+ *
+ *  @author    Paul Speed
+ */
+public class DebugKeysAppState extends AbstractAppState {
+
+    public static final String INPUT_MAPPING_CAMERA_POS = "SIMPLEAPP_CameraPos";
+    public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory";
+    
+    private Application app;
+    private DebugKeyListener keyListener = new DebugKeyListener();
+    private InputManager inputManager;
+
+    public DebugKeysAppState() {
+    }    
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        super.initialize(stateManager, app);
+        
+        this.app = app;
+        this.inputManager = app.getInputManager();
+        
+        if (app.getInputManager() != null) {
+ 
+            inputManager.addMapping(INPUT_MAPPING_CAMERA_POS, new KeyTrigger(KeyInput.KEY_C));
+            inputManager.addMapping(INPUT_MAPPING_MEMORY, new KeyTrigger(KeyInput.KEY_M));
+            
+            inputManager.addListener(keyListener, 
+                                     INPUT_MAPPING_CAMERA_POS, 
+                                     INPUT_MAPPING_MEMORY);                   
+        }               
+    }
+            
+    @Override
+    public void cleanup() {
+        super.cleanup();
+
+        if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS))
+            inputManager.deleteMapping(INPUT_MAPPING_CAMERA_POS);
+        if (inputManager.hasMapping(INPUT_MAPPING_MEMORY))
+            inputManager.deleteMapping(INPUT_MAPPING_MEMORY);
+        
+        inputManager.removeListener(keyListener);
+    }
+
+    
+    private class DebugKeyListener implements ActionListener {
+
+        public void onAction(String name, boolean value, float tpf) {
+            if (!value) {
+                return;
+            }
+
+            if (name.equals(INPUT_MAPPING_CAMERA_POS)) {
+                Camera cam = app.getCamera();
+                if (cam != null) {
+                    Vector3f loc = cam.getLocation();
+                    Quaternion rot = cam.getRotation();
+                    System.out.println("Camera Position: ("
+                            + loc.x + ", " + loc.y + ", " + loc.z + ")");
+                    System.out.println("Camera Rotation: " + rot);
+                    System.out.println("Camera Direction: " + cam.getDirection());
+                }
+            } else if (name.equals(INPUT_MAPPING_MEMORY)) {
+                BufferUtils.printCurrentDirectMemory(null);
+            }
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/app/FlyCamAppState.java b/engine/src/core/com/jme3/app/FlyCamAppState.java
new file mode 100644
index 0000000..5a7b11e
--- /dev/null
+++ b/engine/src/core/com/jme3/app/FlyCamAppState.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.input.FlyByCamera;
+
+
+/**
+ *  Manages a FlyByCamera.  
+ *
+ *  @author    Paul Speed
+ */
+public class FlyCamAppState extends AbstractAppState {
+
+    private Application app;
+    private FlyByCamera flyCam;
+
+    public FlyCamAppState() {
+    }    
+
+    /**
+     *  This is called by SimpleApplication during initialize().
+     */
+    void setCamera( FlyByCamera cam ) {
+        this.flyCam = cam;
+    }
+    
+    public FlyByCamera getCamera() {
+        return flyCam;
+    }
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        super.initialize(stateManager, app);
+        
+        this.app = app;
+
+        if (app.getInputManager() != null) {
+        
+            if (flyCam == null) {
+                flyCam = new FlyByCamera(app.getCamera());
+            }
+            
+            flyCam.registerWithInput(app.getInputManager());            
+        }               
+    }
+            
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        
+        flyCam.setEnabled(enabled);
+    }
+    
+    @Override
+    public void cleanup() {
+        super.cleanup();
+
+        flyCam.unregisterInput();        
+    }
+
+
+}
diff --git a/engine/src/core/com/jme3/app/SimpleApplication.java b/engine/src/core/com/jme3/app/SimpleApplication.java
new file mode 100644
index 0000000..c79ce83
--- /dev/null
+++ b/engine/src/core/com/jme3/app/SimpleApplication.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2009-2010 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.app;
+
+import com.jme3.app.state.AppState;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.input.FlyByCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.JmeSystem;
+import com.jme3.util.BufferUtils;
+
+/**
+ * <code>SimpleApplication</code> extends the {@link com.jme3.app.Application}
+ * class to provide default functionality like a first-person camera,
+ * and an accessible root node that is updated and rendered regularly.
+ * Additionally, <code>SimpleApplication</code> will display a statistics view
+ * using the {@link com.jme3.app.StatsView} class. It will display
+ * the current frames-per-second value on-screen in addition to the statistics.
+ * Several keys have special functionality in <code>SimpleApplication</code>:<br/>
+ *
+ * <table>
+ * <tr><td>Esc</td><td>- Close the application</td></tr>
+ * <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr>
+ * <tr><td>M</td><td>- Display memory usage in the console.</td></tr>
+ * </table>
+ */
+public abstract class SimpleApplication extends Application {
+
+    public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
+    public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
+    public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
+    public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
+                                                                         
+    protected Node rootNode = new Node("Root Node");
+    protected Node guiNode = new Node("Gui Node");
+    protected BitmapText fpsText;
+    protected BitmapFont guiFont;
+    protected FlyByCamera flyCam;
+    protected boolean showSettings = true;
+    private AppActionListener actionListener = new AppActionListener();
+    
+    private class AppActionListener implements ActionListener {
+
+        public void onAction(String name, boolean value, float tpf) {
+            if (!value) {
+                return;
+            }
+
+            if (name.equals(INPUT_MAPPING_EXIT)) {
+                stop();
+            }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){
+                if (stateManager.getState(StatsAppState.class) != null) {
+                    stateManager.getState(StatsAppState.class).toggleStats();
+                }
+            }
+        }
+    }
+
+    public SimpleApplication() {
+        this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
+    }
+
+    public SimpleApplication( AppState... initialStates ) {
+        super();
+        
+        if (initialStates != null) {
+            for (AppState a : initialStates) {
+                if (a != null) {
+                    stateManager.attach(a);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void start() {
+        // set some default settings in-case
+        // settings dialog is not shown
+        boolean loadSettings = false;
+        if (settings == null) {
+            setSettings(new AppSettings(true));
+            loadSettings = true;
+        }
+
+        // show settings dialog
+        if (showSettings) {
+            if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
+                return;
+            }
+        }
+        //re-setting settings they can have been merged from the registry.
+        setSettings(settings);
+        super.start();
+    }
+
+    /**
+     * Retrieves flyCam
+     * @return flyCam Camera object
+     *
+     */
+    public FlyByCamera getFlyByCamera() {
+        return flyCam;
+    }
+
+    /**
+     * Retrieves guiNode
+     * @return guiNode Node object
+     *
+     */
+    public Node getGuiNode() {
+        return guiNode;
+    }
+
+    /**
+     * Retrieves rootNode
+     * @return rootNode Node object
+     *
+     */
+    public Node getRootNode() {
+        return rootNode;
+    }
+
+    public boolean isShowSettings() {
+        return showSettings;
+    }
+
+    /**
+     * Toggles settings window to display at start-up
+     * @param showSettings Sets true/false
+     *
+     */
+    public void setShowSettings(boolean showSettings) {
+        this.showSettings = showSettings;
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        // Several things rely on having this
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+
+        guiNode.setQueueBucket(Bucket.Gui);
+        guiNode.setCullHint(CullHint.Never);
+        viewPort.attachScene(rootNode);
+        guiViewPort.attachScene(guiNode);
+
+        if (inputManager != null) {
+        
+            // We have to special-case the FlyCamAppState because too
+            // many SimpleApplication subclasses expect it to exist in
+            // simpleInit().  But at least it only gets initialized if
+            // the app state is added.
+            if (stateManager.getState(FlyCamAppState.class) != null) {
+                flyCam = new FlyByCamera(cam);
+                flyCam.setMoveSpeed(1f); // odd to set this here but it did it before
+                stateManager.getState(FlyCamAppState.class).setCamera( flyCam ); 
+            }
+
+            if (context.getType() == Type.Display) {
+                inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
+            }
+
+            if (stateManager.getState(StatsAppState.class) != null) {
+                inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
+                inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);            
+            }
+            
+            inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);            
+        }
+
+        if (stateManager.getState(StatsAppState.class) != null) {
+            // Some of the tests rely on having access to fpsText
+            // for quick display.  Maybe a different way would be better.
+            stateManager.getState(StatsAppState.class).setFont(guiFont);
+            fpsText = stateManager.getState(StatsAppState.class).getFpsText();
+        }
+
+        // call user code
+        simpleInitApp();
+    }
+
+    @Override
+    public void update() {
+        super.update(); // makes sure to execute AppTasks
+        if (speed == 0 || paused) {
+            return;
+        }
+
+        float tpf = timer.getTimePerFrame() * speed;
+
+        // update states
+        stateManager.update(tpf);
+
+        // simple update and root node
+        simpleUpdate(tpf);
+ 
+        rootNode.updateLogicalState(tpf);
+        guiNode.updateLogicalState(tpf);
+        
+        rootNode.updateGeometricState();
+        guiNode.updateGeometricState();
+
+        // Moving this here to make sure it is always done.
+        // Now the sets are cleared every frame (guaranteed)
+        // and more than one viewer can access the data.  This
+        // used to be cleared by StatsView but then only StatsView
+        // could get accurate counts.
+        renderer.getStatistics().clearFrame();        
+                
+        // render states
+        stateManager.render(renderManager);
+        renderManager.render(tpf, context.isRenderable());
+        simpleRender(renderManager);
+        stateManager.postRender();        
+    }
+
+    public void setDisplayFps(boolean show) {
+        if (stateManager.getState(StatsAppState.class) != null) {
+            stateManager.getState(StatsAppState.class).setDisplayFps(show);
+        }
+    }
+
+    public void setDisplayStatView(boolean show) {
+        if (stateManager.getState(StatsAppState.class) != null) {
+            stateManager.getState(StatsAppState.class).setDisplayStatView(show);
+        }
+    }
+
+    public abstract void simpleInitApp();
+
+    public void simpleUpdate(float tpf) {
+    }
+
+    public void simpleRender(RenderManager rm) {
+    }
+}
diff --git a/engine/src/core/com/jme3/app/StatsAppState.java b/engine/src/core/com/jme3/app/StatsAppState.java
new file mode 100644
index 0000000..d4c968b
--- /dev/null
+++ b/engine/src/core/com/jme3/app/StatsAppState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+
+
+/**
+ *  Displays stats in SimpleApplication's GUI node or
+ *  using the node and font parameters provided.  
+ *
+ *  @author    Paul Speed
+ */
+public class StatsAppState extends AbstractAppState {
+
+    private Application app;
+    protected StatsView statsView;
+    protected boolean showSettings = true;
+    private  boolean showFps = true;
+    private  boolean showStats = true;
+    
+    protected Node guiNode;
+    protected float secondCounter = 0.0f;
+    protected int frameCounter = 0;
+    protected BitmapText fpsText;
+    protected BitmapFont guiFont;
+
+    public StatsAppState() {
+    }    
+
+    public StatsAppState( Node guiNode, BitmapFont guiFont ) {
+        this.guiNode = guiNode;
+        this.guiFont = guiFont;
+    }
+
+    /**
+     *  Called by SimpleApplication to provide an early font
+     *  so that the fpsText can be created before init.  This
+     *  is because several applications expect to directly access
+     *  fpsText... unfortunately.
+     */
+    void setFont( BitmapFont guiFont ) {
+        this.guiFont = guiFont;
+        this.fpsText = new BitmapText(guiFont, false);
+    }
+
+    public BitmapText getFpsText() {
+        return fpsText;
+    }
+    
+    public StatsView getStatsView() {
+        return statsView;
+    }
+
+    public float getSecondCounter() {
+        return secondCounter;
+    }
+
+    public void toggleStats() {
+        setDisplayFps( !showFps );
+        setDisplayStatView( !showStats );
+    }
+
+    public void setDisplayFps(boolean show) {
+        showFps = show;
+        if (fpsText != null) {
+            fpsText.setCullHint(show ? CullHint.Never : CullHint.Always);
+        }
+    }
+
+    public void setDisplayStatView(boolean show) {
+        showStats = show;
+        if (statsView != null ) {
+            statsView.setEnabled(show);
+            statsView.setCullHint(show ? CullHint.Never : CullHint.Always);
+        }
+    }
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        super.initialize(stateManager, app);
+        this.app = app;
+               
+        if (app instanceof SimpleApplication) {
+            SimpleApplication simpleApp = (SimpleApplication)app;
+            if (guiNode == null)
+                guiNode = simpleApp.guiNode;
+            if (guiFont == null )
+                guiFont = simpleApp.guiFont;
+        } 
+        
+        if (guiNode == null) {
+            throw new RuntimeException( "No guiNode specific and cannot be automatically determined." );
+        } 
+        
+        if (guiFont == null) {
+            guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
+        }
+        
+        loadFpsText();  
+        loadStatsView();      
+    }
+            
+    /**
+     * Attaches FPS statistics to guiNode and displays it on the screen.
+     *
+     */
+    public void loadFpsText() {
+        if (fpsText == null) {
+            fpsText = new BitmapText(guiFont, false);
+        }
+        
+        fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
+        fpsText.setText("Frames per second");
+        fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
+        guiNode.attachChild(fpsText);
+    }
+
+    /**
+     * Attaches Statistics View to guiNode and displays it on the screen
+     * above FPS statistics line.
+     *
+     */
+    public void loadStatsView() {
+        statsView = new StatsView("Statistics View", 
+                                  app.getAssetManager(), 
+                                  app.getRenderer().getStatistics());
+        // move it up so it appears above fps text
+        statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
+        statsView.setEnabled(showStats);
+        statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);        
+        guiNode.attachChild(statsView);
+    }
+        
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        
+        if (enabled) {
+            fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
+            statsView.setEnabled(showStats);
+            statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);        
+        } else {
+            fpsText.setCullHint(CullHint.Always);
+            statsView.setEnabled(false);
+            statsView.setCullHint(CullHint.Always);        
+        }
+    }
+    
+    @Override
+    public void update(float tpf) {
+        if (showFps) {
+            secondCounter += app.getTimer().getTimePerFrame();
+            frameCounter ++;
+            if (secondCounter >= 1.0f) {
+                int fps = (int) (frameCounter / secondCounter);
+                fpsText.setText("Frames per second: " + fps);
+                secondCounter = 0.0f;
+                frameCounter = 0;
+            }          
+        }
+    }
+
+    @Override
+    public void cleanup() {
+        super.cleanup();
+        
+        guiNode.detachChild(statsView);
+        guiNode.detachChild(fpsText);
+    }
+
+
+}
diff --git a/engine/src/core/com/jme3/app/StatsView.java b/engine/src/core/com/jme3/app/StatsView.java
new file mode 100644
index 0000000..49eeb13
--- /dev/null
+++ b/engine/src/core/com/jme3/app/StatsView.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2009-2010 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.app;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Statistics;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+
+/**
+ * The <code>StatsView</code> provides a heads-up display (HUD) of various
+ * statistics of rendering. The data is retrieved every frame from a
+ * {@link com.jme3.renderer.Statistics} and then displayed on screen.<br/>
+ * <br/>
+ * Usage:<br/>
+ * To use the stats view, you need to retrieve the
+ * {@link com.jme3.renderer.Statistics} from the
+ * {@link com.jme3.renderer.Renderer} used by the application. Then, attach
+ * the <code>StatsView</code> to the scene graph.<br/>
+ * <code><br/>
+ * Statistics stats = renderer.getStatistics();<br/>
+ * StatsView statsView = new StatsView("MyStats", assetManager, stats);<br/>
+ * rootNode.attachChild(statsView);<br/>
+ * </code>
+ */
+public class StatsView extends Node implements Control {
+
+    private BitmapText[] labels;
+    private Statistics statistics;
+
+    private String[] statLabels;
+    private int[] statData;
+
+    private boolean enabled = true;
+    
+    private final StringBuilder stringBuilder = new StringBuilder();
+
+    public StatsView(String name, AssetManager manager, Statistics stats){
+        super(name);
+
+        setQueueBucket(Bucket.Gui);
+        setCullHint(CullHint.Never);
+
+        statistics = stats;
+
+        statLabels = statistics.getLabels();
+        statData = new int[statLabels.length];
+        labels = new BitmapText[statLabels.length];
+
+        BitmapFont font = manager.loadFont("Interface/Fonts/Console.fnt");
+        for (int i = 0; i < labels.length; i++){
+            labels[i] = new BitmapText(font);
+            labels[i].setLocalTranslation(0, labels[i].getLineHeight() * (i+1), 0);
+            attachChild(labels[i]);
+        }
+
+        addControl(this);
+    }
+
+    public void update(float tpf) {
+    
+        if (!isEnabled()) 
+            return;
+            
+        statistics.getData(statData);
+        for (int i = 0; i < labels.length; i++) {
+            stringBuilder.setLength(0);
+            stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]);
+            labels[i].setText(stringBuilder);
+        }
+        
+        // Moved to SimpleApplication to make sure it is
+        // done even if there is no StatsView or the StatsView
+        // is disable.
+        //statistics.clearFrame();
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        return (Control) spatial;
+    }
+
+    public void setSpatial(Spatial spatial) {
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+    }
+
+}
diff --git a/engine/src/core/com/jme3/app/package.html b/engine/src/core/com/jme3/app/package.html
new file mode 100644
index 0000000..ec6bb9a
--- /dev/null
+++ b/engine/src/core/com/jme3/app/package.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.application</code> provides a toolset for jME3 applications
+to interact with various components of the engine. Typically, the
+{@link com.jme3.app.Application} class will be extended and the update() method
+implemented to provide functionality for the main loop. <br>
+<p>
+An <code>Application</code> will typically provide the following services:
+<ul>
+    <li>{@link com.jme3.asset.AssetManager} - A system for finding and loading
+    data assets included with the application, such as models and textures.</li>
+    <li>{@link com.jme3.renderer.RenderManager} - A high-level rendering
+        interface for 3D graphics, manages viewports and scenes assigned
+        to the viewports, as well as general high-level rendering.</li>
+    <li>{@link com.jme3.input.InputManager} - An interface for handling input
+        from devices such as keyboard, mouse, and gamepad/joystick.</li>
+    <li>{@link com.jme3.app.state.AppStateManager} - Manager for
+        {@link com.jme3.app.state.AppState}s, which are specific application
+        functionality to be executed inside the main loop.</li>
+    <li>{@link com.jme3.audio.AudioRenderer} - Allows playing sound effects and 
+        music.</li>
+    <li>{@link com.jme3.system.Timer} - The timer keeps track of time and allows
+        computing the time since the last frame (TPF) that is neccessary
+        for framerate-independent updates and motion.</li>
+    <li>{@link com.jme3.system.AppSettings} - A database containing various
+        settings for the application. These settings may be set by the user
+        or the application itself.</li>
+</ul>
+
+
+<h3>Usage</h3>
+
+An example use of the Application class is as follows<br>
+<br>
+
+<code>
+public class ExampleUse extends Application {<br>
+<br>
+    private Node rootNode = new Node("Root Node");<br>
+<br>
+    public static void main(String[] args){<br>
+        ExampleUse app = new ExampleUse();<br>
+        app.start();<br>
+    }<br>
+<br>
+    @Override<br>
+    public void initialize(){<br>
+        super.initialize();<br>
+<br>
+        // attach root node to viewport<br>
+        viewPort.attachScene(rootNode);<br>
+    }<br>
+<br>
+    @Override<br>
+    public void update(){<br>
+        super.update();<br>
+<br>
+        float tpf = timer.getTimePerFrame();<br>
+<br>
+        // update rootNode<br>
+        rootNode.updateLogicalState(tpf);<br>
+        rootNode.updateGeometricState();<br>
+<br>
+        // render the viewports<br>
+        renderManager.render(tpf);<br>
+    }<br>
+}<br>
+<br>
+</code>
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/app/state/AbstractAppState.java b/engine/src/core/com/jme3/app/state/AbstractAppState.java
new file mode 100644
index 0000000..1ea1230
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AbstractAppState.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+
+/**
+ * <code>AbstractAppState</code> implements some common methods
+ * that make creation of AppStates easier.
+ * @author Kirill Vainer
+ */
+public class AbstractAppState implements AppState {
+
+    /**
+     * <code>initialized</code> is set to true when the method
+     * {@link AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) }
+     * is called. When {@link AbstractAppState#cleanup() } is called, <code>initialized</code>
+     * is set back to false.
+     */
+    protected boolean initialized = false;
+    private boolean enabled = true;
+
+    public void initialize(AppStateManager stateManager, Application app) {
+        initialized = true;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+    
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void stateAttached(AppStateManager stateManager) {
+    }
+
+    public void stateDetached(AppStateManager stateManager) {
+    }
+
+    public void update(float tpf) {
+    }
+
+    public void render(RenderManager rm) {
+    }
+
+    public void postRender(){
+    }
+
+    public void cleanup() {
+        initialized = false;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/app/state/AppState.java b/engine/src/core/com/jme3/app/state/AppState.java
new file mode 100644
index 0000000..d94a35f
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AppState.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009-2010 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.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+
+/**
+ * AppState represents a continously executing code inside the main loop.
+ * An <code>AppState</code> can track when it is attached to the 
+ * {@link AppStateManager} or when it is detached. <br/><code>AppState</code>s
+ * are initialized in the render thread, upon a call to {@link AppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) }
+ * and are de-initialized upon a call to {@link AppState#cleanup()}. 
+ * Implementations should return the correct value with a call to 
+ * {@link AppState#isInitialized() } as specified above.<br/>
+ * 
+ *
+ * @author Kirill Vainer
+ */
+public interface AppState {
+
+    /**
+     * Called to initialize the AppState.
+     *
+     * @param stateManager The state manager
+     * @param app
+     */
+    public void initialize(AppStateManager stateManager, Application app);
+
+    /**
+     * @return True if <code>initialize()</code> was called on the state,
+     * false otherwise.
+     */
+    public boolean isInitialized();
+
+    /**
+     * Enable or disable the functionality of the <code>AppState</code>.
+     * The effect of this call depends on implementation. An 
+     * <code>AppState</code> starts as being enabled by default.
+     * 
+     * @param active activate the AppState or not.
+     */
+    public void setEnabled(boolean active);
+    
+    /**
+     * @return True if the <code>AppState</code> is enabled, false otherwise.
+     * 
+     * @see AppState#setEnabled(boolean)
+     */
+    public boolean isEnabled();
+    /**
+     * Called when the state was attached.
+     *
+     * @param stateManager State manager to which the state was attached to.
+     */
+    public void stateAttached(AppStateManager stateManager);
+
+   /**
+    * Called when the state was detached.
+    *
+    * @param stateManager The state manager from which the state was detached from.
+    */
+    public void stateDetached(AppStateManager stateManager);
+
+    /**
+     * Called to update the state.
+     *
+     * @param tpf Time per frame.
+     */
+    public void update(float tpf);
+
+    /**
+     * Render the state.
+     *
+     * @param rm RenderManager
+     */
+    public void render(RenderManager rm);
+
+    /**
+     * Called after all rendering commands are flushed.
+     */
+    public void postRender();
+
+    /**
+     * Cleanup the game state. 
+     */
+    public void cleanup();
+
+}
diff --git a/engine/src/core/com/jme3/app/state/AppStateManager.java b/engine/src/core/com/jme3/app/state/AppStateManager.java
new file mode 100644
index 0000000..81228af
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AppStateManager.java
@@ -0,0 +1,288 @@
+/*

+ * Copyright (c) 2009-2010 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.app.state;

+ 

+import com.jme3.app.Application;

+import com.jme3.renderer.RenderManager;

+import com.jme3.util.SafeArrayList;

+import java.util.Arrays;

+import java.util.List;

+

+/**

+ * The <code>AppStateManager</code> holds a list of {@link AppState}s which

+ * it will update and render.<br/>

+ * When an {@link AppState} is attached or detached, the

+ * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and

+ * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods

+ * will be called respectively.

+ *

+ * <p>The lifecycle for an attached AppState is as follows:</p>

+ * <ul>

+ * <li>stateAttached() : called when the state is attached on the thread on which

+ *                       the state was attached.

+ * <li>initialize() : called ONCE on the render thread at the beginning of the next

+ *                    AppStateManager.update().

+ * <li>stateDetached() : called when the state is attached on the thread on which

+ *                       the state was detached.  This is not necessarily on the

+ *                       render thread and it is not necessarily safe to modify

+ *                       the scene graph, etc..

+ * <li>cleanup() : called ONCE on the render thread at the beginning of the next update

+ *                 after the state has been detached or when the application is 

+ *                 terminating.  

+ * </ul> 

+ *

+ * @author Kirill Vainer, Paul Speed

+ */

+public class AppStateManager {

+

+    /**

+     *  List holding the attached app states that are pending

+     *  initialization.  Once initialized they will be added to

+     *  the running app states.  

+     */

+    private final SafeArrayList<AppState> initializing = new SafeArrayList<AppState>(AppState.class);

+    

+    /**

+     *  Holds the active states once they are initialized.  

+     */

+    private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);

+    

+    /**

+     *  List holding the detached app states that are pending

+     *  cleanup.  

+     */

+    private final SafeArrayList<AppState> terminating = new SafeArrayList<AppState>(AppState.class);

+ 

+    // All of the above lists need to be thread safe but access will be

+    // synchronized separately.... but always on the states list.  This

+    // is to avoid deadlocking that may occur and the most common use case

+    // is that they are all modified from the same thread anyway.

+    

+    private final Application app;

+    private AppState[] stateArray;

+

+    public AppStateManager(Application app){

+        this.app = app;

+    }

+

+    protected AppState[] getInitializing() { 

+        synchronized (states){

+            return initializing.getArray();

+        }

+    } 

+

+    protected AppState[] getTerminating() { 

+        synchronized (states){

+            return terminating.getArray();

+        }

+    } 

+

+    protected AppState[] getStates(){

+        synchronized (states){

+            return states.getArray();

+        }

+    }

+

+    /**

+     * Attach a state to the AppStateManager, the same state cannot be attached

+     * twice.

+     *

+     * @param state The state to attach

+     * @return True if the state was successfully attached, false if the state

+     * was already attached.

+     */

+    public boolean attach(AppState state){

+        synchronized (states){

+            if (!states.contains(state) && !initializing.contains(state)){

+                state.stateAttached(this);

+                initializing.add(state);

+                return true;

+            }else{

+                return false;

+            }

+        }

+    }

+

+    /**

+     * Detaches the state from the AppStateManager. 

+     *

+     * @param state The state to detach

+     * @return True if the state was detached successfully, false

+     * if the state was not attached in the first place.

+     */

+    public boolean detach(AppState state){

+        synchronized (states){

+            if (states.contains(state)){

+                state.stateDetached(this);

+                states.remove(state);

+                terminating.add(state);

+                return true;

+            } else if(initializing.contains(state)){

+                state.stateDetached(this);

+                initializing.remove(state);

+                return true;

+            }else{

+                return false;

+            }

+        }

+    }

+

+    /**

+     * Check if a state is attached or not.

+     *

+     * @param state The state to check

+     * @return True if the state is currently attached to this AppStateManager.

+     * 

+     * @see AppStateManager#attach(com.jme3.app.state.AppState)

+     */

+    public boolean hasState(AppState state){

+        synchronized (states){

+            return states.contains(state) || initializing.contains(state);

+        }

+    }

+

+    /**

+     * Returns the first state that is an instance of subclass of the specified class.

+     * @param <T>

+     * @param stateClass

+     * @return First attached state that is an instance of stateClass

+     */

+    public <T extends AppState> T getState(Class<T> stateClass){

+        synchronized (states){

+            AppState[] array = getStates();

+            for (AppState state : array) {

+                if (stateClass.isAssignableFrom(state.getClass())){

+                    return (T) state;

+                }

+            }

+            

+            // This may be more trouble than its worth but I think

+            // it's necessary for proper decoupling of states and provides

+            // similar behavior to before where a state could be looked

+            // up even if it wasn't initialized. -pspeed

+            array = getInitializing();

+            for (AppState state : array) {

+                if (stateClass.isAssignableFrom(state.getClass())){

+                    return (T) state;

+                }

+            }

+        }

+        return null;

+    }

+

+    protected void initializePending(){

+        AppState[] array = getInitializing();

+        synchronized( states ) {

+            // Move the states that will be initialized

+            // into the active array.  In all but one case the

+            // order doesn't matter but if we do this here then

+            // a state can detach itself in initialize().  If we

+            // did it after then it couldn't.

+            List<AppState> transfer = Arrays.asList(array);         

+            states.addAll(transfer);

+            initializing.removeAll(transfer);

+        }        

+        for (AppState state : array) {

+            state.initialize(this, app);

+        }

+    }

+    

+    protected void terminatePending(){

+        AppState[] array = getTerminating();

+        for (AppState state : array) {

+            state.cleanup();

+        }        

+        synchronized( states ) {

+            // Remove just the states that were terminated...

+            // which might now be a subset of the total terminating

+            // list.

+            terminating.removeAll(Arrays.asList(array));         

+        }

+    }    

+

+    /**

+     * Calls update for attached states, do not call directly.

+     * @param tpf Time per frame.

+     */

+    public void update(float tpf){

+    

+        // Cleanup any states pending

+        terminatePending();

+

+        // Initialize any states pending

+        initializePending();

+

+        // Update enabled states    

+        AppState[] array = getStates();

+        for (AppState state : array){

+            if (state.isEnabled()) {

+                state.update(tpf);

+            }

+        }

+    }

+

+    /**

+     * Calls render for all attached and initialized states, do not call directly.

+     * @param rm The RenderManager

+     */

+    public void render(RenderManager rm){

+        AppState[] array = getStates();

+        for (AppState state : array){

+            if (state.isEnabled()) {

+                state.render(rm);

+            }

+        }

+    }

+

+    /**

+     * Calls render for all attached and initialized states, do not call directly.

+     */

+    public void postRender(){

+        AppState[] array = getStates();

+        for (AppState state : array){

+            if (state.isEnabled()) {

+                state.postRender();

+            }

+        }

+    }

+

+    /**

+     * Calls cleanup on attached states, do not call directly.

+     */

+    public void cleanup(){

+        AppState[] array = getStates();

+        for (AppState state : array){

+            state.cleanup();

+        }

+    }    

+}

diff --git a/engine/src/core/com/jme3/app/state/package.html b/engine/src/core/com/jme3/app/state/package.html
new file mode 100644
index 0000000..0e93b38
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/package.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.app.state</code> package provides 
+{@link com.jme3.app.state.AppState app states},
+an abstract way of handling application logic.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/asset/Asset.java b/engine/src/core/com/jme3/asset/Asset.java
new file mode 100644
index 0000000..f36f963
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/Asset.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+/**
+ * Implementing the asset interface allows use of smart asset management.
+ * <p>
+ * Smart asset management requires cooperation from the {@link AssetKey}. 
+ * In particular, the AssetKey should return true in its 
+ * {@link AssetKey#useSmartCache() } method. Also smart assets MUST
+ * create a clone of the asset and cannot return the same reference,
+ * e.g. {@link AssetKey#createClonedInstance(java.lang.Object) createCloneInstance(someAsset)} <code>!= someAsset</code>.
+ * <p>
+ * If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method
+ * is called twice with the same asset key (equals() wise, not necessarily reference wise)
+ * then both assets will have the same asset key set (reference wise) via
+ * {@link Asset#setKey(com.jme3.asset.AssetKey) }, then this asset key
+ * is used to track all instances of that asset. Once all clones of the asset 
+ * are garbage collected, the shared asset key becomes unreachable and at that 
+ * point it is removed from the smart asset cache. 
+ */
+public interface Asset {
+    
+    /**
+     * Set by the {@link AssetManager} to track this asset. 
+     * 
+     * Only clones of the asset has this set, the original copy that
+     * was loaded has this key set to null so that only the clones are tracked
+     * for garbage collection. 
+     * 
+     * @param key The AssetKey to set
+     */
+    public void setKey(AssetKey key);
+    
+    /**
+     * Returns the asset key that is used to track this asset for garbage
+     * collection.
+     * 
+     * @return the asset key that is used to track this asset for garbage
+     * collection.
+     */
+    public AssetKey getKey();
+}
diff --git a/engine/src/core/com/jme3/asset/AssetCache.java b/engine/src/core/com/jme3/asset/AssetCache.java
new file mode 100644
index 0000000..5dde799
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetCache.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.WeakHashMap;
+
+/**
+ * An <code>AssetCache</code> allows storage of loaded resources in order
+ * to improve their access time if they are requested again in a short period
+ * of time. The AssetCache stores weak references to the resources, allowing
+ * Java's garbage collector to request deletion of rarely used resources
+ * when heap memory is low.
+ */
+public class AssetCache {
+
+    public static final class SmartAssetInfo {
+        public WeakReference<AssetKey> smartKey;
+        public Asset asset;
+    }
+
+    private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
+            = new WeakHashMap<AssetKey, SmartAssetInfo>();
+    private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();
+
+    /**
+     * Adds a resource to the cache.
+     * <br/><br/>
+     * <font color="red">Thread-safe.</font>
+     * @see #getFromCache(java.lang.String)
+     */
+    public void addToCache(AssetKey key, Object obj){
+        synchronized (regularCache){
+            if (obj instanceof Asset && key.useSmartCache()){
+                // put in smart cache
+                Asset asset = (Asset) obj;
+                asset.setKey(null); // no circular references
+                SmartAssetInfo smartInfo = new SmartAssetInfo();
+                smartInfo.asset = asset;
+                // use the original key as smart key
+                smartInfo.smartKey = new WeakReference<AssetKey>(key); 
+                smartCache.put(key, smartInfo);
+            }else{
+                // put in regular cache
+                regularCache.put(key, obj);
+            }
+        }
+    }
+
+    /**
+     * Delete an asset from the cache, returns true if it was deleted successfuly.
+     * <br/><br/>
+     * <font color="red">Thread-safe.</font>
+     */
+    public boolean deleteFromCache(AssetKey key){
+        synchronized (regularCache){
+            if (key.useSmartCache()){
+                return smartCache.remove(key) != null;
+            }else{
+                return regularCache.remove(key) != null;
+            }
+        }
+    }
+
+    /**
+     * Gets an object from the cache given an asset key.
+     * <br/><br/>
+     * <font color="red">Thread-safe.</font>
+     * @param key
+     * @return
+     */
+    public Object getFromCache(AssetKey key){
+        synchronized (regularCache){
+            if (key.useSmartCache()) {
+                return smartCache.get(key).asset;
+            } else {
+                return regularCache.get(key);
+            }
+        }
+    }
+
+    /**
+     * Retrieves smart asset info from the cache.
+     * @param key
+     * @return
+     */
+    public SmartAssetInfo getFromSmartCache(AssetKey key){
+        return smartCache.get(key);
+    }
+
+    /**
+     * Deletes all the assets in the regular cache.
+     */
+    public void deleteAllAssets(){
+        synchronized (regularCache){
+            regularCache.clear();
+            smartCache.clear();
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/asset/AssetConfig.java b/engine/src/core/com/jme3/asset/AssetConfig.java
new file mode 100644
index 0000000..eaf2036
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AssetConfig</code> loads a config file to configure the asset manager.
+ * <br/><br/>
+ * The config file is specified with the following format:
+ * <code>
+ * "LOADER" <class> : (<extension> ",")* <extension>
+ * "LOCATOR" <path> <class> : (<extension> ",")* <extension>
+ * </code>
+ *
+ * @author Kirill Vainer
+ */
+public class AssetConfig {
+
+    private AssetManager manager;
+
+    public AssetConfig(AssetManager manager){
+        this.manager = manager;
+    }
+
+    public void loadText(InputStream in) throws IOException{
+        Scanner scan = new Scanner(in);
+        while (scan.hasNext()){
+            String cmd = scan.next();
+            if (cmd.equals("LOADER")){
+                String loaderClass = scan.next();
+                String colon = scan.next();
+                if (!colon.equals(":")){
+                    throw new IOException("Expected ':', got '"+colon+"'");
+                }
+                String extensionsList = scan.nextLine();
+                String[] extensions = extensionsList.split(",");
+                for (int i = 0; i < extensions.length; i++){
+                    extensions[i] = extensions[i].trim();
+                }
+                if (hasClass(loaderClass)) {
+                    manager.registerLoader(loaderClass, extensions);
+                } else {
+                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find loader {0}", loaderClass);
+                }
+            } else if (cmd.equals("LOCATOR")) {
+                String rootPath = scan.next();
+                String locatorClass = scan.nextLine().trim();
+                if (hasClass(locatorClass)) {
+                    manager.registerLocator(rootPath, locatorClass);
+                } else {
+                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find locator {0}", locatorClass);
+                }
+            } else {
+                throw new IOException("Expected command, got '" + cmd + "'");
+            }
+        }
+    }
+    
+    private boolean hasClass(String name) {
+        try {
+            Class clazz = Class.forName(name);
+            return clazz != null;
+        } catch (ClassNotFoundException ex) {
+            return false;
+        }
+    }
+
+    private static String readString(DataInput dataIn) throws IOException{
+        int length = dataIn.readUnsignedShort();
+        char[] chrs = new char[length];
+        for (int i = 0; i < length; i++){
+            chrs[i] = (char) dataIn.readUnsignedByte();
+        }
+        return String.valueOf(chrs);
+    }
+
+    /*
+    public void loadBinary(DataInput dataIn) throws IOException{
+        // read signature and version
+
+        // how many locator entries?
+        int locatorEntries = dataIn.readUnsignedShort();
+        for (int i = 0; i < locatorEntries; i++){
+            String locatorClazz = readString(dataIn);
+            String rootPath = readString(dataIn);
+            manager.registerLocator(rootPath, locatorClazz);
+        }
+
+        int loaderEntries = dataIn.readUnsignedShort();
+        for (int i = 0; i < loaderEntries; i++){
+            String loaderClazz = readString(dataIn);
+            int numExtensions = dataIn.readUnsignedByte();
+            String[] extensions = new String[numExtensions];
+            for (int j = 0; j < numExtensions; j++){
+                extensions[j] = readString(dataIn);
+            }
+
+            manager.registerLoader(loaderClazz, extensions);
+        }
+    }
+    */
+}
diff --git a/engine/src/core/com/jme3/asset/AssetEventListener.java b/engine/src/core/com/jme3/asset/AssetEventListener.java
new file mode 100644
index 0000000..6907bcf
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetEventListener.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+/**
+ * <code>AssetEventListener</code> is an interface for listening to various
+ * events happening inside {@link AssetManager}. For now, it is possible
+ * to receive an event when an asset has been requested
+ * (one of the AssetManager.load***() methods were called), or when
+ * an asset has been loaded.
+ * 
+ * @author Kirill Vainer
+ */
+public interface AssetEventListener {
+
+    /**
+     * Called when an asset has been successfully loaded (e.g: loaded from
+     * file system and parsed).
+     *
+     * @param key the AssetKey for the asset loaded.
+     */
+    public void assetLoaded(AssetKey key);
+
+    /**
+     * Called when an asset has been requested (e.g any of the load*** methods
+     * in AssetManager are called).
+     * In contrast to the assetLoaded() method, this one will be called even
+     * if the asset has failed to load, or if it was retrieved from the cache.
+     *
+     * @param key
+     */
+    public void assetRequested(AssetKey key);
+    
+    /**
+     * Called when an asset dependency cannot be found for an asset.
+     * When an asset is loaded, each of its dependent assets that 
+     * have failed to load due to a {@link AssetNotFoundException}, will cause 
+     * an invocation of this callback. 
+     * 
+     * @param parentKey The key of the parent asset that is being loaded
+     * from within the user application.
+     * @param dependentAssetKey The asset key of the dependent asset that has 
+     * failed to load.
+     */
+    public void assetDependencyNotFound(AssetKey parentKey, AssetKey dependentAssetKey);
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetInfo.java b/engine/src/core/com/jme3/asset/AssetInfo.java
new file mode 100644
index 0000000..42ee822
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetInfo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import java.io.InputStream;
+
+/**
+ * The result of locating an asset through an AssetKey. Provides
+ * a means to read the asset data through an InputStream.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AssetInfo {
+
+    protected AssetManager manager;
+    protected AssetKey key;
+
+    public AssetInfo(AssetManager manager, AssetKey key) {
+        this.manager = manager;
+        this.key = key;
+    }
+
+    public AssetKey getKey() {
+        return key;
+    }
+
+    public AssetManager getManager() {
+        return manager;
+    }
+
+    @Override
+    public String toString(){
+        return getClass().getName() + "[" + "key=" + key + "]";
+    }
+
+    /**
+     * Implementations of this method should return an {@link InputStream}
+     * allowing access to the data represented by the {@link AssetKey}.
+     * <p>
+     * Each invocation of this method should return a new stream to the
+     * asset data, starting at the beginning of the file.
+     * 
+     * @return The asset data.
+     */
+    public abstract InputStream openStream();
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetKey.java b/engine/src/core/com/jme3/asset/AssetKey.java
new file mode 100644
index 0000000..9b6f0cd
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetKey.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.LinkedList;
+
+/**
+ * <code>AssetKey</code> is a key that is used to
+ * look up a resource from a cache. 
+ * This class should be immutable.
+ */
+public class AssetKey<T> implements Savable {
+
+    protected String name;
+    protected transient String folder;
+    protected transient String extension;
+    
+    public AssetKey(String name){
+        this.name = reducePath(name);
+        this.extension = getExtension(this.name);
+    }
+
+    public AssetKey(){
+    }
+
+    protected static String getExtension(String name){
+        int idx = name.lastIndexOf('.');
+        //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
+        if (name.toLowerCase().endsWith(".xml")) {
+            idx = name.substring(0, idx).lastIndexOf('.');
+            if (idx == -1) {
+                idx = name.lastIndexOf('.');
+            }
+        }
+        if (idx <= 0 || idx == name.length() - 1)
+            return "";
+        else
+            return name.substring(idx+1).toLowerCase();
+    }
+
+    protected static String getFolder(String name){
+        int idx = name.lastIndexOf('/');
+        if (idx <= 0 || idx == name.length() - 1)
+            return "";
+        else
+            return name.substring(0, idx+1);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return The extension of the <code>AssetKey</code>'s name. For example,
+     * the name "Interface/Logo/Monkey.png" has an extension of "png".
+     */
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getFolder(){
+        if (folder == null)
+            folder = getFolder(name);
+        
+        return folder;
+    }
+
+    /**
+     * Do any post-processing on the resource after it has been loaded.
+     * @param asset
+     */
+    public Object postProcess(Object asset){
+        return asset;
+    }
+
+    /**
+     * Create a new instance of the asset, based on a prototype that is stored
+     * in the cache. Implementations are allowed to return the given parameter
+     * as-is if it is considered that cloning is not necessary for that particular
+     * asset type.
+     * 
+     * @param asset The asset to be cloned.
+     * @return The asset, possibly cloned.
+     */
+    public Object createClonedInstance(Object asset){
+        return asset;
+    }
+
+    /**
+     * @return True if the asset for this key should be cached. Subclasses
+     * should override this method if they want to override caching behavior.
+     */
+    public boolean shouldCache(){
+        return true;
+    }
+
+    /**
+     * @return Should return true, if the asset objects implement the "Asset"
+     * interface and want to be removed from the cache when no longer
+     * referenced in user-code.
+     */
+    public boolean useSmartCache(){
+        return false;
+    }
+    
+    /**
+     * Removes all relative elements of a path (A/B/../C.png and A/./C.png).
+     * @param path The path containing relative elements
+     * @return A path without relative elements
+     */
+    public static String reducePath(String path) {
+        if (path == null || path.indexOf("./") == -1) {
+            return path;
+        }
+        String[] parts = path.split("/");
+        LinkedList<String> list = new LinkedList<String>();
+        for (int i = 0; i < parts.length; i++) {
+            String string = parts[i];
+            if (string.length() == 0 || string.equals(".")) {
+                //do nothing
+            } else if (string.equals("..")) {
+                if (list.size() > 0) {
+                    list.removeLast();
+                } else {
+                    throw new IllegalStateException("Relative path is outside assetmanager root!");
+                }
+            } else {
+                list.add(string);
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < list.size(); i++) {
+            String string = list.get(i);
+            if (i != 0) {
+                builder.append("/");
+            }
+            builder.append(string);
+        }
+        return builder.toString();
+    }
+    
+    @Override
+    public boolean equals(Object other){
+        if (!(other instanceof AssetKey)){
+            return false;
+        }
+        return name.equals(((AssetKey)other).name);
+    }
+
+    @Override
+    public int hashCode(){
+        return name.hashCode();
+    }
+
+    @Override
+    public String toString(){
+        return name;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(name, "name", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        name = reducePath(ic.readString("name", null));
+        extension = getExtension(name);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLoadException.java b/engine/src/core/com/jme3/asset/AssetLoadException.java
new file mode 100644
index 0000000..a0139aa
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLoadException.java
@@ -0,0 +1,17 @@
+package com.jme3.asset;
+
+/**
+ * <code>AssetLoadException</code> is thrown when the {@link AssetManager}
+ * is able to find the requested asset, but there was a problem while loading
+ * it.
+ *
+ * @author Kirill Vainer
+ */
+public class AssetLoadException extends RuntimeException {
+    public AssetLoadException(String message){
+        super(message);
+    }
+    public AssetLoadException(String message, Throwable cause){
+        super(message, cause);
+    }
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLoader.java b/engine/src/core/com/jme3/asset/AssetLoader.java
new file mode 100644
index 0000000..4ebffc5
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLoader.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import java.io.IOException;
+
+/**
+ * An interface for asset loaders. An <code>AssetLoader</code> is responsible
+ * for loading a certain type of asset associated with file extension(s).
+ * The loader will load the data in the provided {@link AssetInfo} object by
+ * calling {@link AssetInfo#openStream() }, returning an object representing
+ * the parsed data.
+ */
+public interface AssetLoader {
+
+    /**
+     * Loads asset from the given input stream, parsing it into
+     * an application-usable object.
+     *
+     * @return An object representing the resource.
+     * @throws java.io.IOException If an I/O error occurs while loading
+     */
+    public Object load(AssetInfo assetInfo) throws IOException;
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLocator.java b/engine/src/core/com/jme3/asset/AssetLocator.java
new file mode 100644
index 0000000..561e372
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLocator.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+/**
+ * <code>AssetLocator</code> is used to locate a resource based on an AssetKey.
+ *
+ * @author Kirill Vainer
+ */
+public interface AssetLocator {
+    /**
+     * @param rootPath The root path where to look for assets.
+     * Typically this method will only be called once per
+     * instance of an asset locator.
+     */
+    public void setRootPath(String rootPath);
+
+    /**
+     * Request to locate an asset. The asset key
+     * contains a name identifying the asset.
+     * If an asset was not found, null should be returned.
+     * The {@link AssetInfo} implementation provided should have a proper
+     * return value for its {@link AssetInfo#openStream() } method.
+     * 
+     * @param manager
+     * @param key
+     * @return The {@link AssetInfo} that was located, or null if not found.
+     */
+    public AssetInfo locate(AssetManager manager, AssetKey key);
+}
diff --git a/engine/src/core/com/jme3/asset/AssetManager.java b/engine/src/core/com/jme3/asset/AssetManager.java
new file mode 100644
index 0000000..a863ebf
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetManager.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioKey;
+import com.jme3.font.BitmapFont;
+import com.jme3.material.Material;
+import com.jme3.scene.Spatial;
+import com.jme3.shader.Shader;
+import com.jme3.shader.ShaderKey;
+import com.jme3.texture.Texture;
+import java.util.List;
+
+/**
+ * <code>AssetManager</code> provides an interface for managing the data assets
+ * of a jME3 application.
+ */
+public interface AssetManager {
+
+    /**
+     * Adds a ClassLoader that is used to load *Classes* that are needed for Assets like j3o models.
+     * This does *not* allow loading assets from that classpath, use registerLocator for that.
+     * @param loader A ClassLoader that Classes in asset files can be loaded from
+     */
+    public void addClassLoader(ClassLoader loader);
+
+    /**
+     * Remove a ClassLoader from the list of registered ClassLoaders
+     */
+    public void removeClassLoader(ClassLoader loader);
+
+    /**
+     * Retrieve the list of registered ClassLoaders that are used for loading Classes from
+     * asset files.
+     */
+    public List<ClassLoader> getClassLoaders();
+    
+    /**
+     * Registers a loader for the given extensions.
+     * @param loaderClassName
+     * @param extensions
+     */
+    public void registerLoader(String loaderClassName, String ... extensions);
+
+    /**
+     * Registers an {@link AssetLocator} by using a class name, instead of 
+     * a class instance. See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
+     * method for more information.
+     *
+     * @param rootPath The root path from which to locate assets, implementation
+     * dependent.
+     * @param locatorClassName The full class name of the {@link AssetLocator}
+     * implementation.
+     */
+    public void registerLocator(String rootPath, String locatorClassName);
+
+    /**
+     *
+     * @param loaderClass
+     * @param extensions
+     */
+    public void registerLoader(Class<? extends AssetLoader> loaderClass, String ... extensions);
+
+    /**
+     * Registers the given locator class for locating assets with this
+     * <code>AssetManager</code>. {@link AssetLocator}s are invoked in the order
+     * they were registered, to locate the asset by the {@link AssetKey}.
+     * Once an {@link AssetLocator} returns a non-null AssetInfo, it is sent
+     * to the {@link AssetLoader} to load the asset.
+     * Once a locator is registered, it can be removed via
+     * {@link #unregisterLocator(java.lang.String, java.lang.Class) }.
+     *
+     * @param rootPath Specifies the root path from which to locate assets
+     * for the given {@link AssetLocator}. The purpose of this parameter
+     * depends on the type of the {@link AssetLocator}.
+     * @param locatorClass The class type of the {@link AssetLocator} to register.
+     *
+     * @see AssetLocator#setRootPath(java.lang.String)
+     * @see AssetLocator#locate(com.jme3.asset.AssetManager, com.jme3.asset.AssetKey) 
+     * @see #unregisterLocator(java.lang.String, java.lang.Class) 
+     */
+    public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
+
+    /**
+     * Unregisters the given locator class. This essentially undoes the operation
+     * done by {@link #registerLocator(java.lang.String, java.lang.Class) }.
+     * 
+     * @param rootPath Should be the same as the root path specified in {@link
+     * #registerLocator(java.lang.String, java.lang.Class) }.
+     * @param locatorClass The locator class to unregister
+     */
+    public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
+    
+    /**
+     * Set an {@link AssetEventListener} to receive events from this
+     * <code>AssetManager</code>. There can only be one {@link  AssetEventListener}
+     * associated with an <code>AssetManager</code>
+     * 
+     * @param listener
+     */
+    public void setAssetEventListener(AssetEventListener listener);
+
+    /**
+     * Manually locates an asset with the given {@link AssetKey}. This method
+     * should be used for debugging or internal uses. <br/>
+     * The call will attempt to locate the asset by invoking the
+     * {@link AssetLocator} that are registered with this <code>AssetManager</code>,
+     * in the same way that the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) }
+     * method locates assets.
+     *
+     * @param key The {@link AssetKey} to locate.
+     * @return The {@link AssetInfo} object returned from the {@link AssetLocator}
+     * that located the asset, or null if the asset cannot be located.
+     */
+    public AssetInfo locateAsset(AssetKey<?> key);
+
+    /**
+     * Load an asset from a key, the asset will be located
+     * by one of the {@link AssetLocator} implementations provided in the
+     * {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
+     * call. If located successfully, it will be loaded via the the appropriate
+     * {@link AssetLoader} implementation based on the file's extension, as
+     * specified in the call 
+     * {@link AssetManager#registerLoader(java.lang.Class, java.lang.String[]) }.
+     *
+     * @param <T> The object type that will be loaded from the AssetKey instance.
+     * @param key The AssetKey
+     * @return The loaded asset, or null if it was failed to be located
+     * or loaded.
+     */
+    public <T> T loadAsset(AssetKey<T> key);
+
+    /**
+     * Load a named asset by name, calling this method
+     * is the same as calling
+     * <code>
+     * loadAsset(new AssetKey(name)).
+     * </code>
+     *
+     * @param name The name of the asset to load.
+     * @return The loaded asset, or null if failed to be loaded.
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Object loadAsset(String name);
+
+    /**
+     * Loads texture file, supported types are BMP, JPG, PNG, GIF,
+     * TGA and DDS.
+     *
+     * @param key The {@link TextureKey} to use for loading.
+     * @return The loaded texture, or null if failed to be loaded.
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Texture loadTexture(TextureKey key);
+
+    /**
+     * Loads texture file, supported types are BMP, JPG, PNG, GIF,
+     * TGA and DDS.
+     *
+     * @param name The name of the texture to load.
+     * @return The texture that was loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Texture loadTexture(String name);
+
+    /**
+     * Load audio file, supported types are WAV or OGG.
+     * @param key
+     * @return The audio data loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public AudioData loadAudio(AudioKey key);
+
+    /**
+     * Load audio file, supported types are WAV or OGG.
+     * The file is loaded without stream-mode.
+     * @param name
+     * @return The audio data loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public AudioData loadAudio(String name);
+
+    /**
+     * Loads a named model. Models can be jME3 object files (J3O) or
+     * OgreXML/OBJ files.
+     * @param key
+     * @return The model that was loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Spatial loadModel(ModelKey key);
+
+    /**
+     * Loads a named model. Models can be jME3 object files (J3O) or
+     * OgreXML/OBJ files.
+     * @param name
+     * @return The model that was loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Spatial loadModel(String name);
+
+    /**
+     * Load a material (J3M) file.
+     * @param name
+     * @return The material that was loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Material loadMaterial(String name);
+
+    /**
+     * Loads shader file(s), shouldn't be used by end-user in most cases.
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+     */
+    public Shader loadShader(ShaderKey key);
+
+    /**
+     * Load a font file. Font files are in AngelCode text format,
+     * and are with the extension "fnt".
+     *
+     * @param name
+     * @return The font loaded
+     *
+     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) 
+     */
+    public BitmapFont loadFont(String name);
+}
diff --git a/engine/src/core/com/jme3/asset/AssetNotFoundException.java b/engine/src/core/com/jme3/asset/AssetNotFoundException.java
new file mode 100644
index 0000000..e04a7fb
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetNotFoundException.java
@@ -0,0 +1,17 @@
+package com.jme3.asset;
+
+/**
+ * <code>AssetNotFoundException</code> is thrown when the {@link AssetManager}
+ * is unable to locate the requested asset using any of the registered
+ * {@link AssetLocator}s.
+ *
+ * @author Kirill Vainer
+ */
+public class AssetNotFoundException extends RuntimeException {
+    public AssetNotFoundException(String message){
+        super(message);
+    }
+    public AssetNotFoundException(String message, Exception ex){
+        super(message, ex);
+    }
+}
diff --git a/engine/src/core/com/jme3/asset/Desktop.cfg b/engine/src/core/com/jme3/asset/Desktop.cfg
new file mode 100644
index 0000000..93dafb7
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/Desktop.cfg
@@ -0,0 +1,22 @@
+LOCATOR / com.jme3.asset.plugins.ClasspathLocator

+

+LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg

+LOADER com.jme3.audio.plugins.WAVLoader : wav

+LOADER com.jme3.audio.plugins.OGGLoader : ogg

+LOADER com.jme3.material.plugins.J3MLoader : j3m

+LOADER com.jme3.material.plugins.J3MLoader : j3md

+LOADER com.jme3.font.plugins.BitmapFontLoader : fnt

+LOADER com.jme3.texture.plugins.DDSLoader : dds

+LOADER com.jme3.texture.plugins.PFMLoader : pfm

+LOADER com.jme3.texture.plugins.HDRLoader : hdr

+LOADER com.jme3.texture.plugins.TGALoader : tga

+LOADER com.jme3.export.binary.BinaryImporter : j3o

+LOADER com.jme3.export.binary.BinaryImporter : j3f

+LOADER com.jme3.scene.plugins.OBJLoader : obj

+LOADER com.jme3.scene.plugins.MTLLoader : mtl

+LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml

+LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml

+LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material

+LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene

+LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend

+LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/asset/DesktopAssetManager.java b/engine/src/core/com/jme3/asset/DesktopAssetManager.java
new file mode 100644
index 0000000..e2d8ef8
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/DesktopAssetManager.java
@@ -0,0 +1,421 @@
+/*

+ * Copyright (c) 2009-2010 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.asset;

+

+import com.jme3.asset.AssetCache.SmartAssetInfo;

+import com.jme3.audio.AudioData;

+import com.jme3.audio.AudioKey;

+import com.jme3.font.BitmapFont;

+import com.jme3.material.Material;

+import com.jme3.scene.Spatial;

+import com.jme3.shader.Shader;

+import com.jme3.shader.ShaderKey;

+import com.jme3.texture.Texture;

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.URL;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * <code>AssetManager</code> is the primary method for managing and loading

+ * assets inside jME.

+ *

+ * @author Kirill Vainer

+ */

+public class DesktopAssetManager implements AssetManager {

+

+    private static final Logger logger = Logger.getLogger(AssetManager.class.getName());

+

+    private final AssetCache cache = new AssetCache();

+    private final ImplHandler handler = new ImplHandler(this);

+

+    private AssetEventListener eventListener = null;

+    private List<ClassLoader> classLoaders;

+

+//    private final ThreadingManager threadingMan = new ThreadingManager(this);

+//    private final Set<AssetKey> alreadyLoadingSet = new HashSet<AssetKey>();

+

+    public DesktopAssetManager(){

+        this(null);

+    }

+

+    @Deprecated

+    public DesktopAssetManager(boolean loadDefaults){

+        this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"));

+    }

+

+    public DesktopAssetManager(URL configFile){

+        if (configFile != null){

+            InputStream stream = null;

+            try{

+                AssetConfig cfg = new AssetConfig(this);

+                stream = configFile.openStream();

+                cfg.loadText(stream);

+            }catch (IOException ex){

+                logger.log(Level.SEVERE, "Failed to load asset config", ex);

+            }finally{

+                if (stream != null)

+                    try{

+                        stream.close();

+                    }catch (IOException ex){

+                    }

+            }

+        }

+        logger.info("DesktopAssetManager created.");

+    }

+

+    public void addClassLoader(ClassLoader loader){

+        if(classLoaders == null)

+            classLoaders = Collections.synchronizedList(new ArrayList<ClassLoader>());

+        synchronized(classLoaders) {

+            classLoaders.add(loader);

+        }

+    }

+    

+    public void removeClassLoader(ClassLoader loader){

+        if(classLoaders != null) synchronized(classLoaders) {

+                classLoaders.remove(loader);

+            }

+    }

+

+    public List<ClassLoader> getClassLoaders(){

+        return classLoaders;

+    }

+    

+    public void setAssetEventListener(AssetEventListener listener){

+        eventListener = listener;

+    }

+

+    public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){

+        handler.addLoader(loader, extensions);

+        if (logger.isLoggable(Level.FINER)){

+            logger.log(Level.FINER, "Registered loader: {0} for extensions {1}",

+              new Object[]{loader.getSimpleName(), Arrays.toString(extensions)});

+        }

+    }

+

+    public void registerLoader(String clsName, String ... extensions){

+        Class<? extends AssetLoader> clazz = null;

+        try{

+            clazz = (Class<? extends AssetLoader>) Class.forName(clsName);

+        }catch (ClassNotFoundException ex){

+            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);

+        }catch (NoClassDefFoundError ex){

+            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);

+        }

+        if (clazz != null){

+            registerLoader(clazz, extensions);

+        }

+    }

+

+    public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){

+        handler.addLocator(locatorClass, rootPath);

+        if (logger.isLoggable(Level.FINER)){

+            logger.log(Level.FINER, "Registered locator: {0}",

+                    locatorClass.getSimpleName());

+        }

+    }

+

+    public void registerLocator(String rootPath, String clsName){

+        Class<? extends AssetLocator> clazz = null;

+        try{

+            clazz = (Class<? extends AssetLocator>) Class.forName(clsName);

+        }catch (ClassNotFoundException ex){

+            logger.log(Level.WARNING, "Failed to find locator: "+clsName, ex);

+        }catch (NoClassDefFoundError ex){

+            logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);

+        }

+        if (clazz != null){

+            registerLocator(rootPath, clazz);

+        }

+    }

+    

+    public void unregisterLocator(String rootPath, Class<? extends AssetLocator> clazz){

+        handler.removeLocator(clazz, rootPath);

+        if (logger.isLoggable(Level.FINER)){

+            logger.log(Level.FINER, "Unregistered locator: {0}",

+                    clazz.getSimpleName());

+        }

+    }

+

+    public void clearCache(){

+        cache.deleteAllAssets();

+    }

+

+    /**

+     * Delete an asset from the cache, returns true if it was deleted

+     * successfully.

+     * <br/><br/>

+     * <font color="red">Thread-safe.</font>

+     */

+    public boolean deleteFromCache(AssetKey key){

+        return cache.deleteFromCache(key);

+    }

+

+    /**

+     * Adds a resource to the cache.

+     * <br/><br/>

+     * <font color="red">Thread-safe.</font>

+     */

+    public void addToCache(AssetKey key, Object asset){

+        cache.addToCache(key, asset);

+    }

+

+    public AssetInfo locateAsset(AssetKey<?> key){

+        if (handler.getLocatorCount() == 0){

+            logger.warning("There are no locators currently"+

+                           " registered. Use AssetManager."+

+                           "registerLocator() to register a"+

+                           " locator.");

+            return null;

+        }

+

+        AssetInfo info = handler.tryLocate(key);

+        if (info == null){

+            logger.log(Level.WARNING, "Cannot locate resource: {0}", key);

+        }

+

+        return info;

+    }

+

+    /**

+     * <font color="red">Thread-safe.</font>

+     *

+     * @param <T>

+     * @param key

+     * @return

+     */

+      public <T> T loadAsset(AssetKey<T> key){

+        if (key == null)

+            throw new IllegalArgumentException("key cannot be null");

+        

+        if (eventListener != null)

+            eventListener.assetRequested(key);

+

+        AssetKey smartKey = null;

+        Object o = null;

+        if (key.shouldCache()){

+            if (key.useSmartCache()){

+                SmartAssetInfo smartInfo = cache.getFromSmartCache(key);

+                if (smartInfo != null){

+                    smartKey = smartInfo.smartKey.get();

+                    if (smartKey != null){

+                        o = smartInfo.asset;

+                    }

+                }

+            }else{

+                o = cache.getFromCache(key);

+            }

+        }

+        if (o == null){

+            AssetLoader loader = handler.aquireLoader(key);

+            if (loader == null){

+                throw new IllegalStateException("No loader registered for type \"" +

+                                                key.getExtension() + "\"");

+            }

+

+            if (handler.getLocatorCount() == 0){

+                throw new IllegalStateException("There are no locators currently"+

+                                                " registered. Use AssetManager."+

+                                                "registerLocator() to register a"+

+                                                " locator.");

+            }

+

+            AssetInfo info = handler.tryLocate(key);

+            if (info == null){

+                if (handler.getParentKey() != null && eventListener != null){

+                    // Inform event listener that an asset has failed to load.

+                    // If the parent AssetLoader chooses not to propagate

+                    // the exception, this is the only means of finding

+                    // that something went wrong.

+                    eventListener.assetDependencyNotFound(handler.getParentKey(), key);

+                }

+                throw new AssetNotFoundException(key.toString());

+            }

+

+            try {

+                handler.establishParentKey(key);

+                o = loader.load(info);

+            } catch (IOException ex) {

+                throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);

+            } finally {

+                handler.releaseParentKey(key);

+            }

+            if (o == null){

+                throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using" + loader.getClass().getSimpleName());

+            }else{

+                if (logger.isLoggable(Level.FINER)){

+                    logger.log(Level.FINER, "Loaded {0} with {1}",

+                            new Object[]{key, loader.getClass().getSimpleName()});

+                }

+                

+                // do processing on asset before caching

+                o = key.postProcess(o);

+

+                if (key.shouldCache())

+                    cache.addToCache(key, o);

+

+                if (eventListener != null)

+                    eventListener.assetLoaded(key);

+            }

+        }

+

+        // object o is the asset

+        // create an instance for user

+        T clone = (T) key.createClonedInstance(o);

+

+        if (key.useSmartCache()){

+            if (smartKey != null){

+                // smart asset was already cached, use original key

+                ((Asset)clone).setKey(smartKey);

+            }else{

+                // smart asset was cached on this call, use our key

+                ((Asset)clone).setKey(key);

+            }

+        }

+        

+        return clone;

+    }

+

+    public Object loadAsset(String name){

+        return loadAsset(new AssetKey(name));

+    }

+

+    /**

+     * Loads a texture.

+     *

+     * @return

+     */

+    public Texture loadTexture(TextureKey key){

+        return (Texture) loadAsset(key);

+    }

+

+    public Material loadMaterial(String name){

+        return (Material) loadAsset(new MaterialKey(name));

+    }

+

+    /**

+     * Loads a texture.

+     *

+     * @param name

+     * @param generateMipmaps Enable if applying texture to 3D objects, disable

+     * for GUI/HUD elements.

+     * @return

+     */

+    public Texture loadTexture(String name, boolean generateMipmaps){

+        TextureKey key = new TextureKey(name, true);

+        key.setGenerateMips(generateMipmaps);

+        key.setAsCube(false);

+        return loadTexture(key);

+    }

+

+    public Texture loadTexture(String name, boolean generateMipmaps, boolean flipY, boolean asCube, int aniso){

+        TextureKey key = new TextureKey(name, flipY);

+        key.setGenerateMips(generateMipmaps);

+        key.setAsCube(asCube);

+        key.setAnisotropy(aniso);

+        return loadTexture(key);

+    }

+

+    public Texture loadTexture(String name){

+        return loadTexture(name, true);

+    }

+

+    public AudioData loadAudio(AudioKey key){

+        return (AudioData) loadAsset(key);

+    }

+

+    public AudioData loadAudio(String name){

+        return loadAudio(new AudioKey(name, false));

+    }

+

+    /**

+     * Loads a bitmap font with the given name.

+     *

+     * @param name

+     * @return

+     */

+    public BitmapFont loadFont(String name){

+        return (BitmapFont) loadAsset(new AssetKey(name));

+    }

+

+    public InputStream loadGLSLLibrary(AssetKey key){

+        return (InputStream) loadAsset(key);

+    }

+

+    /**

+     * Load a vertex/fragment shader combo.

+     *

+     * @param key

+     * @return

+     */

+    public Shader loadShader(ShaderKey key){

+        // cache abuse in method

+        // that doesn't use loaders/locators

+        Shader s = (Shader) cache.getFromCache(key);

+        if (s == null){

+            String vertName = key.getVertName();

+            String fragName = key.getFragName();

+

+            String vertSource = (String) loadAsset(new AssetKey(vertName));

+            String fragSource = (String) loadAsset(new AssetKey(fragName));

+

+            s = new Shader(key.getLanguage());

+            s.addSource(Shader.ShaderType.Vertex,   vertName, vertSource, key.getDefines().getCompiled());

+            s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());

+

+            cache.addToCache(key, s);

+        }

+        return s;

+    }

+

+    public Spatial loadModel(ModelKey key){

+        return (Spatial) loadAsset(key);

+    }

+

+    /**

+     * Load a model.

+     *

+     * @param name

+     * @return

+     */

+    public Spatial loadModel(String name){

+        return loadModel(new ModelKey(name));

+    }

+    

+}

diff --git a/engine/src/core/com/jme3/asset/ImplHandler.java b/engine/src/core/com/jme3/asset/ImplHandler.java
new file mode 100644
index 0000000..9b0b50a
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ImplHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>ImplHandler</code> manages the asset loader and asset locator
+ * implementations in a thread safe way. This allows implementations
+ * which store local persistent data to operate with a multi-threaded system.
+ * This is done by keeping an instance of each asset loader and asset
+ * locator object in a thread local.
+ */
+public class ImplHandler {
+
+    private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
+
+    private final AssetManager owner;
+    
+    private final ThreadLocal<AssetKey> parentAssetKey 
+            = new ThreadLocal<AssetKey>();
+    
+    private final ArrayList<ImplThreadLocal> genericLocators =
+                new ArrayList<ImplThreadLocal>();
+
+    private final HashMap<String, ImplThreadLocal> loaders =
+                new HashMap<String, ImplThreadLocal>();
+
+    public ImplHandler(AssetManager owner){
+        this.owner = owner;
+    }
+
+    protected class ImplThreadLocal extends ThreadLocal {
+
+        private final Class<?> type;
+        private final String path;
+
+        public ImplThreadLocal(Class<?> type){
+            this.type = type;
+            path = null;
+        }
+
+        public ImplThreadLocal(Class<?> type, String path){
+            this.type = type;
+            this.path = path;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public Class<?> getTypeClass(){
+            return type;
+        }
+
+        @Override
+        protected Object initialValue(){
+            try {
+                return type.newInstance();
+            } catch (InstantiationException ex) {
+                logger.log(Level.SEVERE,"Cannot create locator of type {0}, does"
+                            + " the class have an empty and publically accessible"+
+                              " constructor?", type.getName());
+                logger.throwing(type.getName(), "<init>", ex);
+            } catch (IllegalAccessException ex) {
+                logger.log(Level.SEVERE,"Cannot create locator of type {0}, "
+                            + "does the class have an empty and publically "
+                            + "accessible constructor?", type.getName());
+                logger.throwing(type.getName(), "<init>", ex);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Establishes the asset key that is used for tracking dependent assets
+     * that have failed to load. When set, the {@link DesktopAssetManager}
+     * gets a hint that it should suppress {@link AssetNotFoundException}s
+     * and instead call the listener callback (if set).
+     * 
+     * @param parentKey The parent key  
+     */
+    public void establishParentKey(AssetKey parentKey){
+        if (parentAssetKey.get() == null){
+            parentAssetKey.set(parentKey);
+        }
+    }
+    
+    public void releaseParentKey(AssetKey parentKey){
+        if (parentAssetKey.get() == parentKey){
+            parentAssetKey.set(null);
+        }
+    }
+    
+    public AssetKey getParentKey(){
+        return parentAssetKey.get();
+    }
+    
+    /**
+     * Attempts to locate the given resource name.
+     * @param key The full name of the resource.
+     * @return The AssetInfo containing resource information required for
+     * access, or null if not found.
+     */
+    public AssetInfo tryLocate(AssetKey key){
+        synchronized (genericLocators){
+            if (genericLocators.isEmpty())
+                return null;
+
+            for (ImplThreadLocal local : genericLocators){
+                AssetLocator locator = (AssetLocator) local.get();
+                if (local.getPath() != null){
+                    locator.setRootPath((String) local.getPath());
+                }
+                AssetInfo info = locator.locate(owner, key);
+                if (info != null)
+                    return info;
+            }
+        }
+        return null;
+    }
+
+    public int getLocatorCount(){
+        synchronized (genericLocators){
+            return genericLocators.size();
+        }
+    }
+
+    /**
+     * Returns the AssetLoader registered for the given extension
+     * of the current thread.
+     * @return AssetLoader registered with addLoader.
+     */
+    public AssetLoader aquireLoader(AssetKey key){
+        synchronized (loaders){
+            ImplThreadLocal local = loaders.get(key.getExtension());
+            if (local != null){
+                AssetLoader loader = (AssetLoader) local.get();
+                return loader;
+            }
+            return null;
+        }
+    }
+
+    public void addLoader(final Class<?> loaderType, String ... extensions){
+        ImplThreadLocal local = new ImplThreadLocal(loaderType);
+        for (String extension : extensions){
+            extension = extension.toLowerCase();
+            synchronized (loaders){
+                loaders.put(extension, local);
+            }
+        }
+    }
+
+    public void addLocator(final Class<?> locatorType, String rootPath){
+        ImplThreadLocal local = new ImplThreadLocal(locatorType, rootPath);
+        synchronized (genericLocators){
+            genericLocators.add(local);
+        }
+    }
+
+    public void removeLocator(final Class<?> locatorType, String rootPath){
+        synchronized (genericLocators){
+            Iterator<ImplThreadLocal> it = genericLocators.iterator();
+            while (it.hasNext()){
+                ImplThreadLocal locator = it.next();
+                if (locator.getPath().equals(rootPath) &&
+                    locator.getTypeClass().equals(locatorType)){
+                    it.remove();
+                }
+            }
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/asset/MaterialKey.java b/engine/src/core/com/jme3/asset/MaterialKey.java
new file mode 100644
index 0000000..cc74fbc
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/MaterialKey.java
@@ -0,0 +1,29 @@
+package com.jme3.asset;
+
+import com.jme3.material.Material;
+
+/**
+ * Used for loading {@link Material materials} only (not material definitions).
+ * 
+ * @author Kirill Vainer
+ */
+public class MaterialKey extends AssetKey {
+    public MaterialKey(String name){
+        super(name);
+    }
+
+    public MaterialKey(){
+        super();
+    }
+
+    @Override
+    public boolean useSmartCache(){
+        return true;
+    }
+    
+    @Override
+    public Object createClonedInstance(Object asset){
+        Material mat = (Material) asset;
+        return mat.clone();
+    }
+}
diff --git a/engine/src/core/com/jme3/asset/ModelKey.java b/engine/src/core/com/jme3/asset/ModelKey.java
new file mode 100644
index 0000000..fcf5c53
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ModelKey.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * 
+ * @author Kirill Vainer
+ */
+public class ModelKey extends AssetKey<Spatial> {
+
+    public ModelKey(String name){
+        super(name);
+    }
+
+    public ModelKey(){
+        super();
+    }
+    @Override
+    public boolean useSmartCache(){
+        return true;
+    }
+    
+    @Override
+    public Object createClonedInstance(Object asset){
+        Spatial model = (Spatial) asset;
+        return model.clone();
+    }
+
+}
diff --git a/engine/src/core/com/jme3/asset/TextureKey.java b/engine/src/core/com/jme3/asset/TextureKey.java
new file mode 100644
index 0000000..118c84e
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/TextureKey.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.texture.Texture.Type;
+import com.jme3.texture.*;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class TextureKey extends AssetKey<Texture> {
+
+    private boolean generateMips;
+    private boolean flipY;
+    private boolean asCube;
+    private boolean asTexture3D;
+    private int anisotropy;
+    private Texture.Type textureTypeHint=Texture.Type.TwoDimensional;
+
+    public TextureKey(String name, boolean flipY) {
+        super(name);
+        this.flipY = flipY;
+    }
+
+    public TextureKey(String name) {
+        super(name);
+        this.flipY = true;
+    }
+
+    public TextureKey() {
+    }
+
+    @Override
+    public String toString() {
+        return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
+    }
+
+    /**
+     * Enable smart caching for textures
+     * @return true to enable smart cache
+     */
+    @Override
+    public boolean useSmartCache() {
+        return true;
+    }
+
+    @Override
+    public Object createClonedInstance(Object asset) {
+        Texture tex = (Texture) asset;
+        return tex.createSimpleClone();
+    }
+
+    @Override
+    public Object postProcess(Object asset) {
+        Image img = (Image) asset;
+        if (img == null) {
+            return null;
+        }
+
+        Texture tex;
+        if (isAsCube()) {
+            if (isFlipY()) {
+                // also flip -y and +y image in cubemap
+                ByteBuffer pos_y = img.getData(2);
+                img.setData(2, img.getData(3));
+                img.setData(3, pos_y);
+            }
+            tex = new TextureCubeMap();
+        } else if (isAsTexture3D()) {
+            tex = new Texture3D();
+        } else {
+            tex = new Texture2D();
+        }
+
+        // enable mipmaps if image has them
+        // or generate them if requested by user
+        if (img.hasMipmaps() || isGenerateMips()) {
+            tex.setMinFilter(Texture.MinFilter.Trilinear);
+        }
+
+        tex.setAnisotropicFilter(getAnisotropy());
+        tex.setName(getName());
+        tex.setImage(img);
+        return tex;
+    }
+
+    public boolean isFlipY() {
+        return flipY;
+    }
+
+    public int getAnisotropy() {
+        return anisotropy;
+    }
+
+    public void setAnisotropy(int anisotropy) {
+        this.anisotropy = anisotropy;
+    }
+
+    public boolean isAsCube() {
+        return asCube;
+    }
+
+    public void setAsCube(boolean asCube) {
+        this.asCube = asCube;
+    }
+
+    public boolean isGenerateMips() {
+        return generateMips;
+    }
+
+    public void setGenerateMips(boolean generateMips) {
+        this.generateMips = generateMips;
+    }
+
+    public boolean isAsTexture3D() {
+        return asTexture3D;
+    }
+
+    public void setAsTexture3D(boolean asTexture3D) {
+        this.asTexture3D = asTexture3D;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof TextureKey)) {
+            return false;
+        }
+        return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
+    }
+
+    public Type getTextureTypeHint() {
+        return textureTypeHint;
+    }
+
+    public void setTextureTypeHint(Type textureTypeHint) {
+        this.textureTypeHint = textureTypeHint;
+    }   
+    
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(flipY, "flip_y", false);
+        oc.write(generateMips, "generate_mips", false);
+        oc.write(asCube, "as_cubemap", false);
+        oc.write(anisotropy, "anisotropy", 0);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        flipY = ic.readBoolean("flip_y", false);
+        generateMips = ic.readBoolean("generate_mips", false);
+        asCube = ic.readBoolean("as_cubemap", false);
+        anisotropy = ic.readInt("anisotropy", 0);
+    }
+}
diff --git a/engine/src/core/com/jme3/asset/ThreadingManager.java b/engine/src/core/com/jme3/asset/ThreadingManager.java
new file mode 100644
index 0000000..6ed2c74
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ThreadingManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * <code>ThreadingManager</code> manages the threads used to load content
+ * within the Content Manager system. A pool of threads and a task queue
+ * is used to load resource data and perform I/O while the application's
+ * render thread is active. 
+ */
+public class ThreadingManager {
+
+    protected final ExecutorService executor =
+            Executors.newFixedThreadPool(2,
+                                         new LoadingThreadFactory());
+
+    protected final AssetManager owner;
+
+    protected int nextThreadId = 0;
+
+    public ThreadingManager(AssetManager owner){
+        this.owner = owner;
+    }
+
+    protected class LoadingThreadFactory implements ThreadFactory {
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(r, "pool" + (nextThreadId++));
+            t.setDaemon(true);
+            t.setPriority(Thread.MIN_PRIORITY);
+            return t;
+        }
+    }
+
+    protected class LoadingTask implements Callable<Object> {
+        private final String resourceName;
+        public LoadingTask(String resourceName){
+            this.resourceName = resourceName;
+        }
+        public Object call() throws Exception {
+            return owner.loadAsset(new AssetKey(resourceName));
+        }
+    }
+
+//    protected class MultiLoadingTask implements Callable<Void> {
+//        private final String[] resourceNames;
+//        public MultiLoadingTask(String[] resourceNames){
+//            this.resourceNames = resourceNames;
+//        }
+//        public Void call(){
+//            owner.loadContents(resourceNames);
+//            return null;
+//        }
+//    }
+
+//    public Future<Void> loadContents(String ... names){
+//        return executor.submit(new MultiLoadingTask(names));
+//    }
+
+//    public Future<Object> loadContent(String name) {
+//        return executor.submit(new LoadingTask(name));
+//    }
+
+    public static boolean isLoadingThread() {
+        return Thread.currentThread().getName().startsWith("pool");
+    }
+
+
+}
diff --git a/engine/src/core/com/jme3/asset/package.html b/engine/src/core/com/jme3/asset/package.html
new file mode 100644
index 0000000..d9e1913
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/package.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+<code>com.jme3.asset</code> contains the {@link com.jme3.asset.AssetManager}, 
+a utility class that is used to load assets such as textures, models, and
+sound effects in a jME3 application. <br>
+
+<p>
+    
+<h3>AssetLoaders</h3>
+{@link com.jme3.asset.AssetLoader asset loaders} are registered to load 
+assets of a particular format. For example, an <code>AssetLoader</code> that
+loads TGA images should read a stream in .tga format and return an 
+{@link com.jme3.texture.Image} object as its output. 
+<code>AssetLoader</code>s are initialized once a file of that format
+is loaded, there's only one AssetLoader per thread so 
+AssetLoader's load() method does not have to be thread safe.
+
+<h3>AssetLocators</h3>
+{@link com.jme3.asset.AssetLocators asset locators} are used to resolve 
+an asset name (a string) into an {@link java.io.InputStream} which is 
+contained in an {@link com.jme3.asset.AssetInfo} object. 
+There are <code>AssetLocators</code> for loading files from the application's 
+classpath, the local hard drive, a ZIP file, an HTTP server, and more. The user
+can implement their own AssetLocators and register them with the <code>AssetManager</code>
+to load their resources from their own location.
+
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/audio/AudioBuffer.java b/engine/src/core/com/jme3/audio/AudioBuffer.java
new file mode 100644
index 0000000..7d29c49
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioBuffer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.audio.AudioData.DataType;
+import com.jme3.util.NativeObject;
+import java.nio.ByteBuffer;
+
+/**
+ * An <code>AudioBuffer</code> is an implementation of AudioData
+ * where the audio is buffered (stored in memory). All parts of it
+ * are accessible at any time. <br/>
+ * AudioBuffers are useful for short sounds, like effects, etc.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioBuffer extends AudioData {
+
+    /**
+     * The audio data buffer. Should be direct and native ordered.
+     */
+    protected ByteBuffer audioData;
+
+    public AudioBuffer(){
+        super();
+    }
+    
+    protected AudioBuffer(int id){
+        super(id);
+    }
+
+    public DataType getDataType() {
+        return DataType.Buffer;
+    }
+
+    /**
+     * @return The duration of the audio in seconds. It is expected
+     * that audio is uncompressed.
+     */
+    public float getDuration(){
+        int bytesPerSec = (bitsPerSample / 8) * channels * sampleRate;
+        if (audioData != null)
+            return (float) audioData.capacity() / bytesPerSec;
+        else
+            return Float.NaN; // unknown
+    }
+
+    @Override
+    public String toString(){
+        return getClass().getSimpleName() +
+               "[id="+id+", ch="+channels+", bits="+bitsPerSample +
+               ", rate="+sampleRate+", duration="+getDuration()+"]";
+    }
+
+    /**
+     * Update the data in the buffer with new data.
+     * @param data
+     */
+    public void updateData(ByteBuffer data){
+        this.audioData = data;
+        updateNeeded = true;
+    }
+
+    /**
+     * @return The buffered audio data.
+     */
+    public ByteBuffer getData(){
+        return audioData;
+    }
+
+    public void resetObject() {
+        id = -1;
+        setUpdateNeeded();
+    }
+
+    public void deleteObject(AudioRenderer ar) {
+        
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((AudioRenderer)rendererObject).deleteAudioData(this);
+    }
+
+    @Override
+    public NativeObject createDestructableClone() {
+        return new AudioBuffer(id);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioContext.java b/engine/src/core/com/jme3/audio/AudioContext.java
new file mode 100644
index 0000000..642f832
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioContext.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+/**
+ *  Holds render thread specific audio context information.
+ *
+ *  @author Paul Speed
+ */
+public class AudioContext {
+
+    private static ThreadLocal<AudioRenderer> audioRenderer = new ThreadLocal<AudioRenderer>();
+ 
+    public static void setAudioRenderer( AudioRenderer ar ) {
+        audioRenderer.set(ar);       
+    }
+    
+    public static AudioRenderer getAudioRenderer() {
+        return audioRenderer.get();
+    }
+}
diff --git a/engine/src/core/com/jme3/audio/AudioData.java b/engine/src/core/com/jme3/audio/AudioData.java
new file mode 100644
index 0000000..186a734
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioData.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.util.NativeObject;
+
+/**
+ * <code>AudioData</code> is an abstract representation
+ * of audio data. There are two ways to handle audio data, short audio files
+ * are to be stored entirely in memory, while long audio files (music) are
+ * streamed from the hard drive as they are played.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AudioData extends NativeObject {
+
+    protected int sampleRate;
+    protected int channels;
+    protected int bitsPerSample;
+
+    public enum DataType {
+        Buffer,
+        Stream
+    }
+    
+    public AudioData(){
+        super(AudioData.class);
+    }
+
+    protected AudioData(int id){
+        super(AudioData.class, id);
+    }
+    
+    /**
+     * @return The data type, either <code>Buffer</code> or <code>Stream</code>.
+     */
+    public abstract DataType getDataType();
+
+    /**
+     * @return the duration in seconds of the audio clip.
+     */
+    public abstract float getDuration();
+
+    /**
+     * @return Bits per single sample from a channel.
+     */
+    public int getBitsPerSample() {
+        return bitsPerSample;
+    }
+
+    /**
+     * @return Number of channels. 1 for mono, 2 for stereo, etc.
+     */
+    public int getChannels() {
+        return channels;
+    }
+
+    /**
+     * @return The sample rate, or how many samples per second.
+     */
+    public int getSampleRate() {
+        return sampleRate;
+    }
+
+    /**
+     * Setup the format of the audio data.
+     * @param channels # of channels, 1 = mono, 2 = stereo
+     * @param bitsPerSample Bits per sample, e.g 8 bits, 16 bits.
+     * @param sampleRate Sample rate, 44100, 22050, etc.
+     */
+    public void setupFormat(int channels, int bitsPerSample, int sampleRate){
+        if (id != -1)
+            throw new IllegalStateException("Already set up");
+
+        this.channels = channels;
+        this.bitsPerSample = bitsPerSample;
+        this.sampleRate = sampleRate;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioKey.java b/engine/src/core/com/jme3/audio/AudioKey.java
new file mode 100644
index 0000000..e9492bb
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioKey.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * <code>AudioKey</code> is extending AssetKey by holding stream flag.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioKey extends AssetKey<AudioData> {
+
+    private boolean stream;
+    private boolean streamCache;
+
+    /**
+     * Create a new AudioKey.
+     * 
+     * @param name Name of the asset
+     * @param stream If true, the audio will be streamed from harddrive,
+     * otherwise it will be buffered entirely and then played.
+     * @param streamCache If stream is true, then this specifies if
+     * the stream cache is used. When enabled, the audio stream will
+     * be read entirely but not decoded, allowing features such as 
+     * seeking, determining duration and looping.
+     */
+    public AudioKey(String name, boolean stream, boolean streamCache){
+        this(name, stream);
+        this.streamCache = streamCache;
+    }
+    
+    /**
+     * Create a new AudioKey
+     *
+     * @param name Name of the asset
+     * @param stream If true, the audio will be streamed from harddrive,
+     * otherwise it will be buffered entirely and then played.
+     */
+    public AudioKey(String name, boolean stream){
+        super(name);
+        this.stream = stream;
+    }
+
+    public AudioKey(String name){
+        super(name);
+        this.stream = false;
+    }
+
+    public AudioKey(){
+    }
+
+    @Override
+    public String toString(){
+        return name + (stream ? 
+                          (streamCache ? 
+                            " (Stream/Cache)" : 
+                            " (Stream)") : 
+                         " (Buffer)");
+    }
+
+    /**
+     * @return True if the loaded audio should be a {@link AudioStream} or
+     * false if it should be a {@link AudioBuffer}.
+     */
+    public boolean isStream() {
+        return stream;
+    }
+    
+    /**
+     * Specifies if the stream cache is used. 
+     * 
+     * When enabled, the audio stream will
+     * be read entirely but not decoded, allowing features such as 
+     * seeking, looping and determining duration.
+     */
+    public boolean useStreamCache(){
+        return streamCache;
+    }
+
+    @Override
+    public boolean shouldCache(){
+        return !stream && !streamCache;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException{
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(stream, "do_stream", false);
+        oc.write(streamCache, "use_stream_cache", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException{
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        stream = ic.readBoolean("do_stream", false);
+        streamCache = ic.readBoolean("use_stream_cache", false);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioNode.java b/engine/src/core/com/jme3/audio/AudioNode.java
new file mode 100644
index 0000000..bc8b8cd
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioNode.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.util.PlaceholderAssets;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An <code>AudioNode</code> is used in jME3 for playing audio files.
+ * <br/>
+ * First, an {@link AudioNode} is loaded from file, and then assigned
+ * to an audio node for playback. Once the audio node is attached to the 
+ * scene, its location will influence the position it is playing from relative
+ * to the {@link Listener}.
+ * <br/>
+ * An audio node can also play in "headspace", meaning its location
+ * or velocity does not influence how it is played. 
+ * The "positional" property of an AudioNode can be set via 
+ * {@link AudioNode#setPositional(boolean) }.
+ * 
+ * @author normenhansen
+ * @author Kirill Vainer
+ */
+public class AudioNode extends Node {
+
+    protected boolean loop = false;
+    protected float volume = 1;
+    protected float pitch = 1;
+    protected float timeOffset = 0;
+    protected Filter dryFilter;
+    protected AudioKey audioKey;
+    protected transient AudioData data = null;
+    protected transient volatile Status status = Status.Stopped;
+    protected transient volatile int channel = -1;
+    protected Vector3f velocity = new Vector3f();
+    protected boolean reverbEnabled = true;
+    protected float maxDistance = 200; // 200 meters
+    protected float refDistance = 10; // 10 meters
+    protected Filter reverbFilter;
+    private boolean directional = false;
+    protected Vector3f direction = new Vector3f(0, 0, 1);
+    protected float innerAngle = 360;
+    protected float outerAngle = 360;
+    protected boolean positional = true;
+
+    /**
+     * <code>Status</code> indicates the current status of the audio node.
+     */
+    public enum Status {
+        /**
+         * The audio node is currently playing. This will be set if
+         * {@link AudioNode#play() } is called.
+         */
+        Playing,
+        
+        /**
+         * The audio node is currently paused.
+         */
+        Paused,
+        
+        /**
+         * The audio node is currently stopped.
+         * This will be set if {@link AudioNode#stop() } is called 
+         * or the audio has reached the end of the file.
+         */
+        Stopped,
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> without any audio data set.
+     */
+    public AudioNode() {
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> without any audio data set.
+     * 
+     * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+     *
+     * @deprecated AudioRenderer parameter is ignored.
+     */
+    public AudioNode(AudioRenderer audioRenderer) {
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given data and key.
+     * 
+     * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+     * @param audioData The audio data contains the audio track to play.
+     * @param audioKey The audio key that was used to load the AudioData
+     *
+     * @deprecated AudioRenderer parameter is ignored.
+     */
+    public AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey audioKey) {
+        setAudioData(audioData, audioKey);
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given data and key.
+     * 
+     * @param audioData The audio data contains the audio track to play.
+     * @param audioKey The audio key that was used to load the AudioData
+     */
+    public AudioNode(AudioData audioData, AudioKey audioKey) {
+        setAudioData(audioData, audioKey);
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * @param stream If true, the audio will be streamed gradually from disk, 
+     *               otherwise, it will be buffered.
+     * @param streamCache If stream is also true, then this specifies if
+     * the stream cache is used. When enabled, the audio stream will
+     * be read entirely but not decoded, allowing features such as 
+     * seeking, looping and determining duration.
+     *
+     * @deprecated AudioRenderer parameter is ignored.
+     */
+    public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream, boolean streamCache) {
+        this.audioKey = new AudioKey(name, stream, streamCache);
+        this.data = (AudioData) assetManager.loadAsset(audioKey);
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * @param stream If true, the audio will be streamed gradually from disk, 
+     *               otherwise, it will be buffered.
+     * @param streamCache If stream is also true, then this specifies if
+     * the stream cache is used. When enabled, the audio stream will
+     * be read entirely but not decoded, allowing features such as 
+     * seeking, looping and determining duration.
+     */
+    public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
+        this.audioKey = new AudioKey(name, stream, streamCache);
+        this.data = (AudioData) assetManager.loadAsset(audioKey);
+    }
+    
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * @param stream If true, the audio will be streamed gradually from disk, 
+     *               otherwise, it will be buffered.
+     *
+     * @deprecated AudioRenderer parameter is ignored.
+     */
+    public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream) {
+        this(audioRenderer, assetManager, name, stream, false);
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * @param stream If true, the audio will be streamed gradually from disk, 
+     *               otherwise, it will be buffered.
+     */
+    public AudioNode(AssetManager assetManager, String name, boolean stream) {
+        this(assetManager, name, stream, false);
+    }
+
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     * 
+     * @deprecated AudioRenderer parameter is ignored.
+     */
+    public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
+        this(assetManager, name, false);
+    }
+    
+    /**
+     * Creates a new <code>AudioNode</code> with the given audio file.
+     * 
+     * @param assetManager The asset manager to use to load the audio file
+     * @param name The filename of the audio file
+     */
+    public AudioNode(AssetManager assetManager, String name) {
+        this(assetManager, name, false);
+    }
+    
+    protected AudioRenderer getRenderer() {
+        AudioRenderer result = AudioContext.getAudioRenderer();
+        if( result == null )
+            throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
+        return result;            
+    }
+    
+    /**
+     * Start playing the audio.
+     */
+    public void play(){
+        getRenderer().playSource(this);
+    }
+
+    /**
+     * Start playing an instance of this audio. This method can be used
+     * to play the same <code>AudioNode</code> multiple times. Note
+     * that changes to the parameters of this AudioNode will not effect the 
+     * instances already playing.
+     */
+    public void playInstance(){
+        getRenderer().playSourceInstance(this);
+    }
+    
+    /**
+     * Stop playing the audio that was started with {@link AudioNode#play() }.
+     */
+    public void stop(){
+        getRenderer().stopSource(this);
+    }
+    
+    /**
+     * Pause the audio that was started with {@link AudioNode#play() }.
+     */
+    public void pause(){
+        getRenderer().pauseSource(this);
+    }
+    
+    /**
+     * Do not use.
+     */
+    public final void setChannel(int channel) {
+        if (status != Status.Stopped) {
+            throw new IllegalStateException("Can only set source id when stopped");
+        }
+
+        this.channel = channel;
+    }
+
+    /**
+     * Do not use.
+     */
+    public int getChannel() {
+        return channel;
+    }
+
+    /**
+     * @return The {#link Filter dry filter} that is set.
+     * @see AudioNode#setDryFilter(com.jme3.audio.Filter) 
+     */
+    public Filter getDryFilter() {
+        return dryFilter;
+    }
+
+    /**
+     * Set the dry filter to use for this audio node.
+     * 
+     * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used, 
+     * the dry filter will only influence the "dry" portion of the audio, 
+     * e.g. not the reverberated parts of the AudioNode playing.
+     * 
+     * See the relevent documentation for the {@link Filter} to determine
+     * the effect.
+     * 
+     * @param dryFilter The filter to set, or null to disable dry filter.
+     */
+    public void setDryFilter(Filter dryFilter) {
+        this.dryFilter = dryFilter;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.DryFilter);
+    }
+
+    /**
+     * Set the audio data to use for the audio. Note that this method
+     * can only be called once, if for example the audio node was initialized
+     * without an {@link AudioData}.
+     * 
+     * @param audioData The audio data contains the audio track to play.
+     * @param audioKey The audio key that was used to load the AudioData
+     */
+    public void setAudioData(AudioData audioData, AudioKey audioKey) {
+        if (data != null) {
+            throw new IllegalStateException("Cannot change data once its set");
+        }
+
+        data = audioData;
+        this.audioKey = audioKey;
+    }
+
+    /**
+     * @return The {@link AudioData} set previously with 
+     * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) }
+     * or any of the constructors that initialize the audio data.
+     */
+    public AudioData getAudioData() {
+        return data;
+    }
+
+    /**
+     * @return The {@link Status} of the audio node. 
+     * The status will be changed when either the {@link AudioNode#play() }
+     * or {@link AudioNode#stop() } methods are called.
+     */
+    public Status getStatus() {
+        return status;
+    }
+
+    /**
+     * Do not use.
+     */
+    public final void setStatus(Status status) {
+        this.status = status;
+    }
+
+    /**
+     * @return True if the audio will keep looping after it is done playing,
+     * otherwise, false.
+     * @see AudioNode#setLooping(boolean)
+     */
+    public boolean isLooping() {
+        return loop;
+    }
+
+    /**
+     * Set the looping mode for the audio node. The default is false.
+     * 
+     * @param loop True if the audio should keep looping after it is done playing.
+     */
+    public void setLooping(boolean loop) {
+        this.loop = loop;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Looping);
+    }
+
+    /**
+     * @return The pitch of the audio, also the speed of playback.
+     * 
+     * @see AudioNode#setPitch(float) 
+     */
+    public float getPitch() {
+        return pitch;
+    }
+
+    /**
+     * Set the pitch of the audio, also the speed of playback.
+     * The value must be between 0.5 and 2.0.
+     * 
+     * @param pitch The pitch to set.
+     * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0.
+     */
+    public void setPitch(float pitch) {
+        if (pitch < 0.5f || pitch > 2.0f) {
+            throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0");
+        }
+
+        this.pitch = pitch;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Pitch);
+    }
+
+    /**
+     * @return The volume of this audio node.
+     * 
+     * @see AudioNode#setVolume(float)
+     */
+    public float getVolume() {
+        return volume;
+    }
+
+    /**
+     * Set the volume of this audio node.
+     * 
+     * The volume is specified as gain. 1.0 is the default.
+     * 
+     * @param volume The volume to set.
+     * @throws IllegalArgumentException If volume is negative
+     */
+    public void setVolume(float volume) {
+        if (volume < 0f) {
+            throw new IllegalArgumentException("Volume cannot be negative");
+        }
+
+        this.volume = volume;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Volume);
+    }
+
+    /**
+     * @return The time offset in seconds when the sound will start playing.
+     */
+    public float getTimeOffset() {
+        return timeOffset;
+    }
+
+    /**
+     * Set the time offset in seconds when the sound will start playing.
+     * 
+     * @param timeOffset The time offset
+     * @throws IllegalArgumentException If timeOffset is negative
+     */
+    public void setTimeOffset(float timeOffset) {
+        if (timeOffset < 0f) {
+            throw new IllegalArgumentException("Time offset cannot be negative");
+        }
+
+        this.timeOffset = timeOffset;
+        if (data instanceof AudioStream) {
+            System.out.println("request setTime");
+            ((AudioStream) data).setTime(timeOffset);
+        }else if(status == Status.Playing){
+            stop();
+            play();
+        }
+    }
+
+    /**
+     * @return The velocity of the audio node.
+     * 
+     * @see AudioNode#setVelocity(com.jme3.math.Vector3f)
+     */
+    public Vector3f getVelocity() {
+        return velocity;
+    }
+
+    /**
+     * Set the velocity of the audio node. The velocity is expected
+     * to be in meters. Does nothing if the audio node is not positional.
+     * 
+     * @param velocity The velocity to set.
+     * @see AudioNode#setPositional(boolean)
+     */
+    public void setVelocity(Vector3f velocity) {
+        this.velocity.set(velocity);
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Velocity);
+    }
+
+    /**
+     * @return True if reverb is enabled, otherwise false.
+     * 
+     * @see AudioNode#setReverbEnabled(boolean)
+     */
+    public boolean isReverbEnabled() {
+        return reverbEnabled;
+    }
+
+    /**
+     * Set to true to enable reverberation effects for this audio node.
+     * Does nothing if the audio node is not positional.
+     * <br/>
+     * When enabled, the audio environment set with 
+     * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) }
+     * will apply a reverb effect to the audio playing from this audio node.
+     * 
+     * @param reverbEnabled True to enable reverb.
+     */
+    public void setReverbEnabled(boolean reverbEnabled) {
+        this.reverbEnabled = reverbEnabled;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled);
+    }
+
+    /**
+     * @return Filter for the reverberations of this audio node.
+     * 
+     * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) 
+     */
+    public Filter getReverbFilter() {
+        return reverbFilter;
+    }
+
+    /**
+     * Set the reverb filter for this audio node.
+     * <br/>
+     * The reverb filter will influence the reverberations
+     * of the audio node playing. This only has an effect if
+     * reverb is enabled.
+     * 
+     * @param reverbFilter The reverb filter to set.
+     * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
+     */
+    public void setReverbFilter(Filter reverbFilter) {
+        this.reverbFilter = reverbFilter;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.ReverbFilter);
+    }
+
+    /**
+     * @return Max distance for this audio node.
+     * 
+     * @see AudioNode#setMaxDistance(float)
+     */
+    public float getMaxDistance() {
+        return maxDistance;
+    }
+
+    /**
+     * Set the maximum distance for the attenuation of the audio node.
+     * Does nothing if the audio node is not positional.
+     * <br/>
+     * The maximum distance is the distance beyond which the audio
+     * node will no longer be attenuated.  Normal attenuation is logarithmic
+     * from refDistance (it reduces by half when the distance doubles).
+     * Max distance sets where this fall-off stops and the sound will never
+     * get any quieter than at that distance.  If you want a sound to fall-off
+     * very quickly then set ref distance very short and leave this distance
+     * very long.
+     * 
+     * @param maxDistance The maximum playing distance.
+     * @throws IllegalArgumentException If maxDistance is negative
+     */
+    public void setMaxDistance(float maxDistance) {
+        if (maxDistance < 0) {
+            throw new IllegalArgumentException("Max distance cannot be negative");
+        }
+
+        this.maxDistance = maxDistance;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.MaxDistance);
+    }
+
+    /**
+     * @return The reference playing distance for the audio node.
+     * 
+     * @see AudioNode#setRefDistance(float) 
+     */
+    public float getRefDistance() {
+        return refDistance;
+    }
+
+    /**
+     * Set the reference playing distance for the audio node.
+     * Does nothing if the audio node is not positional.
+     * <br/>
+     * The reference playing distance is the distance at which the
+     * audio node will be exactly half of its volume.
+     * 
+     * @param refDistance The reference playing distance.
+     * @throws  IllegalArgumentException If refDistance is negative
+     */
+    public void setRefDistance(float refDistance) {
+        if (refDistance < 0) {
+            throw new IllegalArgumentException("Reference distance cannot be negative");
+        }
+
+        this.refDistance = refDistance;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.RefDistance);
+    }
+
+    /**
+     * @return True if the audio node is directional
+     * 
+     * @see AudioNode#setDirectional(boolean) 
+     */
+    public boolean isDirectional() {
+        return directional;
+    }
+
+    /**
+     * Set the audio node to be directional.
+     * Does nothing if the audio node is not positional.
+     * <br/>
+     * After setting directional, you should call 
+     * {@link AudioNode#setDirection(com.jme3.math.Vector3f) }
+     * to set the audio node's direction.
+     * 
+     * @param directional If the audio node is directional
+     */
+    public void setDirectional(boolean directional) {
+        this.directional = directional;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.IsDirectional);
+    }
+
+    /**
+     * @return The direction of this audio node.
+     * 
+     * @see AudioNode#setDirection(com.jme3.math.Vector3f)
+     */
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    /**
+     * Set the direction of this audio node.
+     * Does nothing if the audio node is not directional.
+     * 
+     * @param direction 
+     * @see AudioNode#setDirectional(boolean) 
+     */
+    public void setDirection(Vector3f direction) {
+        this.direction = direction;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Direction);
+    }
+
+    /**
+     * @return The directional audio node, cone inner angle.
+     * 
+     * @see AudioNode#setInnerAngle(float) 
+     */
+    public float getInnerAngle() {
+        return innerAngle;
+    }
+
+    /**
+     * Set the directional audio node cone inner angle.
+     * Does nothing if the audio node is not directional.
+     * 
+     * @param innerAngle The cone inner angle.
+     */
+    public void setInnerAngle(float innerAngle) {
+        this.innerAngle = innerAngle;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.InnerAngle);
+    }
+
+    /**
+     * @return The directional audio node, cone outer angle.
+     * 
+     * @see AudioNode#setOuterAngle(float) 
+     */
+    public float getOuterAngle() {
+        return outerAngle;
+    }
+
+    /**
+     * Set the directional audio node cone outer angle.
+     * Does nothing if the audio node is not directional.
+     * 
+     * @param outerAngle The cone outer angle.
+     */
+    public void setOuterAngle(float outerAngle) {
+        this.outerAngle = outerAngle;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.OuterAngle);
+    }
+
+    /**
+     * @return True if the audio node is positional.
+     * 
+     * @see AudioNode#setPositional(boolean) 
+     */
+    public boolean isPositional() {
+        return positional;
+    }
+
+    /**
+     * Set the audio node as positional.
+     * The position, velocity, and distance parameters effect positional
+     * audio nodes. Set to false if the audio node should play in "headspace".
+     * 
+     * @param positional True if the audio node should be positional, otherwise
+     * false if it should be headspace.
+     */
+    public void setPositional(boolean positional) {
+        this.positional = positional;
+        if (channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.IsPositional);
+    }
+
+    @Override
+    public void updateGeometricState(){
+        boolean updatePos = false;
+        if ((refreshFlags & RF_TRANSFORM) != 0){
+            updatePos = true;
+        }
+        
+        super.updateGeometricState();
+
+        if (updatePos && channel >= 0)
+            getRenderer().updateSourceParam(this, AudioParam.Position);
+    }
+
+    @Override
+    public AudioNode clone(){
+        AudioNode clone = (AudioNode) super.clone();
+        
+        clone.direction = direction.clone();
+        clone.velocity  = velocity.clone();
+        
+        return clone;
+    }
+    
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(audioKey, "audio_key", null);
+        oc.write(loop, "looping", false);
+        oc.write(volume, "volume", 1);
+        oc.write(pitch, "pitch", 1);
+        oc.write(timeOffset, "time_offset", 0);
+        oc.write(dryFilter, "dry_filter", null);
+
+        oc.write(velocity, "velocity", null);
+        oc.write(reverbEnabled, "reverb_enabled", false);
+        oc.write(reverbFilter, "reverb_filter", null);
+        oc.write(maxDistance, "max_distance", 20);
+        oc.write(refDistance, "ref_distance", 10);
+
+        oc.write(directional, "directional", false);
+        oc.write(direction, "direction", null);
+        oc.write(innerAngle, "inner_angle", 360);
+        oc.write(outerAngle, "outer_angle", 360);
+        
+        oc.write(positional, "positional", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        
+        // NOTE: In previous versions of jME3, audioKey was actually
+        // written with the name "key". This has been changed
+        // to "audio_key" in case Spatial's key will be written as "key".
+        if (ic.getSavableVersion(AudioNode.class) == 0){
+            audioKey = (AudioKey) ic.readSavable("key", null);
+        }else{
+            audioKey = (AudioKey) ic.readSavable("audio_key", null);
+        }
+        
+        loop = ic.readBoolean("looping", false);
+        volume = ic.readFloat("volume", 1);
+        pitch = ic.readFloat("pitch", 1);
+        timeOffset = ic.readFloat("time_offset", 0);
+        dryFilter = (Filter) ic.readSavable("dry_filter", null);
+
+        velocity = (Vector3f) ic.readSavable("velocity", null);
+        reverbEnabled = ic.readBoolean("reverb_enabled", false);
+        reverbFilter = (Filter) ic.readSavable("reverb_filter", null);
+        maxDistance = ic.readFloat("max_distance", 20);
+        refDistance = ic.readFloat("ref_distance", 10);
+
+        directional = ic.readBoolean("directional", false);
+        direction = (Vector3f) ic.readSavable("direction", null);
+        innerAngle = ic.readFloat("inner_angle", 360);
+        outerAngle = ic.readFloat("outer_angle", 360);
+        
+        positional = ic.readBoolean("positional", false);
+        
+        if (audioKey != null) {
+            try {
+                data = im.getAssetManager().loadAudio(audioKey);
+            } catch (AssetNotFoundException ex){
+                Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key});
+                data = PlaceholderAssets.getPlaceholderAudio();
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        String ret = getClass().getSimpleName()
+                + "[status=" + status;
+        if (volume != 1f) {
+            ret += ", vol=" + volume;
+        }
+        if (pitch != 1f) {
+            ret += ", pitch=" + pitch;
+        }
+        return ret + "]";
+    }
+}
diff --git a/engine/src/core/com/jme3/audio/AudioParam.java b/engine/src/core/com/jme3/audio/AudioParam.java
new file mode 100644
index 0000000..bf53c24
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioParam.java
@@ -0,0 +1,19 @@
+package com.jme3.audio;
+
+public enum AudioParam {
+    Volume,
+    Pitch,
+    Looping,
+    Position,
+    IsPositional,
+    Direction,
+    IsDirectional,
+    Velocity,
+    OuterAngle,
+    InnerAngle,
+    RefDistance,
+    MaxDistance,
+    DryFilter,
+    ReverbFilter,
+    ReverbEnabled;
+}
diff --git a/engine/src/core/com/jme3/audio/AudioRenderer.java b/engine/src/core/com/jme3/audio/AudioRenderer.java
new file mode 100644
index 0000000..15ad9c9
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioRenderer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+/**
+ * Interface to be implemented by audio renderers.
+ *
+ * @author Kirill Vainer
+ */
+public interface AudioRenderer {
+
+    /**
+     * @param listener The listener camera, all 3D sounds will be
+     * oriented around the listener.
+     */
+    public void setListener(Listener listener);
+
+    /**
+     * Sets the environment, used for reverb effects.
+     *
+     * @see AudioNode#setReverbEnabled(boolean)
+     * @param env The environment to set.
+     */
+    public void setEnvironment(Environment env);
+
+    public void playSourceInstance(AudioNode src);
+    public void playSource(AudioNode src);
+    public void pauseSource(AudioNode src);
+    public void stopSource(AudioNode src);
+
+    public void updateSourceParam(AudioNode src, AudioParam param);
+    public void updateListenerParam(Listener listener, ListenerParam param);
+
+    public void deleteFilter(Filter filter);
+    public void deleteAudioData(AudioData ad);
+
+    /**
+     * Initializes the renderer. Should be the first method called
+     * before using the system.
+     */
+    public void initialize();
+
+    /**
+     * Update the audio system. Must be called periodically.
+     * @param tpf Time per frame.
+     */
+    public void update(float tpf);
+
+    /**
+     * Cleanup/destroy the audio system. Call this when app closes.
+     */
+    public void cleanup();
+}
diff --git a/engine/src/core/com/jme3/audio/AudioStream.java b/engine/src/core/com/jme3/audio/AudioStream.java
new file mode 100644
index 0000000..6d20d67
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioStream.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.util.NativeObject;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AudioStream</code> is an implementation of AudioData that
+ * acquires the audio from an InputStream. Audio can be streamed
+ * from network, hard drive etc. It is assumed the data coming
+ * from the input stream is uncompressed.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioStream extends AudioData implements Closeable{
+
+    private final static Logger logger = Logger.getLogger(AudioStream.class.getName());
+    protected InputStream in;
+    protected float duration = -1f;
+    protected boolean open = false;
+    protected int[] ids;
+    
+    public AudioStream(){
+        super();        
+    }
+    
+    protected AudioStream(int[] ids){
+        // Pass some dummy ID so handle
+        // doesn't get created.
+        super(-1);      
+        // This is what gets destroyed in reality
+        this.ids = ids;
+    }
+
+    public void updateData(InputStream in, float duration){
+        if (id != -1 || this.in != null)
+            throw new IllegalStateException("Data already set!");
+
+        this.in = in;
+        this.duration = duration;
+        open = true;
+    }
+
+    /**
+     * Reads samples from the stream. The format of the data
+     * depends on the getSampleRate(), getChannels(), getBitsPerSample()
+     * values.
+     *
+     * @param buf Buffer where to read the samples
+     * @param offset The offset in the buffer where to read samples
+     * @param length The length inside the buffer where to read samples
+     * @return number of bytes read.
+     */
+    public int readSamples(byte[] buf, int offset, int length){
+        if (!open)
+            return -1;
+
+        try{
+            return in.read(buf, offset, length);
+        }catch (IOException ex){
+            return -1;
+        }
+    }
+
+    /**
+     * Reads samples from the stream.
+     *
+     * @see AudioStream#readSamples(byte[], int, int)
+     * @param buf Buffer where to read the samples
+     * @return number of bytes read.
+     */
+    public int readSamples(byte[] buf){
+        return readSamples(buf, 0, buf.length);
+    }
+
+    public float getDuration(){
+        return duration;
+    }
+
+    @Override
+    public int getId(){
+        throw new RuntimeException("Don't use getId() on streams");
+    }
+
+    @Override
+    public void setId(int id){
+        throw new RuntimeException("Don't use setId() on streams");
+    }
+
+    public void initIds(int count){
+        ids = new int[count];
+    }
+
+    public int getId(int index){
+        return ids[index];
+    }
+
+    public void setId(int index, int id){
+        ids[index] = id;
+    }
+
+    public int[] getIds(){
+        return ids;
+    }
+
+    public void setIds(int[] ids){
+        this.ids = ids;
+    }
+
+    @Override
+    public DataType getDataType() {
+        return DataType.Stream;
+    }
+
+    @Override
+    public void resetObject() {
+        id = -1;
+        ids = null;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        // It seems that the audio renderer is already doing a good
+        // job at deleting audio streams when they finish playing.
+//        ((AudioRenderer)rendererObject).deleteAudioData(this);
+    }
+
+    @Override
+    public NativeObject createDestructableClone() {
+        return new AudioStream(ids);
+    }
+    
+    /**
+     * @return Whether the stream is open or not. Reading from a closed
+     * stream will always return eof.
+     */
+    public boolean isOpen(){
+        return open;
+    }
+
+    /**
+     * Closes the stream, releasing all data relating to it. Reading
+     * from the stream will return eof.
+     * @throws IOException
+     */
+    public void close() {
+        if (in != null && open){
+            try{
+                in.close();
+            }catch (IOException ex){
+            }
+            open = false;
+        }else{
+            throw new RuntimeException("AudioStream is already closed!");
+        }
+    }
+
+  
+    public void setTime(float time){
+        if(in instanceof SeekableStream){
+            ((SeekableStream)in).setTime(time);
+        }else{
+            logger.log(Level.WARNING,"Cannot use setTime on a stream that is not seekable. You must load the file with the streamCache option set to true");
+        }
+    }
+
+    
+}
diff --git a/engine/src/core/com/jme3/audio/Environment.java b/engine/src/core/com/jme3/audio/Environment.java
new file mode 100644
index 0000000..9a1279e
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Environment.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.math.FastMath;
+
+/**
+ * Audio environment, for reverb effects.
+ * @author Kirill
+ */
+public class Environment {
+
+    private float airAbsorbGainHf   = 0.99426f;
+    private float roomRolloffFactor = 0;
+
+    private float decayTime         = 1.49f;
+    private float decayHFRatio      = 0.54f;
+
+    private float density           = 1.0f;
+    private float diffusion         = 0.3f;
+
+    private float gain              = 0.316f;
+    private float gainHf            = 0.022f;
+
+    private float lateReverbDelay   = 0.088f;
+    private float lateReverbGain    = 0.768f;
+
+    private float reflectDelay      = 0.162f;
+    private float reflectGain       = 0.052f;
+
+    private boolean decayHfLimit    = true;
+
+    public static final Environment Garage, Dungeon, Cavern, AcousticLab, Closet;
+
+    static {
+        Garage = new Environment(1, 1, 1, 1, .9f, .5f, .751f, .0039f, .661f, .0137f);
+        Dungeon = new Environment(.75f, 1, 1, .75f, 1.6f, 1, 0.95f, 0.0026f, 0.93f, 0.0103f);
+        Cavern = new Environment(.5f, 1, 1, .5f, 2.25f, 1, .908f, .0103f, .93f, .041f);
+        AcousticLab = new Environment(.5f, 1, 1, 1, .28f, 1, .87f, .002f, .81f, .008f);
+        Closet = new Environment(1, 1, 1, 1, .15f, 1, .6f, .0025f, .5f, .0006f);
+    }
+
+    private static final float eaxDbToAmp(float eaxDb){
+        float dB = eaxDb / 2000f;
+        return FastMath.pow(10f, dB);
+    }
+
+    public Environment(){
+    }
+
+    public Environment(Environment source) {
+        this.airAbsorbGainHf = source.airAbsorbGainHf;
+        this.roomRolloffFactor = source.roomRolloffFactor;
+        this.decayTime = source.decayTime;
+        this.decayHFRatio = source.decayHFRatio;
+        this.density = source.density;
+        this.diffusion = source.diffusion;
+        this.gain = source.gain;
+        this.gainHf = source.gainHf;
+        this.lateReverbDelay = source.lateReverbDelay;
+        this.lateReverbGain = source.lateReverbGain;
+        this.reflectDelay = source.reflectDelay;
+        this.reflectGain = source.reflectGain;
+        this.decayHfLimit = source.decayHfLimit;
+    }
+
+    public Environment(float density, float diffusion, float gain, float gainHf,
+                       float decayTime, float decayHf, float reflGain,
+                       float reflDelay, float lateGain, float lateDelay){
+        this.decayTime = decayTime;
+        this.decayHFRatio = decayHf;
+        this.density = density;
+        this.diffusion = diffusion;
+        this.gain = gain;
+        this.gainHf = gainHf;
+        this.lateReverbDelay = lateDelay;
+        this.lateReverbGain = lateGain;
+        this.reflectDelay = reflDelay;
+        this.reflectGain = reflGain;
+    }
+
+    public Environment(float[] e){
+        if (e.length != 28)
+            throw new IllegalArgumentException("Not an EAX preset");
+
+        // skip env id
+        // e[0]
+        // skip room size
+        // e[1]
+
+//        density = 0;
+        diffusion = e[2];
+        gain = eaxDbToAmp(e[3]); // convert
+        gainHf = eaxDbToAmp(e[4]) / eaxDbToAmp(e[5]); // convert
+        decayTime = e[6];
+        decayHFRatio = e[7] / e[8];
+        reflectGain = eaxDbToAmp(e[9]); // convert
+        reflectDelay = e[10];
+
+        // skip 3 pan values
+        // e[11] e[12] e[13]
+
+        lateReverbGain = eaxDbToAmp(e[14]); // convert
+        lateReverbDelay = e[15];
+
+        // skip 3 pan values
+        // e[16] e[17] e[18]
+
+        // skip echo time, echo damping, mod time, mod damping
+        // e[19] e[20] e[21] e[22]
+
+        airAbsorbGainHf = eaxDbToAmp(e[23]);
+
+        // skip HF Reference and LF Reference
+        // e[24] e[25]
+
+        roomRolloffFactor = e[26];
+
+        // skip flags
+        // e[27]
+    }
+
+    public float getAirAbsorbGainHf() {
+        return airAbsorbGainHf;
+    }
+
+    public void setAirAbsorbGainHf(float airAbsorbGainHf) {
+        this.airAbsorbGainHf = airAbsorbGainHf;
+    }
+
+    public float getDecayHFRatio() {
+        return decayHFRatio;
+    }
+
+    public void setDecayHFRatio(float decayHFRatio) {
+        this.decayHFRatio = decayHFRatio;
+    }
+
+    public boolean isDecayHfLimit() {
+        return decayHfLimit;
+    }
+
+    public void setDecayHfLimit(boolean decayHfLimit) {
+        this.decayHfLimit = decayHfLimit;
+    }
+
+    public float getDecayTime() {
+        return decayTime;
+    }
+
+    public void setDecayTime(float decayTime) {
+        this.decayTime = decayTime;
+    }
+
+    public float getDensity() {
+        return density;
+    }
+
+    public void setDensity(float density) {
+        this.density = density;
+    }
+
+    public float getDiffusion() {
+        return diffusion;
+    }
+
+    public void setDiffusion(float diffusion) {
+        this.diffusion = diffusion;
+    }
+
+    public float getGain() {
+        return gain;
+    }
+
+    public void setGain(float gain) {
+        this.gain = gain;
+    }
+
+    public float getGainHf() {
+        return gainHf;
+    }
+
+    public void setGainHf(float gainHf) {
+        this.gainHf = gainHf;
+    }
+
+    public float getLateReverbDelay() {
+        return lateReverbDelay;
+    }
+
+    public void setLateReverbDelay(float lateReverbDelay) {
+        this.lateReverbDelay = lateReverbDelay;
+    }
+
+    public float getLateReverbGain() {
+        return lateReverbGain;
+    }
+
+    public void setLateReverbGain(float lateReverbGain) {
+        this.lateReverbGain = lateReverbGain;
+    }
+
+    public float getReflectDelay() {
+        return reflectDelay;
+    }
+
+    public void setReflectDelay(float reflectDelay) {
+        this.reflectDelay = reflectDelay;
+    }
+
+    public float getReflectGain() {
+        return reflectGain;
+    }
+
+    public void setReflectGain(float reflectGain) {
+        this.reflectGain = reflectGain;
+    }
+
+    public float getRoomRolloffFactor() {
+        return roomRolloffFactor;
+    }
+
+    public void setRoomRolloffFactor(float roomRolloffFactor) {
+        this.roomRolloffFactor = roomRolloffFactor;
+    }
+}
diff --git a/engine/src/core/com/jme3/audio/Filter.java b/engine/src/core/com/jme3/audio/Filter.java
new file mode 100644
index 0000000..da231d6
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Filter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+
+public abstract class Filter extends NativeObject implements Savable {
+
+    public Filter(){
+        super(Filter.class);
+    }
+    
+    protected Filter(int id){
+        super(Filter.class, id);
+    }
+    
+    public void write(JmeExporter ex) throws IOException {
+        // nothing to save
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        // nothing to read
+    }
+
+    @Override
+    public void resetObject() {
+        this.id = -1;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((AudioRenderer)rendererObject).deleteFilter(this);
+    }
+
+    @Override
+    public abstract NativeObject createDestructableClone();
+
+}
diff --git a/engine/src/core/com/jme3/audio/Listener.java b/engine/src/core/com/jme3/audio/Listener.java
new file mode 100644
index 0000000..609b0ca
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Listener.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+
+public class Listener {
+
+    private Vector3f location;
+    private Vector3f velocity;
+    private Quaternion rotation;
+    private float volume = 1;
+    private AudioRenderer renderer;
+
+    public Listener(){
+        location = new Vector3f();
+        velocity = new Vector3f();
+        rotation = new Quaternion();
+    }
+    
+    public Listener(Listener source){
+        location = source.location.clone();
+        velocity = source.velocity.clone();
+        rotation = source.rotation.clone();
+        volume = source.volume;
+    }
+
+    public void setRenderer(AudioRenderer renderer){
+        this.renderer = renderer;
+    }
+
+    public float getVolume() {
+        return volume;
+    }
+
+    public void setVolume(float volume) {
+        this.volume = volume;
+        if (renderer != null)
+            renderer.updateListenerParam(this, ListenerParam.Volume);
+    }
+    
+    public Vector3f getLocation() {
+        return location;
+    }
+
+    public Quaternion getRotation() {
+        return rotation;
+    }
+
+    public Vector3f getVelocity() {
+        return velocity;
+    }
+
+    public Vector3f getLeft(){
+        return rotation.getRotationColumn(0);
+    }
+
+    public Vector3f getUp(){
+        return rotation.getRotationColumn(1);
+    }
+
+    public Vector3f getDirection(){
+        return rotation.getRotationColumn(2);
+    }
+    
+    public void setLocation(Vector3f location) {
+        this.location.set(location);
+        if (renderer != null)
+            renderer.updateListenerParam(this, ListenerParam.Position);
+    }
+
+    public void setRotation(Quaternion rotation) {
+        this.rotation.set(rotation);
+        if (renderer != null)
+            renderer.updateListenerParam(this, ListenerParam.Rotation);
+    }
+
+    public void setVelocity(Vector3f velocity) {
+        this.velocity.set(velocity);
+        if (renderer != null)
+            renderer.updateListenerParam(this, ListenerParam.Velocity);
+    }
+}
diff --git a/engine/src/core/com/jme3/audio/ListenerParam.java b/engine/src/core/com/jme3/audio/ListenerParam.java
new file mode 100644
index 0000000..6b3b55e
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/ListenerParam.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+public enum ListenerParam {
+    Position,
+    Velocity,
+    Rotation,
+    Volume;
+}
diff --git a/engine/src/core/com/jme3/audio/LowPassFilter.java b/engine/src/core/com/jme3/audio/LowPassFilter.java
new file mode 100644
index 0000000..58242cf
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/LowPassFilter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+
+public class LowPassFilter extends Filter {
+
+    protected float volume, highFreqVolume;
+
+    public LowPassFilter(float volume, float highFreqVolume) {
+        super();
+        setVolume(volume);
+        setHighFreqVolume(highFreqVolume);
+    }
+    
+    protected LowPassFilter(int id){
+        super(id);
+    }
+
+    public float getHighFreqVolume() {
+        return highFreqVolume;
+    }
+
+    public void setHighFreqVolume(float highFreqVolume) {
+        if (highFreqVolume < 0 || highFreqVolume > 1)
+            throw new IllegalArgumentException("High freq volume must be between 0 and 1");
+
+        this.highFreqVolume = highFreqVolume;
+        this.updateNeeded = true;
+    }
+
+    public float getVolume() {
+        return volume;
+    }
+
+    public void setVolume(float volume) {
+        if (volume < 0 || volume > 1)
+            throw new IllegalArgumentException("Volume must be between 0 and 1");
+        
+        this.volume = volume;
+        this.updateNeeded = true;
+    }
+
+    public void write(JmeExporter ex) throws IOException{
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(volume, "volume", 0);
+        oc.write(highFreqVolume, "hf_volume", 0);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException{
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        volume = ic.readFloat("volume", 0);
+        highFreqVolume = ic.readFloat("hf_volume", 0);
+    }
+
+    @Override
+    public NativeObject createDestructableClone() {
+        return new LowPassFilter(id);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/audio/SeekableStream.java b/engine/src/core/com/jme3/audio/SeekableStream.java
new file mode 100644
index 0000000..bb5b9e4
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/SeekableStream.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.audio;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface SeekableStream{
+    
+    public void setTime(float time);
+    
+}
diff --git a/engine/src/core/com/jme3/bounding/BoundingBox.java b/engine/src/core/com/jme3/bounding/BoundingBox.java
new file mode 100644
index 0000000..bc11411
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingBox.java
@@ -0,0 +1,977 @@
+/*

+ * Copyright (c) 2009-2010 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.bounding;

+

+import com.jme3.collision.Collidable;

+import com.jme3.collision.CollisionResult;

+import com.jme3.collision.CollisionResults;

+import com.jme3.collision.UnsupportedCollisionException;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.*;

+import com.jme3.scene.Mesh;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+//import com.jme.scene.TriMesh;

+

+/**

+ * <code>BoundingBox</code> defines an axis-aligned cube that defines a

+ * container for a group of vertices of a particular piece of geometry. This box

+ * defines a center and extents from that center along the x, y and z axis. <br>

+ * <br>

+ * A typical usage is to allow the class define the center and radius by calling

+ * either <code>containAABB</code> or <code>averagePoints</code>. A call to

+ * <code>computeFramePoint</code> in turn calls <code>containAABB</code>.

+ * 

+ * @author Joshua Slack

+ * @version $Id: BoundingBox.java,v 1.50 2007/09/22 16:46:35 irrisor Exp $

+ */

+public class BoundingBox extends BoundingVolume {

+

+    float xExtent, yExtent, zExtent;

+

+    /**

+     * Default constructor instantiates a new <code>BoundingBox</code>

+     * object.

+     */

+    public BoundingBox() {

+    }

+

+    /**

+     * Contstructor instantiates a new <code>BoundingBox</code> object with

+     * given specs.

+     */

+    public BoundingBox(Vector3f c, float x, float y, float z) {

+        this.center.set(c);

+        this.xExtent = x;

+        this.yExtent = y;

+        this.zExtent = z;

+    }

+

+    public BoundingBox(BoundingBox source) {

+        this.center.set(source.center);

+        this.xExtent = source.xExtent;

+        this.yExtent = source.yExtent;

+        this.zExtent = source.zExtent;

+    }

+

+    public BoundingBox(Vector3f min, Vector3f max) {

+        setMinMax(min, max);

+    }

+

+    public Type getType() {

+        return Type.AABB;

+    }

+

+    /**

+     * <code>computeFromPoints</code> creates a new Bounding Box from a given

+     * set of points. It uses the <code>containAABB</code> method as default.

+     * 

+     * @param points

+     *            the points to contain.

+     */

+    public void computeFromPoints(FloatBuffer points) {

+        containAABB(points);

+    }

+

+    /**

+     * <code>computeFromTris</code> creates a new Bounding Box from a given

+     * set of triangles. It is used in OBBTree calculations.

+     * 

+     * @param tris

+     * @param start

+     * @param end

+     */

+    public void computeFromTris(Triangle[] tris, int start, int end) {

+        if (end - start <= 0) {

+            return;

+        }

+

+        TempVars vars = TempVars.get();

+

+        Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));

+        Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));

+

+        Vector3f point;

+        for (int i = start; i < end; i++) {

+            point = tris[i].get(0);

+            checkMinMax(min, max, point);

+            point = tris[i].get(1);

+            checkMinMax(min, max, point);

+            point = tris[i].get(2);

+            checkMinMax(min, max, point);

+        }

+

+        center.set(min.addLocal(max));

+        center.multLocal(0.5f);

+

+        xExtent = max.x - center.x;

+        yExtent = max.y - center.y;

+        zExtent = max.z - center.z;

+

+        vars.release();

+    }

+

+    public void computeFromTris(int[] indices, Mesh mesh, int start, int end) {

+        if (end - start <= 0) {

+            return;

+        }

+

+        TempVars vars = TempVars.get();

+

+        Vector3f vect1 = vars.vect1;

+        Vector3f vect2 = vars.vect2;

+        Triangle triangle = vars.triangle;

+

+        Vector3f min = vect1.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);

+        Vector3f max = vect2.set(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);

+        Vector3f point;

+

+        for (int i = start; i < end; i++) {

+            mesh.getTriangle(indices[i], triangle);

+            point = triangle.get(0);

+            checkMinMax(min, max, point);

+            point = triangle.get(1);

+            checkMinMax(min, max, point);

+            point = triangle.get(2);

+            checkMinMax(min, max, point);

+        }

+

+        center.set(min.addLocal(max));

+        center.multLocal(0.5f);

+

+        xExtent = max.x - center.x;

+        yExtent = max.y - center.y;

+        zExtent = max.z - center.z;

+

+        vars.release();

+    }

+

+    public static void checkMinMax(Vector3f min, Vector3f max, Vector3f point) {

+        if (point.x < min.x) {

+            min.x = point.x;

+        }

+        if (point.x > max.x) {

+            max.x = point.x;

+        }

+        if (point.y < min.y) {

+            min.y = point.y;

+        }

+        if (point.y > max.y) {

+            max.y = point.y;

+        }

+        if (point.z < min.z) {

+            min.z = point.z;

+        }

+        if (point.z > max.z) {

+            max.z = point.z;

+        }

+    }

+

+    /**

+     * <code>containAABB</code> creates a minimum-volume axis-aligned bounding

+     * box of the points, then selects the smallest enclosing sphere of the box

+     * with the sphere centered at the boxes center.

+     * 

+     * @param points

+     *            the list of points.

+     */

+    public void containAABB(FloatBuffer points) {

+        if (points == null) {

+            return;

+        }

+

+        points.rewind();

+        if (points.remaining() <= 2) // we need at least a 3 float vector

+        {

+            return;

+        }

+

+        TempVars vars = TempVars.get();

+

+        BufferUtils.populateFromBuffer(vars.vect1, points, 0);

+        float minX = vars.vect1.x, minY = vars.vect1.y, minZ = vars.vect1.z;

+        float maxX = vars.vect1.x, maxY = vars.vect1.y, maxZ = vars.vect1.z;

+

+        for (int i = 1, len = points.remaining() / 3; i < len; i++) {

+            BufferUtils.populateFromBuffer(vars.vect1, points, i);

+

+            if (vars.vect1.x < minX) {

+                minX = vars.vect1.x;

+            } else if (vars.vect1.x > maxX) {

+                maxX = vars.vect1.x;

+            }

+

+            if (vars.vect1.y < minY) {

+                minY = vars.vect1.y;

+            } else if (vars.vect1.y > maxY) {

+                maxY = vars.vect1.y;

+            }

+

+            if (vars.vect1.z < minZ) {

+                minZ = vars.vect1.z;

+            } else if (vars.vect1.z > maxZ) {

+                maxZ = vars.vect1.z;

+            }

+        }

+

+        vars.release();

+

+        center.set(minX + maxX, minY + maxY, minZ + maxZ);

+        center.multLocal(0.5f);

+

+        xExtent = maxX - center.x;

+        yExtent = maxY - center.y;

+        zExtent = maxZ - center.z;

+    }

+

+    /**

+     * <code>transform</code> modifies the center of the box to reflect the

+     * change made via a rotation, translation and scale.

+     * 

+     * @param trans 

+     *            the transform to apply

+     * @param store

+     *            box to store result in

+     */

+    public BoundingVolume transform(Transform trans, BoundingVolume store) {

+

+        BoundingBox box;

+        if (store == null || store.getType() != Type.AABB) {

+            box = new BoundingBox();

+        } else {

+            box = (BoundingBox) store;

+        }

+

+        center.mult(trans.getScale(), box.center);

+        trans.getRotation().mult(box.center, box.center);

+        box.center.addLocal(trans.getTranslation());

+

+        TempVars vars = TempVars.get();

+

+        Matrix3f transMatrix = vars.tempMat3;

+        transMatrix.set(trans.getRotation());

+        // Make the rotation matrix all positive to get the maximum x/y/z extent

+        transMatrix.absoluteLocal();

+

+        Vector3f scale = trans.getScale();

+        vars.vect1.set(xExtent * scale.x, yExtent * scale.y, zExtent * scale.z);

+        transMatrix.mult(vars.vect1, vars.vect2);

+        // Assign the biggest rotations after scales.

+        box.xExtent = FastMath.abs(vars.vect2.getX());

+        box.yExtent = FastMath.abs(vars.vect2.getY());

+        box.zExtent = FastMath.abs(vars.vect2.getZ());

+

+        vars.release();

+

+        return box;

+    }

+

+    public BoundingVolume transform(Matrix4f trans, BoundingVolume store) {

+        BoundingBox box;

+        if (store == null || store.getType() != Type.AABB) {

+            box = new BoundingBox();

+        } else {

+            box = (BoundingBox) store;

+        }

+        TempVars vars = TempVars.get();

+

+

+        float w = trans.multProj(center, box.center);

+        box.center.divideLocal(w);

+

+        Matrix3f transMatrix = vars.tempMat3;

+        trans.toRotationMatrix(transMatrix);

+

+        // Make the rotation matrix all positive to get the maximum x/y/z extent

+        transMatrix.absoluteLocal();

+

+        vars.vect1.set(xExtent, yExtent, zExtent);

+        transMatrix.mult(vars.vect1, vars.vect1);

+

+        // Assign the biggest rotations after scales.

+        box.xExtent = FastMath.abs(vars.vect1.getX());

+        box.yExtent = FastMath.abs(vars.vect1.getY());

+        box.zExtent = FastMath.abs(vars.vect1.getZ());

+

+        vars.release();

+

+        return box;

+    }

+

+    /**

+     * <code>whichSide</code> takes a plane (typically provided by a view

+     * frustum) to determine which side this bound is on.

+     * 

+     * @param plane

+     *            the plane to check against.

+     */

+    public Plane.Side whichSide(Plane plane) {

+        float radius = FastMath.abs(xExtent * plane.getNormal().getX())

+                + FastMath.abs(yExtent * plane.getNormal().getY())

+                + FastMath.abs(zExtent * plane.getNormal().getZ());

+

+        float distance = plane.pseudoDistance(center);

+

+        //changed to < and > to prevent floating point precision problems

+        if (distance < -radius) {

+            return Plane.Side.Negative;

+        } else if (distance > radius) {

+            return Plane.Side.Positive;

+        } else {

+            return Plane.Side.None;

+        }

+    }

+

+    /**

+     * <code>merge</code> combines this sphere with a second bounding sphere.

+     * This new sphere contains both bounding spheres and is returned.

+     * 

+     * @param volume

+     *            the sphere to combine with this sphere.

+     * @return the new sphere

+     */

+    public BoundingVolume merge(BoundingVolume volume) {

+        if (volume == null) {

+            return this;

+        }

+

+        switch (volume.getType()) {

+            case AABB: {

+                BoundingBox vBox = (BoundingBox) volume;

+                return merge(vBox.center, vBox.xExtent, vBox.yExtent,

+                        vBox.zExtent, new BoundingBox(new Vector3f(0, 0, 0), 0,

+                        0, 0));

+            }

+

+            case Sphere: {

+                BoundingSphere vSphere = (BoundingSphere) volume;

+                return merge(vSphere.center, vSphere.radius, vSphere.radius,

+                        vSphere.radius, new BoundingBox(new Vector3f(0, 0, 0),

+                        0, 0, 0));

+            }

+

+//            case OBB: {

+//                OrientedBoundingBox box = (OrientedBoundingBox) volume;

+//                BoundingBox rVal = (BoundingBox) this.clone(null);

+//                return rVal.mergeOBB(box);

+//            }

+

+            default:

+                return null;

+        }

+    }

+

+    /**

+     * <code>mergeLocal</code> combines this sphere with a second bounding

+     * sphere locally. Altering this sphere to contain both the original and the

+     * additional sphere volumes;

+     * 

+     * @param volume

+     *            the sphere to combine with this sphere.

+     * @return this

+     */

+    public BoundingVolume mergeLocal(BoundingVolume volume) {

+        if (volume == null) {

+            return this;

+        }

+

+        switch (volume.getType()) {

+            case AABB: {

+                BoundingBox vBox = (BoundingBox) volume;

+                return merge(vBox.center, vBox.xExtent, vBox.yExtent,

+                        vBox.zExtent, this);

+            }

+

+            case Sphere: {

+                BoundingSphere vSphere = (BoundingSphere) volume;

+                return merge(vSphere.center, vSphere.radius, vSphere.radius,

+                        vSphere.radius, this);

+            }

+

+//            case OBB: {

+//                return mergeOBB((OrientedBoundingBox) volume);

+//            }

+

+            default:

+                return null;

+        }

+    }

+

+    /**

+     * Merges this AABB with the given OBB.

+     * 

+     * @param volume

+     *            the OBB to merge this AABB with.

+     * @return This AABB extended to fit the given OBB.

+     */

+//    private BoundingBox mergeOBB(OrientedBoundingBox volume) {

+//        if (!volume.correctCorners)

+//            volume.computeCorners();

+//

+//        TempVars vars = TempVars.get();

+//        Vector3f min = vars.compVect1.set(center.x - xExtent, center.y - yExtent,

+//                center.z - zExtent);

+//        Vector3f max = vars.compVect2.set(center.x + xExtent, center.y + yExtent,

+//                center.z + zExtent);

+//

+//        for (int i = 1; i < volume.vectorStore.length; i++) {

+//            Vector3f temp = volume.vectorStore[i];

+//            if (temp.x < min.x)

+//                min.x = temp.x;

+//            else if (temp.x > max.x)

+//                max.x = temp.x;

+//

+//            if (temp.y < min.y)

+//                min.y = temp.y;

+//            else if (temp.y > max.y)

+//                max.y = temp.y;

+//

+//            if (temp.z < min.z)

+//                min.z = temp.z;

+//            else if (temp.z > max.z)

+//                max.z = temp.z;

+//        }

+//

+//        center.set(min.addLocal(max));

+//        center.multLocal(0.5f);

+//

+//        xExtent = max.x - center.x;

+//        yExtent = max.y - center.y;

+//        zExtent = max.z - center.z;

+//        return this;

+//    }

+    /**

+     * <code>merge</code> combines this bounding box with another box which is

+     * defined by the center, x, y, z extents.

+     * 

+     * @param boxCenter

+     *            the center of the box to merge with

+     * @param boxX

+     *            the x extent of the box to merge with.

+     * @param boxY

+     *            the y extent of the box to merge with.

+     * @param boxZ

+     *            the z extent of the box to merge with.

+     * @param rVal

+     *            the resulting merged box.

+     * @return the resulting merged box.

+     */

+    private BoundingBox merge(Vector3f boxCenter, float boxX, float boxY,

+            float boxZ, BoundingBox rVal) {

+

+        TempVars vars = TempVars.get();

+

+        vars.vect1.x = center.x - xExtent;

+        if (vars.vect1.x > boxCenter.x - boxX) {

+            vars.vect1.x = boxCenter.x - boxX;

+        }

+        vars.vect1.y = center.y - yExtent;

+        if (vars.vect1.y > boxCenter.y - boxY) {

+            vars.vect1.y = boxCenter.y - boxY;

+        }

+        vars.vect1.z = center.z - zExtent;

+        if (vars.vect1.z > boxCenter.z - boxZ) {

+            vars.vect1.z = boxCenter.z - boxZ;

+        }

+

+        vars.vect2.x = center.x + xExtent;

+        if (vars.vect2.x < boxCenter.x + boxX) {

+            vars.vect2.x = boxCenter.x + boxX;

+        }

+        vars.vect2.y = center.y + yExtent;

+        if (vars.vect2.y < boxCenter.y + boxY) {

+            vars.vect2.y = boxCenter.y + boxY;

+        }

+        vars.vect2.z = center.z + zExtent;

+        if (vars.vect2.z < boxCenter.z + boxZ) {

+            vars.vect2.z = boxCenter.z + boxZ;

+        }

+

+        center.set(vars.vect2).addLocal(vars.vect1).multLocal(0.5f);

+

+        xExtent = vars.vect2.x - center.x;

+        yExtent = vars.vect2.y - center.y;

+        zExtent = vars.vect2.z - center.z;

+

+        vars.release();

+

+        return rVal;

+    }

+

+    /**

+     * <code>clone</code> creates a new BoundingBox object containing the same

+     * data as this one.

+     * 

+     * @param store

+     *            where to store the cloned information. if null or wrong class,

+     *            a new store is created.

+     * @return the new BoundingBox

+     */

+    public BoundingVolume clone(BoundingVolume store) {

+        if (store != null && store.getType() == Type.AABB) {

+            BoundingBox rVal = (BoundingBox) store;

+            rVal.center.set(center);

+            rVal.xExtent = xExtent;

+            rVal.yExtent = yExtent;

+            rVal.zExtent = zExtent;

+            rVal.checkPlane = checkPlane;

+            return rVal;

+        }

+

+        BoundingBox rVal = new BoundingBox(center.clone(),

+                xExtent, yExtent, zExtent);

+        return rVal;

+    }

+

+    /**

+     * <code>toString</code> returns the string representation of this object.

+     * The form is: "Radius: RRR.SSSS Center: <Vector>".

+     * 

+     * @return the string representation of this.

+     */

+    @Override

+    public String toString() {

+        return getClass().getSimpleName() + " [Center: " + center + "  xExtent: "

+                + xExtent + "  yExtent: " + yExtent + "  zExtent: " + zExtent

+                + "]";

+    }

+

+    /**

+     * intersects determines if this Bounding Box intersects with another given

+     * bounding volume. If so, true is returned, otherwise, false is returned.

+     * 

+     * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume) 

+     */

+    public boolean intersects(BoundingVolume bv) {

+        return bv.intersectsBoundingBox(this);

+    }

+

+    /**

+     * determines if this bounding box intersects a given bounding sphere.

+     * 

+     * @see BoundingVolume#intersectsSphere(com.jme3.bounding.BoundingSphere)

+     */

+    public boolean intersectsSphere(BoundingSphere bs) {

+        assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bs.center);

+

+        if (FastMath.abs(center.x - bs.center.x) < bs.getRadius()

+                + xExtent

+                && FastMath.abs(center.y - bs.center.y) < bs.getRadius()

+                + yExtent

+                && FastMath.abs(center.z - bs.center.z) < bs.getRadius()

+                + zExtent) {

+            return true;

+        }

+

+        return false;

+    }

+

+    /**

+     * determines if this bounding box intersects a given bounding box. If the

+     * two boxes intersect in any way, true is returned. Otherwise, false is

+     * returned.

+     * 

+     * @see BoundingVolume#intersectsBoundingBox(com.jme3.bounding.BoundingBox)

+     */

+    public boolean intersectsBoundingBox(BoundingBox bb) {

+        assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center);

+

+        if (center.x + xExtent < bb.center.x - bb.xExtent

+                || center.x - xExtent > bb.center.x + bb.xExtent) {

+            return false;

+        } else if (center.y + yExtent < bb.center.y - bb.yExtent

+                || center.y - yExtent > bb.center.y + bb.yExtent) {

+            return false;

+        } else if (center.z + zExtent < bb.center.z - bb.zExtent

+                || center.z - zExtent > bb.center.z + bb.zExtent) {

+            return false;

+        } else {

+            return true;

+        }

+    }

+

+    /**

+     * determines if this bounding box intersects with a given oriented bounding

+     * box.

+     * 

+     * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)

+     */

+//    public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {

+//        return obb.intersectsBoundingBox(this);

+//    }

+    /**

+     * determines if this bounding box intersects with a given ray object. If an

+     * intersection has occurred, true is returned, otherwise false is returned.

+     * 

+     * @see BoundingVolume#intersects(com.jme3.math.Ray) 

+     */

+    public boolean intersects(Ray ray) {

+        assert Vector3f.isValidVector(center);

+

+        float rhs;

+

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = ray.origin.subtract(getCenter(vars.vect2), vars.vect1);

+

+        final float[] fWdU = vars.fWdU;

+        final float[] fAWdU = vars.fAWdU;

+        final float[] fDdU = vars.fDdU;

+        final float[] fADdU = vars.fADdU;

+        final float[] fAWxDdU = vars.fAWxDdU;

+

+        fWdU[0] = ray.getDirection().dot(Vector3f.UNIT_X);

+        fAWdU[0] = FastMath.abs(fWdU[0]);

+        fDdU[0] = diff.dot(Vector3f.UNIT_X);

+        fADdU[0] = FastMath.abs(fDdU[0]);

+        if (fADdU[0] > xExtent && fDdU[0] * fWdU[0] >= 0.0) {

+            vars.release();

+            return false;

+        }

+

+        fWdU[1] = ray.getDirection().dot(Vector3f.UNIT_Y);

+        fAWdU[1] = FastMath.abs(fWdU[1]);

+        fDdU[1] = diff.dot(Vector3f.UNIT_Y);

+        fADdU[1] = FastMath.abs(fDdU[1]);

+        if (fADdU[1] > yExtent && fDdU[1] * fWdU[1] >= 0.0) {

+            vars.release();

+            return false;

+        }

+

+        fWdU[2] = ray.getDirection().dot(Vector3f.UNIT_Z);

+        fAWdU[2] = FastMath.abs(fWdU[2]);

+        fDdU[2] = diff.dot(Vector3f.UNIT_Z);

+        fADdU[2] = FastMath.abs(fDdU[2]);

+        if (fADdU[2] > zExtent && fDdU[2] * fWdU[2] >= 0.0) {

+            vars.release();

+            return false;

+        }

+

+        Vector3f wCrossD = ray.getDirection().cross(diff, vars.vect2);

+

+        fAWxDdU[0] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_X));

+        rhs = yExtent * fAWdU[2] + zExtent * fAWdU[1];

+        if (fAWxDdU[0] > rhs) {

+            vars.release();

+            return false;

+        }

+

+        fAWxDdU[1] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Y));

+        rhs = xExtent * fAWdU[2] + zExtent * fAWdU[0];

+        if (fAWxDdU[1] > rhs) {

+            vars.release();

+            return false;

+        }

+

+        fAWxDdU[2] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Z));

+        rhs = xExtent * fAWdU[1] + yExtent * fAWdU[0];

+        if (fAWxDdU[2] > rhs) {

+            vars.release();

+            return false;

+        }

+

+        vars.release();

+        return true;

+    }

+

+    /**

+     * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)

+     */

+    private int collideWithRay(Ray ray, CollisionResults results) {

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center);

+        Vector3f direction = vars.vect2.set(ray.direction);

+

+        float[] t = {0f, Float.POSITIVE_INFINITY};

+

+        float saveT0 = t[0], saveT1 = t[1];

+        boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t)

+                && clip(-direction.x, +diff.x - xExtent, t)

+                && clip(+direction.y, -diff.y - yExtent, t)

+                && clip(-direction.y, +diff.y - yExtent, t)

+                && clip(+direction.z, -diff.z - zExtent, t)

+                && clip(-direction.z, +diff.z - zExtent, t);

+        vars.release();

+

+        if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) {

+            if (t[1] > t[0]) {

+                float[] distances = t;

+                Vector3f[] points = new Vector3f[]{

+                    new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),

+                    new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin)

+                };

+

+                CollisionResult result = new CollisionResult(points[0], distances[0]);

+                results.addCollision(result);

+                result = new CollisionResult(points[1], distances[1]);

+                results.addCollision(result);

+                return 2;

+            }

+

+            Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin);

+            CollisionResult result = new CollisionResult(point, t[0]);

+            results.addCollision(result);

+            return 1;

+        }

+        return 0;

+    }

+

+    public int collideWith(Collidable other, CollisionResults results) {

+        if (other instanceof Ray) {

+            Ray ray = (Ray) other;

+            return collideWithRay(ray, results);

+        } else if (other instanceof Triangle) {

+            Triangle t = (Triangle) other;

+            if (intersects(t.get1(), t.get2(), t.get3())) {

+                CollisionResult r = new CollisionResult();

+                results.addCollision(r);

+                return 1;

+            }

+            return 0;

+        } else {

+            throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName());

+        }

+    }

+

+    /**

+     * C code ported from <a href="http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt">

+     * http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt</a>

+     *

+     * @param v1 The first point in the triangle

+     * @param v2 The second point in the triangle

+     * @param v3 The third point in the triangle

+     * @return True if the bounding box intersects the triangle, false

+     * otherwise.

+     */

+    public boolean intersects(Vector3f v1, Vector3f v2, Vector3f v3) {

+        return Intersection.intersect(this, v1, v2, v3);

+    }

+

+    @Override

+    public boolean contains(Vector3f point) {

+        return FastMath.abs(center.x - point.x) < xExtent

+                && FastMath.abs(center.y - point.y) < yExtent

+                && FastMath.abs(center.z - point.z) < zExtent;

+    }

+

+    @Override

+    public boolean intersects(Vector3f point) {

+        return FastMath.abs(center.x - point.x) <= xExtent

+                && FastMath.abs(center.y - point.y) <= yExtent

+                && FastMath.abs(center.z - point.z) <= zExtent;

+    }

+

+    public float distanceToEdge(Vector3f point) {

+        // compute coordinates of point in box coordinate system

+        TempVars vars= TempVars.get();

+        Vector3f closest = vars.vect1;

+        

+        point.subtract(center,closest);

+

+        // project test point onto box

+        float sqrDistance = 0.0f;

+        float delta;

+

+        if (closest.x < -xExtent) {

+            delta = closest.x + xExtent;

+            sqrDistance += delta * delta;

+            closest.x = -xExtent;

+        } else if (closest.x > xExtent) {

+            delta = closest.x - xExtent;

+            sqrDistance += delta * delta;

+            closest.x = xExtent;

+        }

+

+        if (closest.y < -yExtent) {

+            delta = closest.y + yExtent;

+            sqrDistance += delta * delta;

+            closest.y = -yExtent;

+        } else if (closest.y > yExtent) {

+            delta = closest.y - yExtent;

+            sqrDistance += delta * delta;

+            closest.y = yExtent;

+        }

+

+        if (closest.z < -zExtent) {

+            delta = closest.z + zExtent;

+            sqrDistance += delta * delta;

+            closest.z = -zExtent;

+        } else if (closest.z > zExtent) {

+            delta = closest.z - zExtent;

+            sqrDistance += delta * delta;

+            closest.z = zExtent;

+        }

+        

+        vars.release();

+        return FastMath.sqrt(sqrDistance);

+    }

+

+    /**

+     * <code>clip</code> determines if a line segment intersects the current

+     * test plane.

+     * 

+     * @param denom

+     *            the denominator of the line segment.

+     * @param numer

+     *            the numerator of the line segment.

+     * @param t

+     *            test values of the plane.

+     * @return true if the line segment intersects the plane, false otherwise.

+     */

+    private boolean clip(float denom, float numer, float[] t) {

+        // Return value is 'true' if line segment intersects the current test

+        // plane. Otherwise 'false' is returned in which case the line segment

+        // is entirely clipped.

+        if (denom > 0.0f) {

+            if (numer > denom * t[1]) {

+                return false;

+            }

+            if (numer > denom * t[0]) {

+                t[0] = numer / denom;

+            }

+            return true;

+        } else if (denom < 0.0f) {

+            if (numer > denom * t[0]) {

+                return false;

+            }

+            if (numer > denom * t[1]) {

+                t[1] = numer / denom;

+            }

+            return true;

+        } else {

+            return numer <= 0.0;

+        }

+    }

+

+    /**

+     * Query extent.

+     * 

+     * @param store

+     *            where extent gets stored - null to return a new vector

+     * @return store / new vector

+     */

+    public Vector3f getExtent(Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        store.set(xExtent, yExtent, zExtent);

+        return store;

+    }

+

+    public float getXExtent() {

+        return xExtent;

+    }

+

+    public float getYExtent() {

+        return yExtent;

+    }

+

+    public float getZExtent() {

+        return zExtent;

+    }

+

+    public void setXExtent(float xExtent) {

+        if (xExtent < 0) {

+            throw new IllegalArgumentException();

+        }

+

+        this.xExtent = xExtent;

+    }

+

+    public void setYExtent(float yExtent) {

+        if (yExtent < 0) {

+            throw new IllegalArgumentException();

+        }

+

+        this.yExtent = yExtent;

+    }

+

+    public void setZExtent(float zExtent) {

+        if (zExtent < 0) {

+            throw new IllegalArgumentException();

+        }

+

+        this.zExtent = zExtent;

+    }

+

+    public Vector3f getMin(Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        store.set(center).subtractLocal(xExtent, yExtent, zExtent);

+        return store;

+    }

+

+    public Vector3f getMax(Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        store.set(center).addLocal(xExtent, yExtent, zExtent);

+        return store;

+    }

+

+    public void setMinMax(Vector3f min, Vector3f max) {

+        this.center.set(max).addLocal(min).multLocal(0.5f);

+        xExtent = FastMath.abs(max.x - center.x);

+        yExtent = FastMath.abs(max.y - center.y);

+        zExtent = FastMath.abs(max.z - center.z);

+    }

+

+    @Override

+    public void write(JmeExporter e) throws IOException {

+        super.write(e);

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(xExtent, "xExtent", 0);

+        capsule.write(yExtent, "yExtent", 0);

+        capsule.write(zExtent, "zExtent", 0);

+    }

+

+    @Override

+    public void read(JmeImporter e) throws IOException {

+        super.read(e);

+        InputCapsule capsule = e.getCapsule(this);

+        xExtent = capsule.readFloat("xExtent", 0);

+        yExtent = capsule.readFloat("yExtent", 0);

+        zExtent = capsule.readFloat("zExtent", 0);

+    }

+

+    @Override

+    public float getVolume() {

+        return (8 * xExtent * yExtent * zExtent);

+    }

+}

diff --git a/engine/src/core/com/jme3/bounding/BoundingSphere.java b/engine/src/core/com/jme3/bounding/BoundingSphere.java
new file mode 100644
index 0000000..12be035
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingSphere.java
@@ -0,0 +1,858 @@
+/*

+ * Copyright (c) 2009-2010 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.bounding;

+

+import com.jme3.collision.Collidable;

+import com.jme3.collision.CollisionResult;

+import com.jme3.collision.CollisionResults;

+import com.jme3.collision.UnsupportedCollisionException;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.math.*;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * <code>BoundingSphere</code> defines a sphere that defines a container for a

+ * group of vertices of a particular piece of geometry. This sphere defines a

+ * radius and a center. <br>

+ * <br>

+ * A typical usage is to allow the class define the center and radius by calling

+ * either <code>containAABB</code> or <code>averagePoints</code>. A call to

+ * <code>computeFramePoint</code> in turn calls <code>containAABB</code>.

+ *

+ * @author Mark Powell

+ * @version $Id: BoundingSphere.java,v 1.59 2007/08/17 10:34:26 rherlitz Exp $

+ */

+public class BoundingSphere extends BoundingVolume {

+

+    private static final Logger logger =

+            Logger.getLogger(BoundingSphere.class.getName());

+    float radius;

+    private static final float RADIUS_EPSILON = 1f + 0.00001f;

+

+    /**

+     * Default contstructor instantiates a new <code>BoundingSphere</code>

+     * object.

+     */

+    public BoundingSphere() {

+    }

+

+    /**

+     * Constructor instantiates a new <code>BoundingSphere</code> object.

+     *

+     * @param r

+     *            the radius of the sphere.

+     * @param c

+     *            the center of the sphere.

+     */

+    public BoundingSphere(float r, Vector3f c) {

+        this.center.set(c);

+        this.radius = r;

+    }

+

+    public Type getType() {

+        return Type.Sphere;

+    }

+

+    /**

+     * <code>getRadius</code> returns the radius of the bounding sphere.

+     *

+     * @return the radius of the bounding sphere.

+     */

+    public float getRadius() {

+        return radius;

+    }

+

+    /**

+     * <code>setRadius</code> sets the radius of this bounding sphere.

+     *

+     * @param radius

+     *            the new radius of the bounding sphere.

+     */

+    public void setRadius(float radius) {

+        this.radius = radius;

+    }

+

+    /**

+     * <code>computeFromPoints</code> creates a new Bounding Sphere from a

+     * given set of points. It uses the <code>calcWelzl</code> method as

+     * default.

+     *

+     * @param points

+     *            the points to contain.

+     */

+    public void computeFromPoints(FloatBuffer points) {

+        calcWelzl(points);

+    }

+

+    /**

+     * <code>computeFromTris</code> creates a new Bounding Box from a given

+     * set of triangles. It is used in OBBTree calculations.

+     *

+     * @param tris

+     * @param start

+     * @param end

+     */

+    public void computeFromTris(Triangle[] tris, int start, int end) {

+        if (end - start <= 0) {

+            return;

+        }

+

+        Vector3f[] vertList = new Vector3f[(end - start) * 3];

+

+        int count = 0;

+        for (int i = start; i < end; i++) {

+            vertList[count++] = tris[i].get(0);

+            vertList[count++] = tris[i].get(1);

+            vertList[count++] = tris[i].get(2);

+        }

+        averagePoints(vertList);

+    }

+//

+//    /**

+//     * <code>computeFromTris</code> creates a new Bounding Box from a given

+//     * set of triangles. It is used in OBBTree calculations.

+//     *

+//	 * @param indices

+//	 * @param mesh

+//     * @param start

+//     * @param end

+//     */

+//    public void computeFromTris(int[] indices, Mesh mesh, int start, int end) {

+//    	if (end - start <= 0) {

+//            return;

+//        }

+//

+//    	Vector3f[] vertList = new Vector3f[(end - start) * 3];

+//

+//        int count = 0;

+//        for (int i = start; i < end; i++) {

+//        	mesh.getTriangle(indices[i], verts);

+//        	vertList[count++] = new Vector3f(verts[0]);

+//        	vertList[count++] = new Vector3f(verts[1]);

+//        	vertList[count++] = new Vector3f(verts[2]);

+//        }

+//

+//        averagePoints(vertList);

+//    }

+

+    /**

+     * Calculates a minimum bounding sphere for the set of points. The algorithm

+     * was originally found in C++ at

+     * <p><a href="http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1">

+     * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1</a><br><strong>broken link</strong></p>

+     * <p>and translated to java by Cep21</p>

+     *

+     * @param points

+     *            The points to calculate the minimum bounds from.

+     */

+    public void calcWelzl(FloatBuffer points) {

+        if (center == null) {

+            center = new Vector3f();

+        }

+        FloatBuffer buf = BufferUtils.createFloatBuffer(points.limit());

+        points.rewind();

+        buf.put(points);

+        buf.flip();

+        recurseMini(buf, buf.limit() / 3, 0, 0);

+    }

+

+    /**

+     * Used from calcWelzl. This function recurses to calculate a minimum

+     * bounding sphere a few points at a time.

+     *

+     * @param points

+     *            The array of points to look through.

+     * @param p

+     *            The size of the list to be used.

+     * @param b

+     *            The number of points currently considering to include with the

+     *            sphere.

+     * @param ap

+     *            A variable simulating pointer arithmatic from C++, and offset

+     *            in <code>points</code>.

+     */

+    private void recurseMini(FloatBuffer points, int p, int b, int ap) {

+        //TempVars vars = TempVars.get();

+

+        Vector3f tempA = new Vector3f(); //vars.vect1;

+        Vector3f tempB = new Vector3f(); //vars.vect2;

+        Vector3f tempC = new Vector3f(); //vars.vect3;

+        Vector3f tempD = new Vector3f(); //vars.vect4;

+

+        switch (b) {

+            case 0:

+                this.radius = 0;

+                this.center.set(0, 0, 0);

+                break;

+            case 1:

+                this.radius = 1f - RADIUS_EPSILON;

+                BufferUtils.populateFromBuffer(center, points, ap - 1);

+                break;

+            case 2:

+                BufferUtils.populateFromBuffer(tempA, points, ap - 1);

+                BufferUtils.populateFromBuffer(tempB, points, ap - 2);

+                setSphere(tempA, tempB);

+                break;

+            case 3:

+                BufferUtils.populateFromBuffer(tempA, points, ap - 1);

+                BufferUtils.populateFromBuffer(tempB, points, ap - 2);

+                BufferUtils.populateFromBuffer(tempC, points, ap - 3);

+                setSphere(tempA, tempB, tempC);

+                break;

+            case 4:

+                BufferUtils.populateFromBuffer(tempA, points, ap - 1);

+                BufferUtils.populateFromBuffer(tempB, points, ap - 2);

+                BufferUtils.populateFromBuffer(tempC, points, ap - 3);

+                BufferUtils.populateFromBuffer(tempD, points, ap - 4);

+                setSphere(tempA, tempB, tempC, tempD);

+                //vars.release();

+                return;

+        }

+        for (int i = 0; i < p; i++) {

+            BufferUtils.populateFromBuffer(tempA, points, i + ap);

+            if (tempA.distanceSquared(center) - (radius * radius) > RADIUS_EPSILON - 1f) {

+                for (int j = i; j > 0; j--) {

+                    BufferUtils.populateFromBuffer(tempB, points, j + ap);

+                    BufferUtils.populateFromBuffer(tempC, points, j - 1 + ap);

+                    BufferUtils.setInBuffer(tempC, points, j + ap);

+                    BufferUtils.setInBuffer(tempB, points, j - 1 + ap);

+                }

+                recurseMini(points, i, b + 1, ap + 1);

+            }

+        }

+        //vars.release();

+    }

+

+    /**

+     * Calculates the minimum bounding sphere of 4 points. Used in welzl's

+     * algorithm.

+     *

+     * @param O

+     *            The 1st point inside the sphere.

+     * @param A

+     *            The 2nd point inside the sphere.

+     * @param B

+     *            The 3rd point inside the sphere.

+     * @param C

+     *            The 4th point inside the sphere.

+     * @see #calcWelzl(java.nio.FloatBuffer)

+     */

+    private void setSphere(Vector3f O, Vector3f A, Vector3f B, Vector3f C) {

+        Vector3f a = A.subtract(O);

+        Vector3f b = B.subtract(O);

+        Vector3f c = C.subtract(O);

+

+        float Denominator = 2.0f * (a.x * (b.y * c.z - c.y * b.z) - b.x

+                * (a.y * c.z - c.y * a.z) + c.x * (a.y * b.z - b.y * a.z));

+        if (Denominator == 0) {

+            center.set(0, 0, 0);

+            radius = 0;

+        } else {

+            Vector3f o = a.cross(b).multLocal(c.lengthSquared()).addLocal(

+                    c.cross(a).multLocal(b.lengthSquared())).addLocal(

+                    b.cross(c).multLocal(a.lengthSquared())).divideLocal(

+                    Denominator);

+

+            radius = o.length() * RADIUS_EPSILON;

+            O.add(o, center);

+        }

+    }

+

+    /**

+     * Calculates the minimum bounding sphere of 3 points. Used in welzl's

+     * algorithm.

+     *

+     * @param O

+     *            The 1st point inside the sphere.

+     * @param A

+     *            The 2nd point inside the sphere.

+     * @param B

+     *            The 3rd point inside the sphere.

+     * @see #calcWelzl(java.nio.FloatBuffer)

+     */

+    private void setSphere(Vector3f O, Vector3f A, Vector3f B) {

+        Vector3f a = A.subtract(O);

+        Vector3f b = B.subtract(O);

+        Vector3f acrossB = a.cross(b);

+

+        float Denominator = 2.0f * acrossB.dot(acrossB);

+

+        if (Denominator == 0) {

+            center.set(0, 0, 0);

+            radius = 0;

+        } else {

+

+            Vector3f o = acrossB.cross(a).multLocal(b.lengthSquared()).addLocal(b.cross(acrossB).multLocal(a.lengthSquared())).divideLocal(Denominator);

+            radius = o.length() * RADIUS_EPSILON;

+            O.add(o, center);

+        }

+    }

+

+    /**

+     * Calculates the minimum bounding sphere of 2 points. Used in welzl's

+     * algorithm.

+     *

+     * @param O

+     *            The 1st point inside the sphere.

+     * @param A

+     *            The 2nd point inside the sphere.

+     * @see #calcWelzl(java.nio.FloatBuffer)

+     */

+    private void setSphere(Vector3f O, Vector3f A) {

+        radius = FastMath.sqrt(((A.x - O.x) * (A.x - O.x) + (A.y - O.y)

+                * (A.y - O.y) + (A.z - O.z) * (A.z - O.z)) / 4f) + RADIUS_EPSILON - 1f;

+        center.interpolate(O, A, .5f);

+    }

+

+    /**

+     * <code>averagePoints</code> selects the sphere center to be the average

+     * of the points and the sphere radius to be the smallest value to enclose

+     * all points.

+     *

+     * @param points

+     *            the list of points to contain.

+     */

+    public void averagePoints(Vector3f[] points) {

+        logger.info("Bounding Sphere calculated using average points.");

+        center = points[0];

+

+        for (int i = 1; i < points.length; i++) {

+            center.addLocal(points[i]);

+        }

+

+        float quantity = 1.0f / points.length;

+        center.multLocal(quantity);

+

+        float maxRadiusSqr = 0;

+        for (int i = 0; i < points.length; i++) {

+            Vector3f diff = points[i].subtract(center);

+            float radiusSqr = diff.lengthSquared();

+            if (radiusSqr > maxRadiusSqr) {

+                maxRadiusSqr = radiusSqr;

+            }

+        }

+

+        radius = (float) Math.sqrt(maxRadiusSqr) + RADIUS_EPSILON - 1f;

+

+    }

+

+    /**

+     * <code>transform</code> modifies the center of the sphere to reflect the

+     * change made via a rotation, translation and scale.

+     *

+     * @param trans

+     *            the transform to apply

+     * @param store

+     *            sphere to store result in

+     * @return BoundingVolume

+     * @return ref

+     */

+    public BoundingVolume transform(Transform trans, BoundingVolume store) {

+        BoundingSphere sphere;

+        if (store == null || store.getType() != BoundingVolume.Type.Sphere) {

+            sphere = new BoundingSphere(1, new Vector3f(0, 0, 0));

+        } else {

+            sphere = (BoundingSphere) store;

+        }

+

+        center.mult(trans.getScale(), sphere.center);

+        trans.getRotation().mult(sphere.center, sphere.center);

+        sphere.center.addLocal(trans.getTranslation());

+        sphere.radius = FastMath.abs(getMaxAxis(trans.getScale()) * radius) + RADIUS_EPSILON - 1f;

+        return sphere;

+    }

+

+    public BoundingVolume transform(Matrix4f trans, BoundingVolume store) {

+        BoundingSphere sphere;

+        if (store == null || store.getType() != BoundingVolume.Type.Sphere) {

+            sphere = new BoundingSphere(1, new Vector3f(0, 0, 0));

+        } else {

+            sphere = (BoundingSphere) store;

+        }

+

+        trans.mult(center, sphere.center);

+        Vector3f axes = new Vector3f(1, 1, 1);

+        trans.mult(axes, axes);

+        float ax = getMaxAxis(axes);

+        sphere.radius = FastMath.abs(ax * radius) + RADIUS_EPSILON - 1f;

+        return sphere;

+    }

+

+    private float getMaxAxis(Vector3f scale) {

+        float x = FastMath.abs(scale.x);

+        float y = FastMath.abs(scale.y);

+        float z = FastMath.abs(scale.z);

+

+        if (x >= y) {

+            if (x >= z) {

+                return x;

+            }

+            return z;

+        }

+

+        if (y >= z) {

+            return y;

+        }

+

+        return z;

+    }

+

+    /**

+     * <code>whichSide</code> takes a plane (typically provided by a view

+     * frustum) to determine which side this bound is on.

+     *

+     * @param plane

+     *            the plane to check against.

+     * @return side

+     */

+    public Plane.Side whichSide(Plane plane) {

+        float distance = plane.pseudoDistance(center);

+

+        if (distance <= -radius) {

+            return Plane.Side.Negative;

+        } else if (distance >= radius) {

+            return Plane.Side.Positive;

+        } else {

+            return Plane.Side.None;

+        }

+    }

+

+    /**

+     * <code>merge</code> combines this sphere with a second bounding sphere.

+     * This new sphere contains both bounding spheres and is returned.

+     *

+     * @param volume

+     *            the sphere to combine with this sphere.

+     * @return a new sphere

+     */

+    public BoundingVolume merge(BoundingVolume volume) {

+        if (volume == null) {

+            return this;

+        }

+

+        switch (volume.getType()) {

+

+            case Sphere: {

+                BoundingSphere sphere = (BoundingSphere) volume;

+                float temp_radius = sphere.getRadius();

+                Vector3f temp_center = sphere.center;

+                BoundingSphere rVal = new BoundingSphere();

+                return merge(temp_radius, temp_center, rVal);

+            }

+

+            case AABB: {

+                BoundingBox box = (BoundingBox) volume;

+                Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,

+                        box.zExtent);

+                Vector3f temp_center = box.center;

+                BoundingSphere rVal = new BoundingSphere();

+                return merge(radVect.length(), temp_center, rVal);

+            }

+

+//        case OBB: {

+//        	OrientedBoundingBox box = (OrientedBoundingBox) volume;

+//            BoundingSphere rVal = (BoundingSphere) this.clone(null);

+//            return rVal.mergeOBB(box);

+//        }

+

+            default:

+                return null;

+

+        }

+    }

+

+    /**

+     * <code>mergeLocal</code> combines this sphere with a second bounding

+     * sphere locally. Altering this sphere to contain both the original and the

+     * additional sphere volumes;

+     *

+     * @param volume

+     *            the sphere to combine with this sphere.

+     * @return this

+     */

+    public BoundingVolume mergeLocal(BoundingVolume volume) {

+        if (volume == null) {

+            return this;

+        }

+

+        switch (volume.getType()) {

+

+            case Sphere: {

+                BoundingSphere sphere = (BoundingSphere) volume;

+                float temp_radius = sphere.getRadius();

+                Vector3f temp_center = sphere.center;

+                return merge(temp_radius, temp_center, this);

+            }

+

+            case AABB: {

+                BoundingBox box = (BoundingBox) volume;

+                TempVars vars = TempVars.get();

+                Vector3f radVect = vars.vect1;

+                radVect.set(box.xExtent, box.yExtent, box.zExtent);

+                Vector3f temp_center = box.center;

+                float len = radVect.length();

+                vars.release();

+                return merge(len, temp_center, this);

+            }

+

+//        case OBB: {

+//        	return mergeOBB((OrientedBoundingBox) volume);

+//        }

+

+            default:

+                return null;

+        }

+    }

+

+//    /**

+//     * Merges this sphere with the given OBB.

+//     *

+//     * @param volume

+//     *            The OBB to merge.

+//     * @return This sphere, after merging.

+//     */

+//    private BoundingSphere mergeOBB(OrientedBoundingBox volume) {

+//        // compute edge points from the obb

+//        if (!volume.correctCorners)

+//            volume.computeCorners();

+//        _mergeBuf.rewind();

+//        for (int i = 0; i < 8; i++) {

+//            _mergeBuf.put(volume.vectorStore[i].x);

+//            _mergeBuf.put(volume.vectorStore[i].y);

+//            _mergeBuf.put(volume.vectorStore[i].z);

+//        }

+//

+//        // remember old radius and center

+//        float oldRadius = radius;

+//        Vector3f oldCenter = _compVect2.set( center );

+//

+//        // compute new radius and center from obb points

+//        computeFromPoints(_mergeBuf);

+//        Vector3f newCenter = _compVect3.set( center );

+//        float newRadius = radius;

+//

+//        // restore old center and radius

+//        center.set( oldCenter );

+//        radius = oldRadius;

+//

+//        //merge obb points result

+//        merge( newRadius, newCenter, this );

+//

+//        return this;

+//    }

+    private BoundingVolume merge(float temp_radius, Vector3f temp_center,

+            BoundingSphere rVal) {

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = temp_center.subtract(center, vars.vect1);

+        float lengthSquared = diff.lengthSquared();

+        float radiusDiff = temp_radius - radius;

+

+        float fRDiffSqr = radiusDiff * radiusDiff;

+

+        if (fRDiffSqr >= lengthSquared) {

+            if (radiusDiff <= 0.0f) {

+                vars.release();

+                return this;

+            }

+

+            Vector3f rCenter = rVal.center;

+            if (rCenter == null) {

+                rVal.setCenter(rCenter = new Vector3f());

+            }

+            rCenter.set(temp_center);

+            rVal.setRadius(temp_radius);

+            vars.release();

+            return rVal;

+        }

+

+        float length = (float) Math.sqrt(lengthSquared);

+

+        Vector3f rCenter = rVal.center;

+        if (rCenter == null) {

+            rVal.setCenter(rCenter = new Vector3f());

+        }

+        if (length > RADIUS_EPSILON) {

+            float coeff = (length + radiusDiff) / (2.0f * length);

+            rCenter.set(center.addLocal(diff.multLocal(coeff)));

+        } else {

+            rCenter.set(center);

+        }

+

+        rVal.setRadius(0.5f * (length + radius + temp_radius));

+        vars.release();

+        return rVal;

+    }

+

+    /**

+     * <code>clone</code> creates a new BoundingSphere object containing the

+     * same data as this one.

+     *

+     * @param store

+     *            where to store the cloned information. if null or wrong class,

+     *            a new store is created.

+     * @return the new BoundingSphere

+     */

+    public BoundingVolume clone(BoundingVolume store) {

+        if (store != null && store.getType() == Type.Sphere) {

+            BoundingSphere rVal = (BoundingSphere) store;

+            if (null == rVal.center) {

+                rVal.center = new Vector3f();

+            }

+            rVal.center.set(center);

+            rVal.radius = radius;

+            rVal.checkPlane = checkPlane;

+            return rVal;

+        }

+

+        return new BoundingSphere(radius,

+                (center != null ? (Vector3f) center.clone() : null));

+    }

+

+    /**

+     * <code>toString</code> returns the string representation of this object.

+     * The form is: "Radius: RRR.SSSS Center: <Vector>".

+     *

+     * @return the string representation of this.

+     */

+    @Override

+    public String toString() {

+        return getClass().getSimpleName() + " [Radius: " + radius + " Center: "

+                + center + "]";

+    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume)

+     */

+    public boolean intersects(BoundingVolume bv) {

+        return bv.intersectsSphere(this);

+    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.bounding.BoundingSphere)

+     */

+    public boolean intersectsSphere(BoundingSphere bs) {

+        assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bs.center);

+

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = center.subtract(bs.center, vars.vect1);

+        float rsum = getRadius() + bs.getRadius();

+        boolean eq = (diff.dot(diff) <= rsum * rsum);

+        vars.release();

+        return eq;

+    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)

+     */

+    public boolean intersectsBoundingBox(BoundingBox bb) {

+        assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center);

+

+        if (FastMath.abs(bb.center.x - center.x) < getRadius()

+                + bb.xExtent

+                && FastMath.abs(bb.center.y - center.y) < getRadius()

+                + bb.yExtent

+                && FastMath.abs(bb.center.z - center.z) < getRadius()

+                + bb.zExtent) {

+            return true;

+        }

+

+        return false;

+    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)

+     */

+//    public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {

+//        return obb.intersectsSphere(this);

+//    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray)

+     */

+    public boolean intersects(Ray ray) {

+        assert Vector3f.isValidVector(center);

+

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal(center);

+        float radiusSquared = getRadius() * getRadius();

+        float a = diff.dot(diff) - radiusSquared;

+        if (a <= 0.0) {

+            // in sphere

+            return true;

+        }

+

+        // outside sphere

+        float b = ray.getDirection().dot(diff);

+        vars.release();

+        if (b >= 0.0) {

+            return false;

+        }

+        return b * b >= a;

+    }

+

+    /*

+     * (non-Javadoc)

+     *

+     * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)

+     */

+    private int collideWithRay(Ray ray, CollisionResults results) {

+        TempVars vars = TempVars.get();

+

+        Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal(

+                center);

+        float a = diff.dot(diff) - (getRadius() * getRadius());

+        float a1, discr, root;

+        if (a <= 0.0) {

+            // inside sphere

+            a1 = ray.direction.dot(diff);

+            discr = (a1 * a1) - a;

+            root = FastMath.sqrt(discr);

+

+            float distance = root - a1;

+            Vector3f point = new Vector3f(ray.direction).multLocal(distance).addLocal(ray.origin);

+

+            CollisionResult result = new CollisionResult(point, distance);

+            results.addCollision(result);

+            vars.release();

+            return 1;

+        }

+

+        a1 = ray.direction.dot(diff);

+        vars.release();

+        if (a1 >= 0.0) {

+            return 0;

+        }

+

+        discr = a1 * a1 - a;

+        if (discr < 0.0) {

+            return 0;

+        } else if (discr >= FastMath.ZERO_TOLERANCE) {

+            root = FastMath.sqrt(discr);

+            float dist = -a1 - root;

+            Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);

+            results.addCollision(new CollisionResult(point, dist));

+

+            dist = -a1 + root;

+            point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);

+            results.addCollision(new CollisionResult(point, dist));

+            return 2;

+        } else {

+            float dist = -a1;

+            Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);

+            results.addCollision(new CollisionResult(point, dist));

+            return 1;

+        }

+    }

+    

+    public int collideWith(Collidable other, CollisionResults results) {

+        if (other instanceof Ray) {

+            Ray ray = (Ray) other;

+            return collideWithRay(ray, results);

+        } else if (other instanceof Triangle){

+            Triangle t = (Triangle) other;

+            

+            float r2 = radius * radius;

+            float d1 = center.distanceSquared(t.get1());

+            float d2 = center.distanceSquared(t.get2());

+            float d3 = center.distanceSquared(t.get3());

+            

+            if (d1 <= r2 || d2 <= r2 || d3 <= r2) {

+                CollisionResult r = new CollisionResult();

+                r.setDistance(FastMath.sqrt(Math.min(Math.min(d1, d2), d3)) - radius);

+                results.addCollision(r);

+                return 1;

+            }

+

+            return 0;

+        } else {

+            throw new UnsupportedCollisionException();

+        }

+    }

+

+    @Override

+    public boolean contains(Vector3f point) {

+        return center.distanceSquared(point) < (getRadius() * getRadius());

+    }

+

+    @Override

+    public boolean intersects(Vector3f point) {

+        return center.distanceSquared(point) <= (getRadius() * getRadius());

+    }

+

+    public float distanceToEdge(Vector3f point) {

+        return center.distance(point) - radius;

+    }

+

+    @Override

+    public void write(JmeExporter e) throws IOException {

+        super.write(e);

+        try {

+            e.getCapsule(this).write(radius, "radius", 0);

+        } catch (IOException ex) {

+            logger.logp(Level.SEVERE, this.getClass().toString(), "write(JMEExporter)", "Exception", ex);

+        }

+    }

+

+    @Override

+    public void read(JmeImporter e) throws IOException {

+        super.read(e);

+        try {

+            radius = e.getCapsule(this).readFloat("radius", 0);

+        } catch (IOException ex) {

+            logger.logp(Level.SEVERE, this.getClass().toString(), "read(JMEImporter)", "Exception", ex);

+        }

+    }

+

+    @Override

+    public float getVolume() {

+        return 4 * FastMath.ONE_THIRD * FastMath.PI * radius * radius * radius;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/bounding/BoundingVolume.java b/engine/src/core/com/jme3/bounding/BoundingVolume.java
new file mode 100644
index 0000000..8db2e57
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingVolume.java
@@ -0,0 +1,329 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.bounding;

+

+import com.jme3.collision.Collidable;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.Savable;

+import com.jme3.math.*;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+

+/**

+ * <code>BoundingVolume</code> defines an interface for dealing with

+ * containment of a collection of points.

+ * 

+ * @author Mark Powell

+ * @version $Id: BoundingVolume.java,v 1.24 2007/09/21 15:45:32 nca Exp $

+ */

+public abstract class BoundingVolume implements Savable, Cloneable, Collidable {

+

+    /**

+     * The type of bounding volume being used.

+     */

+    public enum Type {

+        /**

+         * {@link BoundingSphere}

+         */

+        Sphere, 

+        

+        /**

+         * {@link BoundingBox}.

+         */

+        AABB, 

+        

+        /**

+         * {@link com.jme3.bounding.OrientedBoundingBox}

+         */

+        OBB, 

+        

+        /**

+         * Currently unsupported by jME3.

+         */

+        Capsule;

+    }

+

+    protected int checkPlane = 0;

+    protected Vector3f center = new Vector3f();

+

+    public BoundingVolume() {

+    }

+

+    public BoundingVolume(Vector3f center) {

+        this.center.set(center);

+    }

+

+    /**

+     * Grabs the checkplane we should check first.

+     *

+     */

+    public int getCheckPlane() {

+        return checkPlane;

+    }

+

+    /**

+     * Sets the index of the plane that should be first checked during rendering.

+     *

+     * @param value

+     */

+    public final void setCheckPlane(int value) {

+        checkPlane = value;

+    }

+

+    /**

+     * getType returns the type of bounding volume this is.

+     */

+    public abstract Type getType();

+

+    /**

+     *

+     * <code>transform</code> alters the location of the bounding volume by a

+     * rotation, translation and a scalar.

+     *

+     * @param trans

+     *            the transform to affect the bound.

+     * @return the new bounding volume.

+     */

+    public final BoundingVolume transform(Transform trans) {

+        return transform(trans, null);

+    }

+

+    /**

+     *

+     * <code>transform</code> alters the location of the bounding volume by a

+     * rotation, translation and a scalar.

+     *

+     * @param trans

+     *            the transform to affect the bound.

+     * @param store

+     *            sphere to store result in

+     * @return the new bounding volume.

+     */

+    public abstract BoundingVolume transform(Transform trans, BoundingVolume store);

+

+    public abstract BoundingVolume transform(Matrix4f trans, BoundingVolume store);

+

+    /**

+     *

+     * <code>whichSide</code> returns the side on which the bounding volume

+     * lies on a plane. Possible values are POSITIVE_SIDE, NEGATIVE_SIDE, and

+     * NO_SIDE.

+     *

+     * @param plane

+     *            the plane to check against this bounding volume.

+     * @return the side on which this bounding volume lies.

+     */

+    public abstract Plane.Side whichSide(Plane plane);

+

+    /**

+     *

+     * <code>computeFromPoints</code> generates a bounding volume that

+     * encompasses a collection of points.

+     *

+     * @param points

+     *            the points to contain.

+     */

+    public abstract void computeFromPoints(FloatBuffer points);

+

+    /**

+     * <code>merge</code> combines two bounding volumes into a single bounding

+     * volume that contains both this bounding volume and the parameter volume.

+     *

+     * @param volume

+     *            the volume to combine.

+     * @return the new merged bounding volume.

+     */

+    public abstract BoundingVolume merge(BoundingVolume volume);

+

+    /**

+     * <code>mergeLocal</code> combines two bounding volumes into a single

+     * bounding volume that contains both this bounding volume and the parameter

+     * volume. The result is stored locally.

+     *

+     * @param volume

+     *            the volume to combine.

+     * @return this

+     */

+    public abstract BoundingVolume mergeLocal(BoundingVolume volume);

+

+    /**

+     * <code>clone</code> creates a new BoundingVolume object containing the

+     * same data as this one.

+     *

+     * @param store

+     *            where to store the cloned information. if null or wrong class,

+     *            a new store is created.

+     * @return the new BoundingVolume

+     */

+    public abstract BoundingVolume clone(BoundingVolume store);

+

+    public final Vector3f getCenter() {

+        return center;

+    }

+

+    public final Vector3f getCenter(Vector3f store) {

+        store.set(center);

+        return store;

+    }

+

+    public final void setCenter(Vector3f newCenter) {

+        center.set(newCenter);

+    }

+

+    /**

+     * Find the distance from the center of this Bounding Volume to the given

+     * point.

+     * 

+     * @param point

+     *            The point to get the distance to

+     * @return distance

+     */

+    public final float distanceTo(Vector3f point) {

+        return center.distance(point);

+    }

+

+    /**

+     * Find the squared distance from the center of this Bounding Volume to the

+     * given point.

+     * 

+     * @param point

+     *            The point to get the distance to

+     * @return distance

+     */

+    public final float distanceSquaredTo(Vector3f point) {

+        return center.distanceSquared(point);

+    }

+

+    /**

+     * Find the distance from the nearest edge of this Bounding Volume to the given

+     * point.

+     * 

+     * @param point

+     *            The point to get the distance to

+     * @return distance

+     */

+    public abstract float distanceToEdge(Vector3f point);

+

+    /**

+     * determines if this bounding volume and a second given volume are

+     * intersecting. Intersecting being: one volume contains another, one volume

+     * overlaps another or one volume touches another.

+     *

+     * @param bv

+     *            the second volume to test against.

+     * @return true if this volume intersects the given volume.

+     */

+    public abstract boolean intersects(BoundingVolume bv);

+

+    /**

+     * determines if a ray intersects this bounding volume.

+     *

+     * @param ray

+     *            the ray to test.

+     * @return true if this volume is intersected by a given ray.

+     */

+    public abstract boolean intersects(Ray ray);

+

+

+    /**

+     * determines if this bounding volume and a given bounding sphere are

+     * intersecting.

+     *

+     * @param bs

+     *            the bounding sphere to test against.

+     * @return true if this volume intersects the given bounding sphere.

+     */

+    public abstract boolean intersectsSphere(BoundingSphere bs);

+

+    /**

+     * determines if this bounding volume and a given bounding box are

+     * intersecting.

+     *

+     * @param bb

+     *            the bounding box to test against.

+     * @return true if this volume intersects the given bounding box.

+     */

+    public abstract boolean intersectsBoundingBox(BoundingBox bb);

+

+    /**

+     * determines if this bounding volume and a given bounding box are

+     * intersecting.

+     *

+     * @param bb

+     *            the bounding box to test against.

+     * @return true if this volume intersects the given bounding box.

+     */

+//	public abstract boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb);

+    /**

+     * 

+     * determines if a given point is contained within this bounding volume.

+     * If the point is on the edge of the bounding volume, this method will

+     * return false. Use intersects(Vector3f) to check for edge intersection.

+     * 

+     * @param point

+     *            the point to check

+     * @return true if the point lies within this bounding volume.

+     */

+    public abstract boolean contains(Vector3f point);

+

+    /**

+     * Determines if a given point intersects (touches or is inside) this bounding volume.

+     * @param point the point to check

+     * @return true if the point lies within this bounding volume.

+     */

+    public abstract boolean intersects(Vector3f point);

+

+    public abstract float getVolume();

+

+    @Override

+    public BoundingVolume clone() {

+        try{

+            BoundingVolume clone = (BoundingVolume) super.clone();

+            clone.center = center.clone();

+            return clone;

+        }catch (CloneNotSupportedException ex){

+            throw new AssertionError();

+        }

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        e.getCapsule(this).write(center, "center", Vector3f.ZERO);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        center = (Vector3f) e.getCapsule(this).readSavable("center", Vector3f.ZERO.clone());

+    }

+ 

+}

+

diff --git a/engine/src/core/com/jme3/bounding/Intersection.java b/engine/src/core/com/jme3/bounding/Intersection.java
new file mode 100644
index 0000000..c53b792
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/Intersection.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 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.bounding;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * This class includes some utility methods for computing intersection
+ * between bounding volumes and triangles.
+ * @author Kirill
+ */
+public class Intersection {
+
+    private static final void findMinMax(float x0, float x1, float x2, Vector3f minMax) {
+        minMax.set(x0, x0, 0);
+        if (x1 < minMax.x) {
+            minMax.setX(x1);
+        }
+        if (x1 > minMax.y) {
+            minMax.setY(x1);
+        }
+        if (x2 < minMax.x) {
+            minMax.setX(x2);
+        }
+        if (x2 > minMax.y) {
+            minMax.setY(x2);
+        }
+    }
+
+//    private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, )
+//    private boolean axisTestX01(float a, float b, float fa, float fb,
+//                             Vector3f center, Vector3f ext,
+//                             Vector3f v1, Vector3f v2, Vector3f v3){
+//	float p0 = a * v0.y - b * v0.z;
+//	float p2 = a * v2.y - b * v2.z;
+//        if(p0 < p2){
+//            min = p0;
+//            max = p2;
+//        } else {
+//            min = p2;
+//            max = p0;
+//        }
+//	float rad = fa * boxhalfsize.y + fb * boxhalfsize.z;
+//	if(min > rad || max < -rad)
+//            return false;
+//    }
+    public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vector3f v3) {
+        //  use separating axis theorem to test overlap between triangle and box
+        //  need to test for overlap in these directions:
+        //  1) the {x,y,z}-directions (actually, since we use the AABB of the triangle
+        //     we do not even need to test these)
+        //  2) normal of the triangle
+        //  3) crossproduct(edge from tri, {x,y,z}-directin)
+        //       this gives 3x3=9 more tests
+
+        TempVars vars = TempVars.get();
+
+
+        Vector3f tmp0 = vars.vect1,
+                tmp1 = vars.vect2,
+                tmp2 = vars.vect3;
+
+        Vector3f e0 = vars.vect4,
+                e1 = vars.vect5,
+                e2 = vars.vect6;
+
+        Vector3f center = bbox.getCenter();
+        Vector3f extent = bbox.getExtent(null);
+
+//   float min,max,p0,p1,p2,rad,fex,fey,fez;
+//   float normal[3]
+
+        // This is the fastest branch on Sun
+        // move everything so that the boxcenter is in (0,0,0)
+        v1.subtract(center, tmp0);
+        v2.subtract(center, tmp1);
+        v3.subtract(center, tmp2);
+
+        // compute triangle edges
+        tmp1.subtract(tmp0, e0); // tri edge 0
+        tmp2.subtract(tmp1, e1); // tri edge 1
+        tmp0.subtract(tmp2, e2); // tri edge 2
+
+        // Bullet 3:
+        //  test the 9 tests first (this was faster)
+        float min, max;
+        float p0, p1, p2, rad;
+        float fex = FastMath.abs(e0.x);
+        float fey = FastMath.abs(e0.y);
+        float fez = FastMath.abs(e0.z);
+
+
+
+        //AXISTEST_X01(e0[Z], e0[Y], fez, fey);
+        p0 = e0.z * tmp0.y - e0.y * tmp0.z;
+        p2 = e0.z * tmp2.y - e0.y * tmp2.z;
+        min = min(p0, p2);
+        max = max(p0, p2);
+        rad = fez * extent.y + fey * extent.z;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        //   AXISTEST_Y02(e0[Z], e0[X], fez, fex);
+        p0 = -e0.z * tmp0.x + e0.x * tmp0.z;
+        p2 = -e0.z * tmp2.x + e0.x * tmp2.z;
+        min = min(p0, p2);
+        max = max(p0, p2);
+        rad = fez * extent.x + fex * extent.z;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        // AXISTEST_Z12(e0[Y], e0[X], fey, fex);
+        p1 = e0.y * tmp1.x - e0.x * tmp1.y;
+        p2 = e0.y * tmp2.x - e0.x * tmp2.y;
+        min = min(p1, p2);
+        max = max(p1, p2);
+        rad = fey * extent.x + fex * extent.y;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        fex = FastMath.abs(e1.x);
+        fey = FastMath.abs(e1.y);
+        fez = FastMath.abs(e1.z);
+
+//        AXISTEST_X01(e1[Z], e1[Y], fez, fey);
+        p0 = e1.z * tmp0.y - e1.y * tmp0.z;
+        p2 = e1.z * tmp2.y - e1.y * tmp2.z;
+        min = min(p0, p2);
+        max = max(p0, p2);
+        rad = fez * extent.y + fey * extent.z;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        //   AXISTEST_Y02(e1[Z], e1[X], fez, fex);
+        p0 = -e1.z * tmp0.x + e1.x * tmp0.z;
+        p2 = -e1.z * tmp2.x + e1.x * tmp2.z;
+        min = min(p0, p2);
+        max = max(p0, p2);
+        rad = fez * extent.x + fex * extent.z;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        // AXISTEST_Z0(e1[Y], e1[X], fey, fex);
+        p0 = e1.y * tmp0.x - e1.x * tmp0.y;
+        p1 = e1.y * tmp1.x - e1.x * tmp1.y;
+        min = min(p0, p1);
+        max = max(p0, p1);
+        rad = fey * extent.x + fex * extent.y;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+//
+        fex = FastMath.abs(e2.x);
+        fey = FastMath.abs(e2.y);
+        fez = FastMath.abs(e2.z);
+
+        // AXISTEST_X2(e2[Z], e2[Y], fez, fey);
+        p0 = e2.z * tmp0.y - e2.y * tmp0.z;
+        p1 = e2.z * tmp1.y - e2.y * tmp1.z;
+        min = min(p0, p1);
+        max = max(p0, p1);
+        rad = fez * extent.y + fey * extent.z;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        // AXISTEST_Y1(e2[Z], e2[X], fez, fex);
+        p0 = -e2.z * tmp0.x + e2.x * tmp0.z;
+        p1 = -e2.z * tmp1.x + e2.x * tmp1.z;
+        min = min(p0, p1);
+        max = max(p0, p1);
+        rad = fez * extent.x + fex * extent.y;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+//   AXISTEST_Z12(e2[Y], e2[X], fey, fex);
+        p1 = e2.y * tmp1.x - e2.x * tmp1.y;
+        p2 = e2.y * tmp2.x - e2.x * tmp2.y;
+        min = min(p1, p2);
+        max = max(p1, p2);
+        rad = fey * extent.x + fex * extent.y;
+        if (min > rad || max < -rad) {
+            vars.release();
+            return false;
+        }
+
+        //  Bullet 1:
+        //  first test overlap in the {x,y,z}-directions
+        //  find min, max of the triangle each direction, and test for overlap in
+        //  that direction -- this is equivalent to testing a minimal AABB around
+        //  the triangle against the AABB
+
+
+        Vector3f minMax = vars.vect7;
+
+        // test in X-direction
+        findMinMax(tmp0.x, tmp1.x, tmp2.x, minMax);
+        if (minMax.x > extent.x || minMax.y < -extent.x) {
+            vars.release();
+            return false;
+        }
+
+        // test in Y-direction
+        findMinMax(tmp0.y, tmp1.y, tmp2.y, minMax);
+        if (minMax.x > extent.y || minMax.y < -extent.y) {
+            vars.release();
+            return false;
+        }
+
+        // test in Z-direction
+        findMinMax(tmp0.z, tmp1.z, tmp2.z, minMax);
+        if (minMax.x > extent.z || minMax.y < -extent.z) {
+            vars.release();
+            return false;
+        }
+
+//       // Bullet 2:
+//       //  test if the box intersects the plane of the triangle
+//       //  compute plane equation of triangle: normal * x + d = 0
+//        Vector3f normal = new Vector3f();
+//        e0.cross(e1, normal);
+        Plane p = vars.plane;
+
+        p.setPlanePoints(v1, v2, v3);
+        if (bbox.whichSide(p) == Plane.Side.Negative) {
+            vars.release();
+            return false;
+        }
+//
+//        if(!planeBoxOverlap(normal,v0,boxhalfsize)) return false;
+
+        vars.release();
+
+        return true;   /* box and triangle overlaps */
+    }
+}
diff --git a/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java b/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java
new file mode 100644
index 0000000..f383a94
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java
@@ -0,0 +1,1522 @@
+/*

+ * Copyright (c) 2009-2010 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.bounding;

+

+/**

+ * NOTE: This class has been commented out as it has too many dependencies.

+ */

+

+

+//

+//import java.io.IOException;

+//import java.nio.FloatBuffer;

+//

+////import com.jme.scene.TriMesh;

+//

+///**

+// * Started Date: Sep 5, 2004 <br>

+// * <br>

+// *

+// * @author Jack Lindamood

+// * @author Joshua Slack (alterations for .9)

+// * @version $Id: OrientedBoundingBox.java,v 1.35 2007/09/21 15:45:31 nca Exp $

+// */

+//public class OrientedBoundingBox extends BoundingVolume {

+//

+//    private static final long serialVersionUID = 1L;

+//

+//    static private final Vector3f _compVect3 = new Vector3f();

+//

+//    static private final Vector3f _compVect4 = new Vector3f();

+//

+//    static private final Vector3f _compVect5 = new Vector3f();

+//

+//    static private final Vector3f _compVect6 = new Vector3f();

+//

+//    static private final Vector3f _compVect7 = new Vector3f();

+//

+//    static private final Vector3f _compVect8 = new Vector3f();

+//

+//    static private final Vector3f _compVect9 = new Vector3f();

+//

+//    static private final Vector3f _compVect10 = new Vector3f();

+//

+//    static private final Vector3f tempVe = new Vector3f();

+//

+//    static private final Matrix3f tempMa = new Matrix3f();

+//

+//    static private final Quaternion tempQa = new Quaternion();

+//

+//    static private final Quaternion tempQb = new Quaternion();

+//

+//    private static final float[] fWdU = new float[3];

+//

+//    private static final float[] fAWdU = new float[3];

+//

+//    private static final float[] fDdU = new float[3];

+//

+//    private static final float[] fADdU = new float[3];

+//

+//    private static final float[] fAWxDdU = new float[3];

+//

+//    private static final float[] tempFa = new float[3];

+//

+//    private static final float[] tempFb = new float[3];

+//

+//    /** X axis of the Oriented Box. */

+//    public final Vector3f xAxis = new Vector3f(1, 0, 0);

+//

+//    /** Y axis of the Oriented Box. */

+//    public final Vector3f yAxis = new Vector3f(0, 1, 0);

+//

+//    /** Z axis of the Oriented Box. */

+//    public final Vector3f zAxis = new Vector3f(0, 0, 1);

+//

+//    /** Extents of the box along the x,y,z axis. */

+//    public final Vector3f extent = new Vector3f(0, 0, 0);

+//

+//    /** Vector array used to store the array of 8 corners the box has. */

+//    public final Vector3f[] vectorStore = new Vector3f[8];

+//

+//    private final Vector3f tempVk = new Vector3f();

+//    private final Vector3f tempForword = new Vector3f(0, 0, 1);

+//    private final Vector3f tempLeft = new Vector3f(1, 0, 0);

+//    private final Vector3f tempUp = new Vector3f(0, 1, 0);

+//

+//    static private final FloatBuffer _mergeBuf = BufferUtils

+//            .createVector3Buffer(16);

+//

+//    /**

+//     * If true, the box's vectorStore array correctly represents the box's

+//     * corners.

+//     */

+//    public boolean correctCorners = false;

+//

+//    public OrientedBoundingBox() {

+//        for (int x = 0; x < 8; x++)

+//            vectorStore[x] = new Vector3f();

+//    }

+//

+//    public Type getType() {

+//        return Type.OBB;

+//    }

+//

+//    public BoundingVolume transform(Quaternion rotate, Vector3f translate,

+//            Vector3f scale, BoundingVolume store) {

+//        rotate.toRotationMatrix(tempMa);

+//        return transform(tempMa, translate, scale, store);

+//    }

+//

+//    public BoundingVolume transform(Matrix3f rotate, Vector3f translate,

+//            Vector3f scale, BoundingVolume store) {

+//        if (store == null || store.getType() != Type.OBB) {

+//            store = new OrientedBoundingBox();

+//        }

+//        OrientedBoundingBox toReturn = (OrientedBoundingBox) store;

+//        toReturn.extent.set(FastMath.abs(extent.x * scale.x),

+//                FastMath.abs(extent.y * scale.y),

+//                FastMath.abs(extent.z * scale.z));

+//        rotate.mult(xAxis, toReturn.xAxis);

+//        rotate.mult(yAxis, toReturn.yAxis);

+//        rotate.mult(zAxis, toReturn.zAxis);

+//        center.mult(scale, toReturn.center);

+//        rotate.mult(toReturn.center, toReturn.center);

+//        toReturn.center.addLocal(translate);

+//        toReturn.correctCorners = false;

+//        return toReturn;

+//    }

+//

+//    public int whichSide(Plane plane) {

+//        float fRadius = FastMath.abs(extent.x * (plane.getNormal().dot(xAxis)))

+//                + FastMath.abs(extent.y * (plane.getNormal().dot(yAxis)))

+//                + FastMath.abs(extent.z * (plane.getNormal().dot(zAxis)));

+//        float fDistance = plane.pseudoDistance(center);

+//        if (fDistance <= -fRadius)

+//            return Plane.NEGATIVE_SIDE;

+//        else if (fDistance >= fRadius)

+//            return Plane.POSITIVE_SIDE;

+//        else

+//           return Plane.NO_SIDE;

+//    }

+//

+//    public void computeFromPoints(FloatBuffer points) {

+//        containAABB(points);

+//    }

+//

+//    /**

+//     * Calculates an AABB of the given point values for this OBB.

+//     *

+//     * @param points

+//     *            The points this OBB should contain.

+//     */

+//    private void containAABB(FloatBuffer points) {

+//        if (points == null || points.limit() <= 2) { // we need at least a 3

+//            // float vector

+//            return;

+//        }

+//

+//        BufferUtils.populateFromBuffer(_compVect1, points, 0);

+//        float minX = _compVect1.x, minY = _compVect1.y, minZ = _compVect1.z;

+//        float maxX = _compVect1.x, maxY = _compVect1.y, maxZ = _compVect1.z;

+//

+//        for (int i = 1, len = points.limit() / 3; i < len; i++) {

+//            BufferUtils.populateFromBuffer(_compVect1, points, i);

+//

+//            if (_compVect1.x < minX)

+//                minX = _compVect1.x;

+//            else if (_compVect1.x > maxX)

+//                maxX = _compVect1.x;

+//

+//            if (_compVect1.y < minY)

+//                minY = _compVect1.y;

+//            else if (_compVect1.y > maxY)

+//                maxY = _compVect1.y;

+//

+//            if (_compVect1.z < minZ)

+//                minZ = _compVect1.z;

+//            else if (_compVect1.z > maxZ)

+//                maxZ = _compVect1.z;

+//        }

+//

+//        center.set(minX + maxX, minY + maxY, minZ + maxZ);

+//        center.multLocal(0.5f);

+//

+//        extent.set(maxX - center.x, maxY - center.y, maxZ - center.z);

+//

+//        xAxis.set(1, 0, 0);

+//        yAxis.set(0, 1, 0);

+//        zAxis.set(0, 0, 1);

+//

+//        correctCorners = false;

+//    }

+//

+//    public BoundingVolume merge(BoundingVolume volume) {

+//        // clone ourselves into a new bounding volume, then merge.

+//        return clone(new OrientedBoundingBox()).mergeLocal(volume);

+//    }

+//

+//    public BoundingVolume mergeLocal(BoundingVolume volume) {

+//        if (volume == null)

+//            return this;

+//

+//        switch (volume.getType()) {

+//

+//            case OBB: {

+//                return mergeOBB((OrientedBoundingBox) volume);

+//            }

+//

+//            case AABB: {

+//                return mergeAABB((BoundingBox) volume);

+//            }

+//

+//            case Sphere: {

+//                return mergeSphere((BoundingSphere) volume);

+//            }

+//

+//            default:

+//                return null;

+//

+//        }

+//    }

+//

+//    private BoundingVolume mergeSphere(BoundingSphere volume) {

+//        BoundingSphere mergeSphere = volume;

+//        if (!correctCorners)

+//            this.computeCorners();

+//

+//        _mergeBuf.rewind();

+//        for (int i = 0; i < 8; i++) {

+//            _mergeBuf.put(vectorStore[i].x);

+//            _mergeBuf.put(vectorStore[i].y);

+//            _mergeBuf.put(vectorStore[i].z);

+//        }

+//        _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(

+//                mergeSphere.center.y + mergeSphere.radius).put(

+//                mergeSphere.center.z + mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(

+//                mergeSphere.center.y + mergeSphere.radius).put(

+//                mergeSphere.center.z + mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(

+//                mergeSphere.center.y - mergeSphere.radius).put(

+//                mergeSphere.center.z + mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(

+//                mergeSphere.center.y + mergeSphere.radius).put(

+//                mergeSphere.center.z - mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(

+//                mergeSphere.center.y - mergeSphere.radius).put(

+//                mergeSphere.center.z + mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(

+//                mergeSphere.center.y + mergeSphere.radius).put(

+//                mergeSphere.center.z - mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(

+//                mergeSphere.center.y - mergeSphere.radius).put(

+//                mergeSphere.center.z - mergeSphere.radius);

+//        _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(

+//                mergeSphere.center.y - mergeSphere.radius).put(

+//                mergeSphere.center.z - mergeSphere.radius);

+//        containAABB(_mergeBuf);

+//        correctCorners = false;

+//        return this;

+//    }

+//

+//    private BoundingVolume mergeAABB(BoundingBox volume) {

+//        BoundingBox mergeBox = volume;

+//        if (!correctCorners)

+//            this.computeCorners();

+//

+//        _mergeBuf.rewind();

+//        for (int i = 0; i < 8; i++) {

+//            _mergeBuf.put(vectorStore[i].x);

+//            _mergeBuf.put(vectorStore[i].y);

+//            _mergeBuf.put(vectorStore[i].z);

+//        }

+//        _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(

+//                mergeBox.center.y + mergeBox.yExtent).put(

+//                mergeBox.center.z + mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(

+//                mergeBox.center.y + mergeBox.yExtent).put(

+//                mergeBox.center.z + mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(

+//                mergeBox.center.y - mergeBox.yExtent).put(

+//                mergeBox.center.z + mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(

+//                mergeBox.center.y + mergeBox.yExtent).put(

+//                mergeBox.center.z - mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(

+//                mergeBox.center.y - mergeBox.yExtent).put(

+//                mergeBox.center.z + mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(

+//                mergeBox.center.y + mergeBox.yExtent).put(

+//                mergeBox.center.z - mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(

+//                mergeBox.center.y - mergeBox.yExtent).put(

+//                mergeBox.center.z - mergeBox.zExtent);

+//        _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(

+//                mergeBox.center.y - mergeBox.yExtent).put(

+//                mergeBox.center.z - mergeBox.zExtent);

+//        containAABB(_mergeBuf);

+//        correctCorners = false;

+//        return this;

+//    }

+//

+//    private BoundingVolume mergeOBB(OrientedBoundingBox volume) {

+//        // OrientedBoundingBox mergeBox=(OrientedBoundingBox) volume;

+//        // if (!correctCorners) this.computeCorners();

+//        // if (!mergeBox.correctCorners) mergeBox.computeCorners();

+//        // Vector3f[] mergeArray=new Vector3f[16];

+//        // for (int i=0;i<vectorStore.length;i++){

+//        // mergeArray[i*2+0]=this .vectorStore[i];

+//        // mergeArray[i*2+1]=mergeBox.vectorStore[i];

+//        // }

+//        // containAABB(mergeArray);

+//        // correctCorners=false;

+//        // return this;

+//        // construct a box that contains the input boxes

+//        // Box3<Real> kBox;

+//        OrientedBoundingBox rkBox0 = this;

+//        OrientedBoundingBox rkBox1 = volume;

+//

+//        // The first guess at the box center. This value will be updated later

+//        // after the input box vertices are projected onto axes determined by an

+//        // average of box axes.

+//        Vector3f kBoxCenter = (rkBox0.center.add(rkBox1.center, _compVect7))

+//                .multLocal(.5f);

+//

+//        // A box's axes, when viewed as the columns of a matrix, form a rotation

+//        // matrix. The input box axes are converted to quaternions. The average

+//        // quaternion is computed, then normalized to unit length. The result is

+//        // the slerp of the two input quaternions with t-value of 1/2. The

+//        // result is converted back to a rotation matrix and its columns are

+//        // selected as the merged box axes.

+//        Quaternion kQ0 = tempQa, kQ1 = tempQb;

+//        kQ0.fromAxes(rkBox0.xAxis, rkBox0.yAxis, rkBox0.zAxis);

+//        kQ1.fromAxes(rkBox1.xAxis, rkBox1.yAxis, rkBox1.zAxis);

+//

+//        if (kQ0.dot(kQ1) < 0.0f)

+//            kQ1.negate();

+//

+//        Quaternion kQ = kQ0.addLocal(kQ1);

+//        kQ.normalize();

+//

+//        Matrix3f kBoxaxis = kQ.toRotationMatrix(tempMa);

+//        Vector3f newXaxis = kBoxaxis.getColumn(0, _compVect8);

+//        Vector3f newYaxis = kBoxaxis.getColumn(1, _compVect9);

+//        Vector3f newZaxis = kBoxaxis.getColumn(2, _compVect10);

+//

+//        // Project the input box vertices onto the merged-box axes. Each axis

+//        // D[i] containing the current center C has a minimum projected value

+//        // pmin[i] and a maximum projected value pmax[i]. The corresponding end

+//        // points on the axes are C+pmin[i]*D[i] and C+pmax[i]*D[i]. The point C

+//        // is not necessarily the midpoint for any of the intervals. The actual

+//        // box center will be adjusted from C to a point C' that is the midpoint

+//        // of each interval,

+//        // C' = C + sum_{i=0}^1 0.5*(pmin[i]+pmax[i])*D[i]

+//        // The box extents are

+//        // e[i] = 0.5*(pmax[i]-pmin[i])

+//

+//        int i;

+//        float fDot;

+//        Vector3f kDiff = _compVect4;

+//        Vector3f kMin = _compVect5;

+//        Vector3f kMax = _compVect6;

+//        kMin.zero();

+//        kMax.zero();

+//

+//        if (!rkBox0.correctCorners)

+//            rkBox0.computeCorners();

+//        for (i = 0; i < 8; i++) {

+//            rkBox0.vectorStore[i].subtract(kBoxCenter, kDiff);

+//

+//            fDot = kDiff.dot(newXaxis);

+//            if (fDot > kMax.x)

+//                kMax.x = fDot;

+//            else if (fDot < kMin.x)

+//                kMin.x = fDot;

+//

+//            fDot = kDiff.dot(newYaxis);

+//            if (fDot > kMax.y)

+//                kMax.y = fDot;

+//            else if (fDot < kMin.y)

+//                kMin.y = fDot;

+//

+//            fDot = kDiff.dot(newZaxis);

+//            if (fDot > kMax.z)

+//                kMax.z = fDot;

+//            else if (fDot < kMin.z)

+//                kMin.z = fDot;

+//

+//        }

+//

+//        if (!rkBox1.correctCorners)

+//            rkBox1.computeCorners();

+//        for (i = 0; i < 8; i++) {

+//            rkBox1.vectorStore[i].subtract(kBoxCenter, kDiff);

+//

+//            fDot = kDiff.dot(newXaxis);

+//            if (fDot > kMax.x)

+//                kMax.x = fDot;

+//            else if (fDot < kMin.x)

+//                kMin.x = fDot;

+//

+//            fDot = kDiff.dot(newYaxis);

+//            if (fDot > kMax.y)

+//                kMax.y = fDot;

+//            else if (fDot < kMin.y)

+//                kMin.y = fDot;

+//

+//            fDot = kDiff.dot(newZaxis);

+//            if (fDot > kMax.z)

+//                kMax.z = fDot;

+//            else if (fDot < kMin.z)

+//                kMin.z = fDot;

+//        }

+//

+//        this.xAxis.set(newXaxis);

+//        this.yAxis.set(newYaxis);

+//        this.zAxis.set(newZaxis);

+//

+//        this.extent.x = .5f * (kMax.x - kMin.x);

+//        kBoxCenter.addLocal(this.xAxis.mult(.5f * (kMax.x + kMin.x), tempVe));

+//

+//        this.extent.y = .5f * (kMax.y - kMin.y);

+//        kBoxCenter.addLocal(this.yAxis.mult(.5f * (kMax.y + kMin.y), tempVe));

+//

+//        this.extent.z = .5f * (kMax.z - kMin.z);

+//        kBoxCenter.addLocal(this.zAxis.mult(.5f * (kMax.z + kMin.z), tempVe));

+//

+//        this.center.set(kBoxCenter);

+//

+//        this.correctCorners = false;

+//        return this;

+//    }

+//

+//    public BoundingVolume clone(BoundingVolume store) {

+//        OrientedBoundingBox toReturn;

+//        if (store instanceof OrientedBoundingBox) {

+//            toReturn = (OrientedBoundingBox) store;

+//        } else {

+//            toReturn = new OrientedBoundingBox();

+//        }

+//        toReturn.extent.set(extent);

+//        toReturn.xAxis.set(xAxis);

+//        toReturn.yAxis.set(yAxis);

+//        toReturn.zAxis.set(zAxis);

+//        toReturn.center.set(center);

+//        toReturn.checkPlane = checkPlane;

+//        for (int x = vectorStore.length; --x >= 0; )

+//            toReturn.vectorStore[x].set(vectorStore[x]);

+//        toReturn.correctCorners = this.correctCorners;

+//        return toReturn;

+//    }

+//

+//    /**

+//     * Sets the vectorStore information to the 8 corners of the box.

+//     */

+//    public void computeCorners() {

+//        Vector3f akEAxis0 = xAxis.mult(extent.x, _compVect1);

+//        Vector3f akEAxis1 = yAxis.mult(extent.y, _compVect2);

+//        Vector3f akEAxis2 = zAxis.mult(extent.z, _compVect3);

+//

+//        vectorStore[0].set(center).subtractLocal(akEAxis0).subtractLocal(akEAxis1).subtractLocal(akEAxis2);

+//        vectorStore[1].set(center).addLocal(akEAxis0).subtractLocal(akEAxis1).subtractLocal(akEAxis2);

+//        vectorStore[2].set(center).addLocal(akEAxis0).addLocal(akEAxis1).subtractLocal(akEAxis2);

+//        vectorStore[3].set(center).subtractLocal(akEAxis0).addLocal(akEAxis1).subtractLocal(akEAxis2);

+//        vectorStore[4].set(center).subtractLocal(akEAxis0).subtractLocal(akEAxis1).addLocal(akEAxis2);

+//        vectorStore[5].set(center).addLocal(akEAxis0).subtractLocal(akEAxis1).addLocal(akEAxis2);

+//        vectorStore[6].set(center).addLocal(akEAxis0).addLocal(akEAxis1).addLocal(akEAxis2);

+//        vectorStore[7].set(center).subtractLocal(akEAxis0).addLocal(akEAxis1).addLocal(akEAxis2);

+//        correctCorners = true;

+//    }

+//

+////    public void computeFromTris(int[] indices, TriMesh mesh, int start, int end) {

+////        if (end - start <= 0) {

+////            return;

+////        }

+////        Vector3f[] verts = new Vector3f[3];

+////        Vector3f min = _compVect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));

+////        Vector3f max = _compVect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));

+////        Vector3f point;

+////        for (int i = start; i < end; i++) {

+////        	mesh.getTriangle(indices[i], verts);

+////            point = verts[0];

+////            if (point.x < min.x)

+////                min.x = point.x;

+////            else if (point.x > max.x)

+////                max.x = point.x;

+////            if (point.y < min.y)

+////                min.y = point.y;

+////            else if (point.y > max.y)

+////                max.y = point.y;

+////            if (point.z < min.z)

+////                min.z = point.z;

+////            else if (point.z > max.z)

+////                max.z = point.z;

+////

+////            point = verts[1];

+////            if (point.x < min.x)

+////                min.x = point.x;

+////            else if (point.x > max.x)

+////                max.x = point.x;

+////            if (point.y < min.y)

+////                min.y = point.y;

+////            else if (point.y > max.y)

+////                max.y = point.y;

+////            if (point.z < min.z)

+////                min.z = point.z;

+////            else if (point.z > max.z)

+////                max.z = point.z;

+////

+////            point = verts[2];

+////            if (point.x < min.x)

+////                min.x = point.x;

+////            else if (point.x > max.x)

+////                max.x = point.x;

+////

+////            if (point.y < min.y)

+////                min.y = point.y;

+////            else if (point.y > max.y)

+////                max.y = point.y;

+////

+////            if (point.z < min.z)

+////                min.z = point.z;

+////            else if (point.z > max.z)

+////                max.z = point.z;

+////        }

+////

+////        center.set(min.addLocal(max));

+////        center.multLocal(0.5f);

+////

+////        extent.set(max.x - center.x, max.y - center.y, max.z - center.z);

+////

+////        xAxis.set(1, 0, 0);

+////        yAxis.set(0, 1, 0);

+////        zAxis.set(0, 0, 1);

+////

+////        correctCorners = false;

+////    }

+//

+//    public void computeFromTris(Triangle[] tris, int start, int end) {

+//        if (end - start <= 0) {

+//            return;

+//        }

+//

+//        Vector3f min = _compVect1.set(tris[start].get(0));

+//        Vector3f max = _compVect2.set(min);

+//        Vector3f point;

+//        for (int i = start; i < end; i++) {

+//

+//            point = tris[i].get(0);

+//            if (point.x < min.x)

+//                min.x = point.x;

+//            else if (point.x > max.x)

+//                max.x = point.x;

+//            if (point.y < min.y)

+//                min.y = point.y;

+//            else if (point.y > max.y)

+//                max.y = point.y;

+//            if (point.z < min.z)

+//                min.z = point.z;

+//            else if (point.z > max.z)

+//                max.z = point.z;

+//

+//            point = tris[i].get(1);

+//            if (point.x < min.x)

+//                min.x = point.x;

+//            else if (point.x > max.x)

+//                max.x = point.x;

+//            if (point.y < min.y)

+//                min.y = point.y;

+//            else if (point.y > max.y)

+//                max.y = point.y;

+//            if (point.z < min.z)

+//                min.z = point.z;

+//            else if (point.z > max.z)

+//                max.z = point.z;

+//

+//            point = tris[i].get(2);

+//            if (point.x < min.x)

+//                min.x = point.x;

+//            else if (point.x > max.x)

+//                max.x = point.x;

+//

+//            if (point.y < min.y)

+//                min.y = point.y;

+//            else if (point.y > max.y)

+//                max.y = point.y;

+//

+//            if (point.z < min.z)

+//                min.z = point.z;

+//            else if (point.z > max.z)

+//                max.z = point.z;

+//        }

+//

+//        center.set(min.addLocal(max));

+//        center.multLocal(0.5f);

+//

+//        extent.set(max.x - center.x, max.y - center.y, max.z - center.z);

+//

+//        xAxis.set(1, 0, 0);

+//        yAxis.set(0, 1, 0);

+//        zAxis.set(0, 0, 1);

+//

+//        correctCorners = false;

+//    }

+//

+//    public boolean intersection(OrientedBoundingBox box1) {

+//        // Cutoff for cosine of angles between box axes. This is used to catch

+//        // the cases when at least one pair of axes are parallel. If this

+//        // happens,

+//        // there is no need to test for separation along the Cross(A[i],B[j])

+//        // directions.

+//        OrientedBoundingBox box0 = this;

+//        float cutoff = 0.999999f;

+//        boolean parallelPairExists = false;

+//        int i;

+//

+//        // convenience variables

+//        Vector3f akA[] = new Vector3f[] { box0.xAxis, box0.yAxis, box0.zAxis };

+//        Vector3f[] akB = new Vector3f[] { box1.xAxis, box1.yAxis, box1.zAxis };

+//        Vector3f afEA = box0.extent;

+//        Vector3f afEB = box1.extent;

+//

+//        // compute difference of box centers, D = C1-C0

+//        Vector3f kD = box1.center.subtract(box0.center, _compVect1);

+//

+//        float[][] aafC = { fWdU, fAWdU, fDdU };

+//

+//        float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };

+//

+//        float[] afAD = tempFb;

+//        float fR0, fR1, fR; // interval radii and distance between centers

+//        float fR01; // = R0 + R1

+//

+//        // axis C0+t*A0

+//        for (i = 0; i < 3; i++) {

+//            aafC[0][i] = akA[0].dot(akB[i]);

+//            aafAbsC[0][i] = FastMath.abs(aafC[0][i]);

+//            if (aafAbsC[0][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[0] = akA[0].dot(kD);

+//        fR = FastMath.abs(afAD[0]);

+//        fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z

+//                * aafAbsC[0][2];

+//        fR01 = afEA.x + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1

+//        for (i = 0; i < 3; i++) {

+//            aafC[1][i] = akA[1].dot(akB[i]);

+//            aafAbsC[1][i] = FastMath.abs(aafC[1][i]);

+//            if (aafAbsC[1][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[1] = akA[1].dot(kD);

+//        fR = FastMath.abs(afAD[1]);

+//        fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z

+//                * aafAbsC[1][2];

+//        fR01 = afEA.y + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2

+//        for (i = 0; i < 3; i++) {

+//            aafC[2][i] = akA[2].dot(akB[i]);

+//            aafAbsC[2][i] = FastMath.abs(aafC[2][i]);

+//            if (aafAbsC[2][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[2] = akA[2].dot(kD);

+//        fR = FastMath.abs(afAD[2]);

+//        fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z

+//                * aafAbsC[2][2];

+//        fR01 = afEA.z + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B0

+//        fR = FastMath.abs(akB[0].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z

+//                * aafAbsC[2][0];

+//        fR01 = fR0 + afEB.x;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B1

+//        fR = FastMath.abs(akB[1].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z

+//                * aafAbsC[2][1];

+//        fR01 = fR0 + afEB.y;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B2

+//        fR = FastMath.abs(akB[2].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z

+//                * aafAbsC[2][2];

+//        fR01 = fR0 + afEB.z;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // At least one pair of box axes was parallel, so the separation is

+//        // effectively in 2D where checking the "edge" normals is sufficient for

+//        // the separation of the boxes.

+//        if (parallelPairExists) {

+//            return true;

+//        }

+//

+//        // axis C0+t*A0xB0

+//        fR = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);

+//        fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];

+//        fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB1

+//        fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);

+//        fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];

+//        fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB2

+//        fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);

+//        fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];

+//        fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB0

+//        fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);

+//        fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB1

+//        fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);

+//        fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB2

+//        fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);

+//        fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB0

+//        fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);

+//        fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB1

+//        fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);

+//        fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB2

+//        fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);

+//        fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        return true;

+//    }

+//

+//    /*

+//     * (non-Javadoc)

+//     *

+//     * @see com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume)

+//     */

+//    public boolean intersects(BoundingVolume bv) {

+//        if (bv == null)

+//            return false;

+//

+//        return bv.intersectsOrientedBoundingBox(this);

+//    }

+//

+//    /*

+//     * (non-Javadoc)

+//     *

+//     * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.bounding.BoundingSphere)

+//     */

+//    public boolean intersectsSphere(BoundingSphere bs) {

+//        if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(bs.center)) return false;

+//

+//        _compVect1.set(bs.getCenter()).subtractLocal(center);

+//        tempMa.fromAxes(xAxis, yAxis, zAxis);

+//

+//        tempMa.mult(_compVect1, _compVect2);

+//

+//        if (FastMath.abs(_compVect2.x) < bs.getRadius() + extent.x

+//                && FastMath.abs(_compVect2.y) < bs.getRadius() + extent.y

+//                && FastMath.abs(_compVect2.z) < bs.getRadius() + extent.z)

+//            return true;

+//

+//        return false;

+//    }

+//

+//    /*

+//     * (non-Javadoc)

+//     *

+//     * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)

+//     */

+//    public boolean intersectsBoundingBox(BoundingBox bb) {

+//        if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(bb.center)) return false;

+//

+//        // Cutoff for cosine of angles between box axes. This is used to catch

+//        // the cases when at least one pair of axes are parallel. If this

+//        // happens,

+//        // there is no need to test for separation along the Cross(A[i],B[j])

+//        // directions.

+//        float cutoff = 0.999999f;

+//        boolean parallelPairExists = false;

+//        int i;

+//

+//        // convenience variables

+//        Vector3f akA[] = new Vector3f[] { xAxis, yAxis, zAxis };

+//        Vector3f[] akB = new Vector3f[] { tempForword, tempLeft, tempUp };

+//        Vector3f afEA = extent;

+//        Vector3f afEB = tempVk.set(bb.xExtent, bb.yExtent, bb.zExtent);

+//

+//        // compute difference of box centers, D = C1-C0

+//        Vector3f kD = bb.getCenter().subtract(center, _compVect1);

+//

+//        float[][] aafC = { fWdU, fAWdU, fDdU };

+//

+//        float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };

+//

+//        float[] afAD = tempFb;

+//        float fR0, fR1, fR; // interval radii and distance between centers

+//        float fR01; // = R0 + R1

+//

+//        // axis C0+t*A0

+//        for (i = 0; i < 3; i++) {

+//            aafC[0][i] = akA[0].dot(akB[i]);

+//            aafAbsC[0][i] = FastMath.abs(aafC[0][i]);

+//            if (aafAbsC[0][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[0] = akA[0].dot(kD);

+//        fR = FastMath.abs(afAD[0]);

+//        fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z

+//                * aafAbsC[0][2];

+//        fR01 = afEA.x + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1

+//        for (i = 0; i < 3; i++) {

+//            aafC[1][i] = akA[1].dot(akB[i]);

+//            aafAbsC[1][i] = FastMath.abs(aafC[1][i]);

+//            if (aafAbsC[1][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[1] = akA[1].dot(kD);

+//        fR = FastMath.abs(afAD[1]);

+//        fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z

+//                * aafAbsC[1][2];

+//        fR01 = afEA.y + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2

+//        for (i = 0; i < 3; i++) {

+//            aafC[2][i] = akA[2].dot(akB[i]);

+//            aafAbsC[2][i] = FastMath.abs(aafC[2][i]);

+//            if (aafAbsC[2][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[2] = akA[2].dot(kD);

+//        fR = FastMath.abs(afAD[2]);

+//        fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z

+//                * aafAbsC[2][2];

+//        fR01 = afEA.z + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B0

+//        fR = FastMath.abs(akB[0].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z

+//                * aafAbsC[2][0];

+//        fR01 = fR0 + afEB.x;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B1

+//        fR = FastMath.abs(akB[1].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z

+//                * aafAbsC[2][1];

+//        fR01 = fR0 + afEB.y;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B2

+//        fR = FastMath.abs(akB[2].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z

+//                * aafAbsC[2][2];

+//        fR01 = fR0 + afEB.z;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // At least one pair of box axes was parallel, so the separation is

+//        // effectively in 2D where checking the "edge" normals is sufficient for

+//        // the separation of the boxes.

+//        if (parallelPairExists) {

+//            return true;

+//        }

+//

+//        // axis C0+t*A0xB0

+//        fR = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);

+//        fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];

+//        fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB1

+//        fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);

+//        fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];

+//        fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB2

+//        fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);

+//        fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];

+//        fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB0

+//        fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);

+//        fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB1

+//        fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);

+//        fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB2

+//        fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);

+//        fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB0

+//        fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);

+//        fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB1

+//        fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);

+//        fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB2

+//        fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);

+//        fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        return true;

+//    }

+//

+//    /*

+//     * (non-Javadoc)

+//     *

+//     * @see com.jme.bounding.BoundingVolume#intersectsOBB2(com.jme.bounding.OBB2)

+//     */

+//    public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {

+//        if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(obb.center)) return false;

+//

+//        // Cutoff for cosine of angles between box axes. This is used to catch

+//        // the cases when at least one pair of axes are parallel. If this

+//        // happens,

+//        // there is no need to test for separation along the Cross(A[i],B[j])

+//        // directions.

+//        float cutoff = 0.999999f;

+//        boolean parallelPairExists = false;

+//        int i;

+//

+//        // convenience variables

+//        Vector3f akA[] = new Vector3f[] { xAxis, yAxis, zAxis };

+//        Vector3f[] akB = new Vector3f[] { obb.xAxis, obb.yAxis, obb.zAxis };

+//        Vector3f afEA = extent;

+//        Vector3f afEB = obb.extent;

+//

+//        // compute difference of box centers, D = C1-C0

+//        Vector3f kD = obb.center.subtract(center, _compVect1);

+//

+//        float[][] aafC = { fWdU, fAWdU, fDdU };

+//

+//        float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };

+//

+//        float[] afAD = tempFb;

+//        float fR0, fR1, fR; // interval radii and distance between centers

+//        float fR01; // = R0 + R1

+//

+//        // axis C0+t*A0

+//        for (i = 0; i < 3; i++) {

+//            aafC[0][i] = akA[0].dot(akB[i]);

+//            aafAbsC[0][i] = FastMath.abs(aafC[0][i]);

+//            if (aafAbsC[0][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[0] = akA[0].dot(kD);

+//        fR = FastMath.abs(afAD[0]);

+//        fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z

+//                * aafAbsC[0][2];

+//        fR01 = afEA.x + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1

+//        for (i = 0; i < 3; i++) {

+//            aafC[1][i] = akA[1].dot(akB[i]);

+//            aafAbsC[1][i] = FastMath.abs(aafC[1][i]);

+//            if (aafAbsC[1][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[1] = akA[1].dot(kD);

+//        fR = FastMath.abs(afAD[1]);

+//        fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z

+//                * aafAbsC[1][2];

+//        fR01 = afEA.y + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2

+//        for (i = 0; i < 3; i++) {

+//            aafC[2][i] = akA[2].dot(akB[i]);

+//            aafAbsC[2][i] = FastMath.abs(aafC[2][i]);

+//            if (aafAbsC[2][i] > cutoff) {

+//                parallelPairExists = true;

+//            }

+//        }

+//        afAD[2] = akA[2].dot(kD);

+//        fR = FastMath.abs(afAD[2]);

+//        fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z

+//                * aafAbsC[2][2];

+//        fR01 = afEA.z + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B0

+//        fR = FastMath.abs(akB[0].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z

+//                * aafAbsC[2][0];

+//        fR01 = fR0 + afEB.x;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B1

+//        fR = FastMath.abs(akB[1].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z

+//                * aafAbsC[2][1];

+//        fR01 = fR0 + afEB.y;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*B2

+//        fR = FastMath.abs(akB[2].dot(kD));

+//        fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z

+//                * aafAbsC[2][2];

+//        fR01 = fR0 + afEB.z;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // At least one pair of box axes was parallel, so the separation is

+//        // effectively in 2D where checking the "edge" normals is sufficient for

+//        // the separation of the boxes.

+//        if (parallelPairExists) {

+//            return true;

+//        }

+//

+//        // axis C0+t*A0xB0

+//        fR = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);

+//        fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];

+//        fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB1

+//        fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);

+//        fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];

+//        fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A0xB2

+//        fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);

+//        fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];

+//        fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB0

+//        fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);

+//        fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB1

+//        fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);

+//        fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A1xB2

+//        fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);

+//        fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB0

+//        fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);

+//        fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];

+//        fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB1

+//        fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);

+//        fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];

+//        fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        // axis C0+t*A2xB2

+//        fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);

+//        fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];

+//        fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];

+//        fR01 = fR0 + fR1;

+//        if (fR > fR01) {

+//            return false;

+//        }

+//

+//        return true;

+//    }

+//

+//    /*

+//     * (non-Javadoc)

+//     *

+//     * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray)

+//     */

+//    public boolean intersects(Ray ray) {

+//        if (!Vector3f.isValidVector(center)) return false;

+//

+//        float rhs;

+//        Vector3f diff = ray.origin.subtract(getCenter(_compVect2), _compVect1);

+//

+//        fWdU[0] = ray.getDirection().dot(xAxis);

+//        fAWdU[0] = FastMath.abs(fWdU[0]);

+//        fDdU[0] = diff.dot(xAxis);

+//        fADdU[0] = FastMath.abs(fDdU[0]);

+//        if (fADdU[0] > extent.x && fDdU[0] * fWdU[0] >= 0.0) {

+//            return false;

+//        }

+//

+//        fWdU[1] = ray.getDirection().dot(yAxis);

+//        fAWdU[1] = FastMath.abs(fWdU[1]);

+//        fDdU[1] = diff.dot(yAxis);

+//        fADdU[1] = FastMath.abs(fDdU[1]);

+//        if (fADdU[1] > extent.y && fDdU[1] * fWdU[1] >= 0.0) {

+//            return false;

+//        }

+//

+//        fWdU[2] = ray.getDirection().dot(zAxis);

+//        fAWdU[2] = FastMath.abs(fWdU[2]);

+//        fDdU[2] = diff.dot(zAxis);

+//        fADdU[2] = FastMath.abs(fDdU[2]);

+//        if (fADdU[2] > extent.z && fDdU[2] * fWdU[2] >= 0.0) {

+//            return false;

+//        }

+//

+//        Vector3f wCrossD = ray.getDirection().cross(diff, _compVect2);

+//

+//        fAWxDdU[0] = FastMath.abs(wCrossD.dot(xAxis));

+//        rhs = extent.y * fAWdU[2] + extent.z * fAWdU[1];

+//        if (fAWxDdU[0] > rhs) {

+//            return false;

+//        }

+//

+//        fAWxDdU[1] = FastMath.abs(wCrossD.dot(yAxis));

+//        rhs = extent.x * fAWdU[2] + extent.z * fAWdU[0];

+//        if (fAWxDdU[1] > rhs) {

+//            return false;

+//        }

+//

+//        fAWxDdU[2] = FastMath.abs(wCrossD.dot(zAxis));

+//        rhs = extent.x * fAWdU[1] + extent.y * fAWdU[0];

+//        if (fAWxDdU[2] > rhs) {

+//            return false;

+//

+//        }

+//

+//        return true;

+//    }

+//

+//    /**

+//     * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)

+//     */

+//    public IntersectionRecord intersectsWhere(Ray ray) {

+//        Vector3f diff = _compVect1.set(ray.origin).subtractLocal(center);

+//        // convert ray to box coordinates

+//        Vector3f direction = _compVect2.set(ray.direction.x, ray.direction.y,

+//                ray.direction.z);

+//        float[] t = { 0f, Float.POSITIVE_INFINITY };

+//

+//        float saveT0 = t[0], saveT1 = t[1];

+//        boolean notEntirelyClipped = clip(+direction.x, -diff.x - extent.x, t)

+//                && clip(-direction.x, +diff.x - extent.x, t)

+//                && clip(+direction.y, -diff.y - extent.y, t)

+//                && clip(-direction.y, +diff.y - extent.y, t)

+//                && clip(+direction.z, -diff.z - extent.z, t)

+//                && clip(-direction.z, +diff.z - extent.z, t);

+//

+//        if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) {

+//            if (t[1] > t[0]) {

+//                float[] distances = t;

+//                Vector3f[] points = new Vector3f[] {

+//                        new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),

+//                        new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin)

+//                        };

+//                IntersectionRecord record = new IntersectionRecord(distances, points);

+//                return record;

+//            }

+//

+//            float[] distances = new float[] { t[0] };

+//            Vector3f[] points = new Vector3f[] {

+//                    new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),

+//                    };

+//            IntersectionRecord record = new IntersectionRecord(distances, points);

+//            return record;

+//        }

+//

+//        return new IntersectionRecord();

+//

+//    }

+//

+//    /**

+//     * <code>clip</code> determines if a line segment intersects the current

+//     * test plane.

+//     *

+//     * @param denom

+//     *            the denominator of the line segment.

+//     * @param numer

+//     *            the numerator of the line segment.

+//     * @param t

+//     *            test values of the plane.

+//     * @return true if the line segment intersects the plane, false otherwise.

+//     */

+//    private boolean clip(float denom, float numer, float[] t) {

+//        // Return value is 'true' if line segment intersects the current test

+//        // plane. Otherwise 'false' is returned in which case the line segment

+//        // is entirely clipped.

+//        if (denom > 0.0f) {

+//            if (numer > denom * t[1])

+//                return false;

+//            if (numer > denom * t[0])

+//                t[0] = numer / denom;

+//            return true;

+//        } else if (denom < 0.0f) {

+//            if (numer > denom * t[0])

+//                return false;

+//            if (numer > denom * t[1])

+//                t[1] = numer / denom;

+//            return true;

+//        } else {

+//            return numer <= 0.0;

+//        }

+//    }

+//

+//    public void setXAxis(Vector3f axis) {

+//        xAxis.set(axis);

+//        correctCorners = false;

+//    }

+//

+//    public void setYAxis(Vector3f axis) {

+//        yAxis.set(axis);

+//        correctCorners = false;

+//    }

+//

+//    public void setZAxis(Vector3f axis) {

+//        zAxis.set(axis);

+//        correctCorners = false;

+//    }

+//

+//    public void setExtent(Vector3f ext) {

+//        extent.set(ext);

+//        correctCorners = false;

+//    }

+//

+//    public Vector3f getXAxis() {

+//        return xAxis;

+//    }

+//

+//    public Vector3f getYAxis() {

+//        return yAxis;

+//    }

+//

+//    public Vector3f getZAxis() {

+//        return zAxis;

+//    }

+//

+//    public Vector3f getExtent() {

+//        return extent;

+//    }

+//

+//    @Override

+//    public boolean contains(Vector3f point) {

+//        _compVect1.set(point).subtractLocal(center);

+//        float coeff = _compVect1.dot(xAxis);

+//        if (FastMath.abs(coeff) > extent.x) return false;

+//

+//        coeff = _compVect1.dot(yAxis);

+//        if (FastMath.abs(coeff) > extent.y) return false;

+//

+//        coeff = _compVect1.dot(zAxis);

+//        if (FastMath.abs(coeff) > extent.z) return false;

+//

+//        return true;

+//    }

+//

+//    @Override

+//    public float distanceToEdge(Vector3f point) {

+//        // compute coordinates of point in box coordinate system

+//        Vector3f diff = point.subtract(center);

+//        Vector3f closest = new Vector3f(diff.dot(xAxis), diff.dot(yAxis), diff

+//                .dot(zAxis));

+//

+//        // project test point onto box

+//        float sqrDistance = 0.0f;

+//        float delta;

+//

+//        if (closest.x < -extent.x) {

+//            delta = closest.x + extent.x;

+//            sqrDistance += delta * delta;

+//            closest.x = -extent.x;

+//        } else if (closest.x > extent.x) {

+//            delta = closest.x - extent.x;

+//            sqrDistance += delta * delta;

+//            closest.x = extent.x;

+//        }

+//

+//        if (closest.y < -extent.y) {

+//            delta = closest.y + extent.y;

+//            sqrDistance += delta * delta;

+//            closest.y = -extent.y;

+//        } else if (closest.y > extent.y) {

+//            delta = closest.y - extent.y;

+//            sqrDistance += delta * delta;

+//            closest.y = extent.y;

+//        }

+//

+//        if (closest.z < -extent.z) {

+//            delta = closest.z + extent.z;

+//            sqrDistance += delta * delta;

+//            closest.z = -extent.z;

+//        } else if (closest.z > extent.z) {

+//            delta = closest.z - extent.z;

+//            sqrDistance += delta * delta;

+//            closest.z = extent.z;

+//        }

+//

+//        return FastMath.sqrt(sqrDistance);

+//    }

+//

+//    public void write(JMEExporter e) throws IOException {

+//        super.write(e);

+//        OutputCapsule capsule = e.getCapsule(this);

+//        capsule.write(xAxis, "xAxis", Vector3f.UNIT_X);

+//        capsule.write(yAxis, "yAxis", Vector3f.UNIT_Y);

+//        capsule.write(zAxis, "zAxis", Vector3f.UNIT_Z);

+//        capsule.write(extent, "extent", Vector3f.ZERO);

+//    }

+//

+//    public void read(JMEImporter e) throws IOException {

+//        super.read(e);

+//        InputCapsule capsule = e.getCapsule(this);

+//        xAxis.set((Vector3f) capsule.readSavable("xAxis", Vector3f.UNIT_X.clone()));

+//        yAxis.set((Vector3f) capsule.readSavable("yAxis", Vector3f.UNIT_Y.clone()));

+//        zAxis.set((Vector3f) capsule.readSavable("zAxis", Vector3f.UNIT_Z.clone()));

+//        extent.set((Vector3f) capsule.readSavable("extent", Vector3f.ZERO.clone()));

+//        correctCorners = false;

+//    }

+//

+//    @Override

+//    public float getVolume() {

+//        return (8*extent.x*extent.y*extent.z);

+//    }

+//}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/cinematic/Cinematic.java b/engine/src/core/com/jme3/cinematic/Cinematic.java
new file mode 100644
index 0000000..3477c22
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/Cinematic.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.app.state.AppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.cinematic.events.AbstractCinematicEvent;
+import com.jme3.cinematic.events.CinematicEvent;
+import com.jme3.cinematic.events.CinematicEventListener;
+import com.jme3.export.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.CameraNode;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ */
+public class Cinematic extends AbstractCinematicEvent implements AppState {
+    
+    private static final Logger logger = Logger.getLogger(Application.class.getName());
+    private Node scene;
+    protected TimeLine timeLine = new TimeLine();
+    private int lastFetchedKeyFrame = -1;
+    private List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>();
+    private Map<String, CameraNode> cameras = new HashMap<String, CameraNode>();
+    private CameraNode currentCam;
+    private boolean initialized = false;
+    private Map<String, Map<String, Object>> eventsData;    
+    
+    public Cinematic() {
+    }
+    
+    public Cinematic(Node scene) {
+        this.scene = scene;
+    }
+    
+    public Cinematic(Node scene, float initialDuration) {
+        super(initialDuration);
+        this.scene = scene;
+    }
+    
+    public Cinematic(Node scene, LoopMode loopMode) {
+        super(loopMode);
+        this.scene = scene;
+    }
+    
+    public Cinematic(Node scene, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.scene = scene;
+    }
+    
+    @Override
+    public void onPlay() {
+        if (isInitialized()) {
+            if (playState == PlayState.Paused) {
+                for (int i = 0; i < cinematicEvents.size(); i++) {
+                    CinematicEvent ce = cinematicEvents.get(i);
+                    if (ce.getPlayState() == PlayState.Paused) {
+                        ce.play();
+                    }
+                }
+            }
+        }
+    }
+    
+    @Override
+    public void onStop() {
+        time = 0;
+        lastFetchedKeyFrame = -1;
+        for (int i = 0; i < cinematicEvents.size(); i++) {
+            CinematicEvent ce = cinematicEvents.get(i);
+            ce.stop();
+        }
+        enableCurrentCam(false);
+    }
+    
+    @Override
+    public void onPause() {
+        for (int i = 0; i < cinematicEvents.size(); i++) {
+            CinematicEvent ce = cinematicEvents.get(i);
+            if (ce.getPlayState() == PlayState.Playing) {
+                ce.pause();
+            }
+        }
+    }
+    
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        
+        oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
+        oc.writeStringSavableMap(cameras, "cameras", null);
+        oc.write(timeLine, "timeLine", null);
+        
+    }
+    
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        
+        cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
+        cameras = (Map<String, CameraNode>) ic.readStringSavableMap("cameras", null);
+        timeLine = (TimeLine) ic.readSavable("timeLine", null);
+    }
+    
+    @Override
+    public void setSpeed(float speed) {
+        super.setSpeed(speed);
+        for (int i = 0; i < cinematicEvents.size(); i++) {
+            CinematicEvent ce = cinematicEvents.get(i);
+            ce.setSpeed(speed);
+        }
+        
+        
+    }
+    
+    public void initialize(AppStateManager stateManager, Application app) {
+        initEvent(app, this);
+        for (CinematicEvent cinematicEvent : cinematicEvents) {
+            cinematicEvent.initEvent(app, this);
+        }
+        
+        initialized = true;
+    }
+    
+    public boolean isInitialized() {
+        return initialized;
+    }
+    
+    public void setEnabled(boolean enabled) {
+        if (enabled) {
+            play();
+        }
+    }
+    
+    public boolean isEnabled() {
+        return playState == PlayState.Playing;
+    }
+    
+    public void stateAttached(AppStateManager stateManager) {
+    }
+    
+    public void stateDetached(AppStateManager stateManager) {
+        stop();
+    }
+    
+    public void update(float tpf) {
+        if (isInitialized()) {
+            internalUpdate(tpf);
+        }
+    }
+    
+    @Override
+    public void onUpdate(float tpf) {        
+        for (int i = 0; i < cinematicEvents.size(); i++) {
+            CinematicEvent ce = cinematicEvents.get(i);
+            ce.internalUpdate(tpf);
+        }
+        
+        int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
+
+        //iterate to make sure every key frame is triggered
+        for (int i = lastFetchedKeyFrame + 1; i <= keyFrameIndex; i++) {
+            KeyFrame keyFrame = timeLine.get(i);
+            if (keyFrame != null) {
+                keyFrame.trigger();
+            }
+        }
+        
+        lastFetchedKeyFrame = keyFrameIndex;
+    }
+    
+    @Override
+    public void setTime(float time) {
+        super.setTime(time);        
+        int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
+
+        //triggering all the event from start to "time" 
+        //then computing timeOffset for each event
+        for (int i = 0; i <= keyFrameIndex; i++) {
+            KeyFrame keyFrame = timeLine.get(i);
+            if (keyFrame != null) {
+                for (CinematicEvent ce : keyFrame.getCinematicEvents()) {
+                    ce.play();                    
+                    ce.setTime(time - timeLine.getKeyFrameTime(keyFrame));
+                }
+            }
+        }
+        if (playState != PlayState.Playing) {
+            pause();
+        }
+
+        //  step();
+    }
+    
+    public KeyFrame addCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) {
+        KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp);
+        if (keyFrame == null) {
+            keyFrame = new KeyFrame();
+            timeLine.addKeyFrameAtTime(timeStamp, keyFrame);
+        }
+        keyFrame.cinematicEvents.add(cinematicEvent);
+        cinematicEvents.add(cinematicEvent);
+        return keyFrame;
+    }
+    
+    public void render(RenderManager rm) {
+    }
+    
+    public void postRender() {
+    }
+    
+    public void cleanup() {
+    }
+
+    /**
+     * fits the duration of the cinamatic to the duration of all its child cinematic events
+     */
+    public void fitDuration() {
+        KeyFrame kf = timeLine.getKeyFrameAtTime(timeLine.getLastKeyFrameIndex());
+        float d = 0;
+        for (int i = 0; i < kf.getCinematicEvents().size(); i++) {
+            CinematicEvent ce = kf.getCinematicEvents().get(i);
+            if (d < (ce.getDuration() * ce.getSpeed())) {
+                d = (ce.getDuration() * ce.getSpeed());
+            }
+        }
+        
+        initialDuration = d;
+    }
+    
+    public CameraNode bindCamera(String cameraName, Camera cam) {
+        CameraNode node = new CameraNode(cameraName, cam);
+        node.setControlDir(ControlDirection.SpatialToCamera);
+        node.getControl(CameraControl.class).setEnabled(false);
+        cameras.put(cameraName, node);
+        scene.attachChild(node);
+        return node;
+    }
+    
+    public CameraNode getCamera(String cameraName) {
+        return cameras.get(cameraName);
+    }
+    
+    private void enableCurrentCam(boolean enabled) {
+        if (currentCam != null) {
+            currentCam.getControl(CameraControl.class).setEnabled(enabled);
+        }
+    }
+    
+    public void setActiveCamera(String cameraName) {
+        enableCurrentCam(false);
+        currentCam = cameras.get(cameraName);
+        if (currentCam == null) {
+            logger.log(Level.WARNING, "{0} is not a camera bond to the cinematic, cannot activate", cameraName);
+        }
+        enableCurrentCam(true);
+    }
+    
+    public void activateCamera(final float timeStamp, final String cameraName) {
+        addCinematicEvent(timeStamp, new AbstractCinematicEvent() {
+            
+            @Override
+            public void play() {
+                super.play();
+                stop();
+            }
+            
+            @Override
+            public void onPlay() {
+                setActiveCamera(cameraName);
+            }
+            
+            @Override
+            public void onUpdate(float tpf) {
+            }
+            
+            @Override
+            public void onStop() {
+            }
+            
+            @Override
+            public void onPause() {
+            }
+            
+            @Override
+            public void setTime(float time) {
+                play();
+            }
+        });
+    }
+    
+    public void setScene(Node scene) {
+        this.scene = scene;
+    }
+    
+    private Map<String, Map<String, Object>> getEventsData() {
+        if (eventsData == null) {
+            eventsData = new HashMap<String, Map<String, Object>>();
+        }
+        return eventsData;
+    }
+    
+    public void putEventData(String type, String name, Object object) {
+        Map<String, Map<String, Object>> data = getEventsData();
+        Map<String, Object> row = data.get(type);
+        if (row == null) {
+            row = new HashMap<String, Object>();
+        }
+        row.put(name, object);
+    }
+    
+    public Object getEventData(String type, String name) {
+        if (eventsData != null) {
+            Map<String, Object> row = eventsData.get(type);
+            if (row != null) {
+                return row.get(name);
+            }
+        }
+        return null;
+    }
+    
+    public Savable removeEventData(String type, String name) {
+        if (eventsData != null) {
+            Map<String, Object> row = eventsData.get(type);
+            if (row != null) {
+                row.remove(name);
+            }
+        }
+        return null;
+    }
+    
+    public Node getScene() {
+        return scene;
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/KeyFrame.java b/engine/src/core/com/jme3/cinematic/KeyFrame.java
new file mode 100644
index 0000000..2a4b200
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/KeyFrame.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+import com.jme3.cinematic.events.CinematicEvent;
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Nehon
+ */
+public class KeyFrame implements Savable {
+
+    List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>();
+    private int index;
+
+    public List<CinematicEvent> getCinematicEvents() {
+        return cinematicEvents;
+    }
+
+    public void setCinematicEvents(List<CinematicEvent> cinematicEvents) {
+        this.cinematicEvents = cinematicEvents;
+    }
+
+    public List<CinematicEvent> trigger() {
+        for (CinematicEvent event : cinematicEvents) {
+            event.play();
+        }
+        return cinematicEvents;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
+        oc.write(index, "index", 0);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
+        index=ic.readInt("index", 0);
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+
+}
diff --git a/engine/src/core/com/jme3/cinematic/MotionPath.java b/engine/src/core/com/jme3/cinematic/MotionPath.java
new file mode 100644
index 0000000..cdb31d0
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/MotionPath.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.cinematic.events.MotionTrack;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Spline;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Curve;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Motion path is used to create a path between way points.
+ * @author Nehon
+ */
+public class MotionPath implements Savable {
+
+    private Node debugNode;
+    private AssetManager assetManager;
+    private List<MotionPathListener> listeners;
+    private Spline spline = new Spline();
+    private float eps = 0.0001f;
+
+    /**
+     * Create a motion Path
+     */
+    public MotionPath() {
+    }
+
+    /**
+     * interpolate the path giving the time since the beginnin and the motionControl     
+     * this methods sets the new localTranslation to the spatial of the motionTrack control.
+     * @param time the time since the animation started
+     * @param control the ocntrol over the moving spatial
+     */
+    public float interpolatePath(float time, MotionTrack control) {
+
+        float traveledDistance = 0;
+        TempVars vars = TempVars.get();
+        Vector3f temp = vars.vect1;
+        Vector3f tmpVector = vars.vect2;
+        //computing traveled distance according to new time
+        traveledDistance = time * (getLength() / control.getInitialDuration());
+
+        //getting waypoint index and current value from new traveled distance
+        Vector2f v = getWayPointIndexForDistance(traveledDistance);
+
+        //setting values
+        control.setCurrentWayPoint((int) v.x);
+        control.setCurrentValue(v.y);
+        
+        //interpolating new position
+        getSpline().interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp);
+        if (control.needsDirection()) {
+            tmpVector.set(temp);
+            control.setDirection(tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).normalizeLocal());
+        }
+
+        control.getSpatial().setLocalTranslation(temp);
+        vars.release();
+        return traveledDistance;
+    }
+
+    private void attachDebugNode(Node root) {
+        if (debugNode == null) {
+            debugNode = new Node();
+            Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+            for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+                Vector3f cp = it.next();
+                Geometry geo = new Geometry("box", new Box(cp, 0.3f, 0.3f, 0.3f));
+                geo.setMaterial(m);
+                debugNode.attachChild(geo);
+
+            }
+            switch (spline.getType()) {
+                case CatmullRom:
+                    debugNode.attachChild(CreateCatmullRomPath());
+                    break;
+                case Linear:
+                    debugNode.attachChild(CreateLinearPath());
+                    break;
+                default:
+                    debugNode.attachChild(CreateLinearPath());
+                    break;
+            }
+
+            root.attachChild(debugNode);
+        }
+    }
+
+    private Geometry CreateLinearPath() {
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Blue);
+        Geometry lineGeometry = new Geometry("line", new Curve(spline, 0));
+        lineGeometry.setMaterial(mat);
+        return lineGeometry;
+    }
+
+    private Geometry CreateCatmullRomPath() {
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Blue);
+        Geometry lineGeometry = new Geometry("line", new Curve(spline, 10));
+        lineGeometry.setMaterial(mat);
+        return lineGeometry;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(spline, "spline", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        spline = (Spline) in.readSavable("spline", null);
+
+    }
+
+    /**
+     * compute the index of the waypoint and the interpolation value according to a distance
+     * returns a vector 2 containing the index in the x field and the interpolation value in the y field
+     * @param distance the distance traveled on this path
+     * @return the waypoint index and the interpolation value in a vector2
+     */
+    public Vector2f getWayPointIndexForDistance(float distance) {
+        float sum = 0;
+        distance = distance % spline.getTotalLength();
+        int i = 0;
+        for (Float len : spline.getSegmentsLength()) {
+            if (sum + len >= distance) {
+                return new Vector2f((float) i, (distance - sum) / len);
+            }
+            sum += len;
+            i++;
+        }
+        return new Vector2f((float) spline.getControlPoints().size() - 1, 1.0f);
+    }
+
+    /**
+     * Addsa waypoint to the path
+     * @param wayPoint a position in world space
+     */
+    public void addWayPoint(Vector3f wayPoint) {
+        spline.addControlPoint(wayPoint);
+    }
+
+    /**
+     * retruns the length of the path in world units
+     * @return the length
+     */
+    public float getLength() {
+        return spline.getTotalLength();
+    }
+
+    /**
+     * returns the waypoint at the given index
+     * @param i the index
+     * @return returns the waypoint position
+     */
+    public Vector3f getWayPoint(int i) {
+        return spline.getControlPoints().get(i);
+    }
+
+    /**
+     * remove the waypoint from the path
+     * @param wayPoint the waypoint to remove
+     */
+    public void removeWayPoint(Vector3f wayPoint) {
+        spline.removeControlPoint(wayPoint);
+    }
+
+    /**
+     * remove the waypoint at the given index from the path
+     * @param i the index of the waypoint to remove
+     */
+    public void removeWayPoint(int i) {
+        removeWayPoint(spline.getControlPoints().get(i));
+    }
+
+    /**
+     * returns an iterator on the waypoints collection
+     * @return
+     */
+    public Iterator<Vector3f> iterator() {
+        return spline.getControlPoints().iterator();
+    }
+
+    /**
+     * return the type of spline used for the path interpolation for this path
+     * @return the path interpolation spline type
+     */
+    public SplineType getPathSplineType() {
+        return spline.getType();
+    }
+
+    /**
+     * sets the type of spline used for the path interpolation for this path
+     * @param pathSplineType
+     */
+    public void setPathSplineType(SplineType pathSplineType) {
+        spline.setType(pathSplineType);
+        if (debugNode != null) {
+            Node parent = debugNode.getParent();
+            debugNode.removeFromParent();
+            debugNode.detachAllChildren();
+            debugNode = null;
+            attachDebugNode(parent);
+        }
+    }
+
+    /**
+     * disable the display of the path and the waypoints
+     */
+    public void disableDebugShape() {
+
+        debugNode.detachAllChildren();
+        debugNode = null;
+        assetManager = null;
+    }
+
+    /**
+     * enable the display of the path and the waypoints
+     * @param manager the assetManager
+     * @param rootNode the node where the debug shapes must be attached
+     */
+    public void enableDebugShape(AssetManager manager, Node rootNode) {
+        assetManager = manager;
+        // computeTotalLentgh();
+        attachDebugNode(rootNode);
+    }
+
+    /**
+     * Adds a motion pathListener to the path
+     * @param listener the MotionPathListener to attach
+     */
+    public void addListener(MotionPathListener listener) {
+        if (listeners == null) {
+            listeners = new ArrayList<MotionPathListener>();
+        }
+        listeners.add(listener);
+    }
+
+    /**
+     * remove the given listener
+     * @param listener the listener to remove
+     */
+    public void removeListener(MotionPathListener listener) {
+        if (listeners != null) {
+            listeners.remove(listener);
+        }
+    }
+
+    /**
+     * return the number of waypoints of this path
+     * @return
+     */
+    public int getNbWayPoints() {
+        return spline.getControlPoints().size();
+    }
+
+    public void triggerWayPointReach(int wayPointIndex, MotionTrack control) {
+        if (listeners != null) {
+            for (Iterator<MotionPathListener> it = listeners.iterator(); it.hasNext();) {
+                MotionPathListener listener = it.next();
+                listener.onWayPointReach(control, wayPointIndex);
+            }
+        }
+    }
+
+    /**
+     * Returns the curve tension
+     * @return
+     */
+    public float getCurveTension() {
+        return spline.getCurveTension();
+    }
+
+    /**
+     * sets the tension of the curve (only for catmull rom) 0.0 will give a linear curve, 1.0 a round curve
+     * @param curveTension
+     */
+    public void setCurveTension(float curveTension) {
+        spline.setCurveTension(curveTension);
+        if (debugNode != null) {
+            Node parent = debugNode.getParent();
+            debugNode.removeFromParent();
+            debugNode.detachAllChildren();
+            debugNode = null;
+            attachDebugNode(parent);
+        }
+    }
+
+    public void clearWayPoints() {
+        spline.clearControlPoints();
+    }
+
+    /**
+     * Sets the path to be a cycle
+     * @param cycle
+     */
+    public void setCycle(boolean cycle) {
+
+        spline.setCycle(cycle);
+        if (debugNode != null) {
+            Node parent = debugNode.getParent();
+            debugNode.removeFromParent();
+            debugNode.detachAllChildren();
+            debugNode = null;
+            attachDebugNode(parent);
+        }
+
+    }
+
+    /**
+     * returns true if the path is a cycle
+     * @return
+     */
+    public boolean isCycle() {
+        return spline.isCycle();
+    }
+
+    public Spline getSpline() {
+        return spline;
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/MotionPathListener.java b/engine/src/core/com/jme3/cinematic/MotionPathListener.java
new file mode 100644
index 0000000..99a3dcb
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/MotionPathListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+import com.jme3.cinematic.events.MotionTrack;
+
+/**
+ * Trigger the events appening on an motion path
+ * @author Nehon
+ */
+public interface MotionPathListener {
+
+    /**
+     * Triggers every time the target reach a waypoint on the path
+     * @param motionControl the MotionTrack objects that reached the waypoint
+     * @param wayPointIndex the index of the way point reached
+     */
+    public void onWayPointReach(MotionTrack motionControl,int wayPointIndex);
+
+}
diff --git a/engine/src/core/com/jme3/cinematic/PlayState.java b/engine/src/core/com/jme3/cinematic/PlayState.java
new file mode 100644
index 0000000..648dc3d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/PlayState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+/**
+ * The play state of a cinematic event
+ * @author Nehon
+ */
+public enum PlayState {
+
+        /**The CinematicEvent is currently beeing played*/
+        Playing,
+        /**The animatable has been paused*/
+        Paused,
+        /**the animatable is stoped*/
+        Stopped
+}
+    
diff --git a/engine/src/core/com/jme3/cinematic/TimeLine.java b/engine/src/core/com/jme3/cinematic/TimeLine.java
new file mode 100644
index 0000000..ae3e06d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/TimeLine.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TimeLine extends HashMap<Integer, KeyFrame> implements Savable {
+
+    protected int keyFramesPerSeconds = 30;
+    protected int lastKeyFrameIndex = 0;
+
+    public TimeLine() {
+        super();
+    }
+
+    public KeyFrame getKeyFrameAtTime(float time) {
+        return get(getKeyFrameIndexFromTime(time));
+    }
+
+    public KeyFrame getKeyFrameAtIndex(int keyFrameIndex) {
+        return get(keyFrameIndex);
+    }
+
+    public void addKeyFrameAtTime(float time, KeyFrame keyFrame) {
+        addKeyFrameAtIndex(getKeyFrameIndexFromTime(time), keyFrame);
+    }
+
+    public void addKeyFrameAtIndex(int keyFrameIndex, KeyFrame keyFrame) {
+        put(keyFrameIndex, keyFrame);
+        keyFrame.setIndex(keyFrameIndex);
+        if (lastKeyFrameIndex < keyFrameIndex) {
+            lastKeyFrameIndex = keyFrameIndex;
+        }
+    }
+
+    public void removeKeyFrame(int keyFrameIndex) {
+        remove(keyFrameIndex);
+        if (lastKeyFrameIndex == keyFrameIndex) {
+            KeyFrame kf = null;
+            for (int i = keyFrameIndex; kf == null && i >= 0; i--) {
+                kf = getKeyFrameAtIndex(i);
+                lastKeyFrameIndex = i;
+            }
+        }
+    }
+
+    public void removeKeyFrame(float time) {
+        removeKeyFrame(getKeyFrameIndexFromTime(time));
+    }
+
+    public int getKeyFrameIndexFromTime(float time) {
+        return Math.round(time * keyFramesPerSeconds);
+    }
+    
+    public float getKeyFrameTime(KeyFrame keyFrame) {
+        return (float)keyFrame.getIndex()/(float)keyFramesPerSeconds;
+    }
+
+    public Collection<KeyFrame> getAllKeyFrames() {
+        return values();
+    }
+
+    public int getLastKeyFrameIndex() {
+        return lastKeyFrameIndex;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        ArrayList list = new ArrayList();
+        list.addAll(values());
+        oc.writeSavableArrayList(list, "keyFrames", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        ArrayList list = ic.readSavableArrayList("keyFrames", null);
+        for (Iterator it = list.iterator(); it.hasNext();) {
+            KeyFrame keyFrame = (KeyFrame) it.next();
+            addKeyFrameAtIndex(keyFrame.getIndex(), keyFrame);
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
new file mode 100644
index 0000000..ea2d132
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This calls contains basic behavior of a cinematic event
+ * every cinematic event must extend this class
+ * 
+ * A cinematic event must be given an inital duration in seconds (duration of the event at speed = 1) (default is 10)
+ * @author Nehon
+ */
+public abstract class AbstractCinematicEvent implements CinematicEvent {
+
+    protected PlayState playState = PlayState.Stopped;
+    protected float speed = 1;
+    protected float initialDuration = 10;
+    protected LoopMode loopMode = LoopMode.DontLoop;
+    protected float time = 0;
+    protected boolean resuming = false;
+    
+    /**
+     * the list of listeners
+     */
+    protected List<CinematicEventListener> listeners;
+
+    /**
+     * contruct a cinematic event
+     */
+    public AbstractCinematicEvent() {
+    }
+
+    /**
+     * contruct a cinematic event wwith the given initial duration
+     * @param initialDuration 
+     */
+    public AbstractCinematicEvent(float initialDuration) {
+        this.initialDuration = initialDuration;
+    }
+
+    /**
+     * contruct a cinematic event with the given loopMode
+     * @param loopMode 
+     */
+    public AbstractCinematicEvent(LoopMode loopMode) {
+        this.loopMode = loopMode;
+    }
+
+    /**
+     * contruct a cinematic event with the given loopMode and the given initialDuration
+     * @param initialDuration the duration of the event at speed = 1
+     * @param loopMode the loop mode of the event
+     */
+    public AbstractCinematicEvent(float initialDuration, LoopMode loopMode) {
+        this.initialDuration = initialDuration;
+        this.loopMode = loopMode;
+    }
+
+    /**
+     * Play this event
+     */
+    public void play() {
+        onPlay();
+        playState = PlayState.Playing;
+        if (listeners != null) {
+            for (int i = 0; i < listeners.size(); i++) {
+                CinematicEventListener cel = listeners.get(i);
+                cel.onPlay(this);
+            }
+        }
+    }
+
+    /**
+     * Place here the code you want to execute when the event is started
+     */
+    protected abstract void onPlay();
+
+    /**
+     * should be used internally only
+     * @param tpf time per frame
+     */
+    public void internalUpdate(float tpf) {
+        if (playState == PlayState.Playing) {
+            time = time + (tpf * speed);
+            //time = elapsedTimePause + (timer.getTimeInSeconds() - start) * speed;
+
+            onUpdate(tpf);
+            if (time >= initialDuration && loopMode == loopMode.DontLoop) {
+                stop();
+            }
+        }
+
+    }
+
+    /**
+     * Place here the code you want to execute on update (only called when the event is playing)
+     * @param tpf time per frame
+     */
+    protected abstract void onUpdate(float tpf);
+
+    /**
+     * stops the animation, next time play() is called the animation will start from the begining.
+     */
+    public void stop() {
+        onStop();
+        time = 0;
+        playState = PlayState.Stopped;
+        if (listeners != null) {
+            for (int i = 0; i < listeners.size(); i++) {
+                CinematicEventListener cel = listeners.get(i);
+                cel.onStop(this);
+            }
+        }
+    }
+
+    /**
+     * Place here the code you want to execute when the event is stoped.
+     */
+    protected abstract void onStop();
+
+    /**
+     * pause this event
+     */
+    public void pause() {
+        onPause();
+        playState = PlayState.Paused;
+        if (listeners != null) {
+            for (int i = 0; i < listeners.size(); i++) {
+                CinematicEventListener cel = listeners.get(i);
+                cel.onPause(this);
+            }
+        }
+    }
+
+    /**
+     * place here the code you want to execute when the event is paused
+     */
+    public abstract void onPause();
+
+    /**
+     * returns the actual duration of the animtion (initialDuration/speed)
+     * @return
+     */
+    public float getDuration() {
+        return initialDuration / speed;
+    }
+
+    /**
+     * Sets the speed of the animation.
+     * At speed = 1, the animation will last initialDuration seconds,
+     * At speed = 2 the animation will last initialDuraiton/2...
+     * @param speed
+     */
+    public void setSpeed(float speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * returns the speed of the animation.
+     * @return
+     */
+    public float getSpeed() {
+        return speed;
+    }
+
+    /**
+     * Returns the current playstate of the animation
+     * @return
+     */
+    public PlayState getPlayState() {
+        return playState;
+    }
+
+    /**
+     * returns the initial duration of the animation at speed = 1 in seconds.
+     * @return
+     */
+    public float getInitialDuration() {
+        return initialDuration;
+    }
+
+    /**
+     * Sets the duration of the antionamtion at speed = 1 in seconds
+     * @param initialDuration
+     */
+    public void setInitialDuration(float initialDuration) {
+        this.initialDuration = initialDuration;
+    }
+
+    /**
+     * retursthe loopMode of the animation
+     * @see LoopMode
+     * @return
+     */
+    public LoopMode getLoopMode() {
+        return loopMode;
+    }
+
+    /**
+     * Sets the loopMode of the animation
+     * @see LoopMode
+     * @param loopMode
+     */
+    public void setLoopMode(LoopMode loopMode) {
+        this.loopMode = loopMode;
+    }
+
+    /**
+     * for serialization only
+     * @param ex exporter
+     * @throws IOException 
+     */
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(playState, "playState", PlayState.Stopped);
+        oc.write(speed, "speed", 1);
+        oc.write(initialDuration, "initalDuration", 10);
+        oc.write(loopMode, "loopMode", LoopMode.DontLoop);
+    }
+
+    /**
+     * for serialization only
+     * @param im importer
+     * @throws IOException 
+     */
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        playState = ic.readEnum("playState", PlayState.class, PlayState.Stopped);
+        speed = ic.readFloat("speed", 1);
+        initialDuration = ic.readFloat("initalDuration", 10);
+        loopMode = ic.readEnum("loopMode", LoopMode.class, LoopMode.DontLoop);
+    }
+
+    /**
+     * initialize this event (should be called internally only)
+     * @param app
+     * @param cinematic 
+     */
+    public void initEvent(Application app, Cinematic cinematic) {
+    }
+
+    /**
+     * return a list of CinematicEventListener added on this event
+     * @return 
+     */
+    private List<CinematicEventListener> getListeners() {
+        if (listeners == null) {
+            listeners = new ArrayList<CinematicEventListener>();
+        }
+        return listeners;
+    }
+
+    /**
+     * Add a CinematicEventListener to this event
+     * @param listener CinematicEventListener
+     */
+    public void addListener(CinematicEventListener listener) {
+        getListeners().add(listener);
+    }
+
+    /**
+     * remove a CinematicEventListener from this event
+     * @param listener CinematicEventListener
+     */
+    public void removeListener(CinematicEventListener listener) {
+        getListeners().remove(listener);
+    }
+
+    /**
+     * When this method is invoked, the event should fast forward to the given time according tim 0 is the start of the event.
+     * @param time the time to fast forward to
+     */
+    public void setTime(float time) {
+        this.time = time / speed;
+    }
+
+    public float getTime() {
+        return time;
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java b/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java
new file mode 100644
index 0000000..e857706
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic.events;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ */
+public class AnimationTrack extends AbstractCinematicEvent {
+
+    private static final Logger log = Logger.getLogger(AnimationTrack.class.getName());
+    protected AnimChannel channel;
+    protected String animationName;
+    protected String modelName;
+
+    public AnimationTrack() {
+    }
+
+    public AnimationTrack(Spatial model, String animationName) {
+        modelName = model.getName();
+        this.animationName = animationName;
+        initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
+    }
+
+    public AnimationTrack(Spatial model, String animationName, float initialDuration) {
+        super(initialDuration);
+        modelName = model.getName();
+        this.animationName = animationName;
+    }
+
+    public AnimationTrack(Spatial model, String animationName, LoopMode loopMode) {
+        super(loopMode);
+        initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
+        modelName = model.getName();
+        this.animationName = animationName;
+    }
+
+    public AnimationTrack(Spatial model, String animationName, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        modelName = model.getName();
+        this.animationName = animationName;
+    }
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        if (channel == null) {
+            Object s = cinematic.getEventData("modelChannels", modelName);
+            if (s != null && s instanceof AnimChannel) {
+                this.channel = (AnimChannel) s;
+            } else if (s == null) {
+                Spatial model = cinematic.getScene().getChild(modelName);
+                if (model != null) {
+                    channel = model.getControl(AnimControl.class).createChannel();
+                    cinematic.putEventData("modelChannels", modelName, channel);
+                } else {
+                    log.log(Level.WARNING, "spatial {0} not found in the scene, cannot perform animation", modelName);
+                }
+            }
+
+        }
+    }
+
+    @Override
+    public void setTime(float time) {
+        super.setTime(time);
+        float t = time;
+        if(loopMode == loopMode.Loop){
+            t = t % channel.getAnimMaxTime();
+        }
+        if(loopMode == loopMode.Cycle){
+            float parity = (float)Math.ceil(time / channel.getAnimMaxTime());
+            if(parity >0 && parity%2 ==0){
+                t = channel.getAnimMaxTime() - t % channel.getAnimMaxTime();
+            }else{
+                t = t % channel.getAnimMaxTime();
+            }
+            
+        }
+        channel.setTime(t);
+        channel.getControl().update(0);
+    }
+
+    @Override
+    public void onPlay() {
+        channel.getControl().setEnabled(true);
+        if (playState == PlayState.Stopped) {
+            channel.setAnim(animationName);
+            channel.setSpeed(speed);
+            channel.setLoopMode(loopMode);
+            channel.setTime(time);
+        }
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+    }
+
+    @Override
+    public void onStop() {
+        channel.getControl().setEnabled(false);
+        channel.setTime(0);
+    }
+
+    @Override
+    public void onPause() {
+        channel.getControl().setEnabled(false);
+    }
+
+    @Override
+    public void setLoopMode(LoopMode loopMode) {
+        super.setLoopMode(loopMode);
+        channel.setLoopMode(loopMode);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(modelName, "modelName", "");
+        oc.write(animationName, "animationName", "");
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        modelName = ic.readString("modelName", "");
+        animationName = ic.readString("animationName", "");
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
new file mode 100644
index 0000000..37e70f9
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.Savable;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface CinematicEvent extends Savable {
+
+    /**
+     * Starts the animation
+     */
+    public void play();
+
+    /**
+     * Stops the animation
+     */
+    public void stop();
+
+    /**
+     * Pauses the animation
+     */
+    public void pause();
+
+    /**
+     * Returns the actual duration of the animation
+     * @return the duration
+     */
+    public float getDuration();
+
+    /**
+     * Sets the speed of the animation (1 is normal speed, 2 is twice faster)
+     * @param speed
+     */
+    public void setSpeed(float speed);
+
+    /**
+     * returns the speed of the animation
+     * @return the speed
+     */
+    public float getSpeed();
+
+    /**
+     * returns the PlayState of the animation
+     * @return the plat state
+     */
+    public PlayState getPlayState();
+
+    /**
+     * @param loop Set the loop mode for the channel. The loop mode
+     * determines what will happen to the animation once it finishes
+     * playing.
+     *
+     * For more information, see the LoopMode enum class.
+     * @see LoopMode
+     */
+    public void setLoopMode(LoopMode loop);
+
+    /**
+     * @return The loop mode currently set for the animation. The loop mode
+     * determines what will happen to the animation once it finishes
+     * playing.
+     *
+     * For more information, see the LoopMode enum class.
+     * @see LoopMode
+     */
+    public LoopMode getLoopMode();
+
+    /**
+     * returns the initial duration of the animation at speed = 1 in seconds.
+     * @return the initial duration
+     */
+    public float getInitialDuration();
+
+    /**
+     * Sets the duration of the antionamtion at speed = 1 in seconds
+     * @param initialDuration
+     */
+    public void setInitialDuration(float initialDuration);
+
+    /**
+     * called internally in the update method, place here anything you want to run in the update loop
+     * @param tpf time per frame
+     */
+    public void internalUpdate(float tpf);
+
+    /**
+     * initialize this event
+     * @param app the application
+     * @param cinematic the cinematic
+     */
+    public void initEvent(Application app, Cinematic cinematic);
+    
+    /**
+     * When this method is invoked, the event should fast forward to the given time according tim 0 is the start of the event.
+     * @param time the time to fast forward to
+     */
+    public void setTime(float time);    
+   
+    /**
+     * returns the current time of the cinematic event
+     * @return the time
+     */
+    public float getTime();
+        
+    
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java b/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java
new file mode 100644
index 0000000..9a5f5a6
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java
@@ -0,0 +1,17 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.cinematic.events;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface CinematicEventListener {
+
+    public void onPlay(CinematicEvent cinematic);
+    public void onPause(CinematicEvent cinematic);
+    public void onStop(CinematicEvent cinematic);
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/MotionTrack.java b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java
new file mode 100644
index 0000000..2995452
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+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.TempVars;
+import java.io.IOException;
+
+/**
+ * A MotionTrack is a control over the spatial that manage the position and direction of the spatial while following a motion Path
+ *
+ * You must first create a MotionPath and then create a MotionTrack to associate a spatial and the path.
+ *
+ * @author Nehon
+ */
+public class MotionTrack extends AbstractCinematicEvent implements Control {
+
+    protected Spatial spatial;
+    protected int currentWayPoint;
+    protected float currentValue;
+    protected Vector3f direction = new Vector3f();
+    protected Vector3f lookAt;
+    protected Vector3f upVector;
+    protected Quaternion rotation;
+    protected Direction directionType = Direction.None;
+    protected MotionPath path;
+    private boolean isControl = true;
+    /**
+     * the distance traveled by the spatial on the path
+     */
+    protected float traveledDistance = 0;
+
+    /**
+     * Enum for the different type of target direction behavior
+     */
+    public enum Direction {
+
+        /**
+         * the target stay in the starting direction
+         */
+        None,
+        /**
+         * The target rotates with the direction of the path
+         */
+        Path,
+        /**
+         * The target rotates with the direction of the path but with the additon of a rtotation
+         * you need to use the setRotation mathod when using this Direction
+         */
+        PathAndRotation,
+        /**
+         * The target rotates with the given rotation
+         */
+        Rotation,
+        /**
+         * The target looks at a point
+         * You need to use the setLookAt method when using this direction
+         */
+        LookAt
+    }
+
+    /**
+     * Create MotionTrack,
+     * when using this constructor don't forget to assign spatial and path
+     */
+    public MotionTrack() {
+        super();
+    }
+
+    /**
+     * Creates a MotionPath for the given spatial on the given motion path
+     * @param spatial
+     * @param path
+     */
+    public MotionTrack(Spatial spatial, MotionPath path) {
+        super();
+        this.spatial = spatial;
+        spatial.addControl(this);
+        this.path = path;
+    }
+
+    /**
+     * Creates a MotionPath for the given spatial on the given motion path
+     * @param spatial
+     * @param path
+     */
+    public MotionTrack(Spatial spatial, MotionPath path, float initialDuration) {
+        super(initialDuration);
+        this.spatial = spatial;
+        spatial.addControl(this);
+        this.path = path;
+    }
+
+    /**
+     * Creates a MotionPath for the given spatial on the given motion path
+     * @param spatial
+     * @param path
+     */
+    public MotionTrack(Spatial spatial, MotionPath path, LoopMode loopMode) {
+        super();
+        this.spatial = spatial;
+        spatial.addControl(this);
+        this.path = path;
+        this.loopMode = loopMode;
+    }
+
+    /**
+     * Creates a MotionPath for the given spatial on the given motion path
+     * @param spatial
+     * @param path
+     */
+    public MotionTrack(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
+        super(initialDuration);
+        this.spatial = spatial;
+        spatial.addControl(this);
+        this.path = path;
+        this.loopMode = loopMode;
+    }
+
+    public void update(float tpf) {
+        if (isControl) {
+
+            if (playState == PlayState.Playing) {
+                time = time + (tpf * speed);
+
+                if (time >= initialDuration && loopMode == loopMode.DontLoop) {
+                    stop();
+                } else {
+                    onUpdate(tpf);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        isControl = false;
+    }
+
+    @Override
+    public void setTime(float time) {
+        super.setTime(time);
+        onUpdate(0);
+    }
+
+    public void onUpdate(float tpf) {
+        traveledDistance = path.interpolatePath(time, this);
+        computeTargetDirection();
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(lookAt, "lookAt", Vector3f.ZERO);
+        oc.write(upVector, "upVector", Vector3f.UNIT_Y);
+        oc.write(rotation, "rotation", Quaternion.IDENTITY);
+        oc.write(directionType, "directionType", Direction.None);
+        oc.write(path, "path", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule in = im.getCapsule(this);
+        lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO);
+        upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
+        rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY);
+        directionType = in.readEnum("directionType", Direction.class, Direction.None);
+        path = (MotionPath) in.readSavable("path", null);
+    }
+
+    /**
+     * this method is meant to be called by the motion path only
+     * @return
+     */
+    public boolean needsDirection() {
+        return directionType == Direction.Path || directionType == Direction.PathAndRotation;
+    }
+
+    private void computeTargetDirection() {
+        switch (directionType) {
+            case Path:
+                Quaternion q = new Quaternion();
+                q.lookAt(direction, Vector3f.UNIT_Y);
+                spatial.setLocalRotation(q);
+                break;
+            case LookAt:
+                if (lookAt != null) {
+                    spatial.lookAt(lookAt, upVector);
+                }
+                break;
+            case PathAndRotation:
+                if (rotation != null) {
+                    Quaternion q2 = new Quaternion();
+                    q2.lookAt(direction, Vector3f.UNIT_Y);
+                    q2.multLocal(rotation);
+                    spatial.setLocalRotation(q2);
+                }
+                break;
+            case Rotation:
+                if (rotation != null) {
+                    spatial.setLocalRotation(rotation);
+                }
+                break;
+            case None:
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Clone this control for the given spatial
+     * @param spatial
+     * @return
+     */
+    public Control cloneForSpatial(Spatial spatial) {
+        MotionTrack control = new MotionTrack(spatial, path);
+        control.playState = playState;
+        control.currentWayPoint = currentWayPoint;
+        control.currentValue = currentValue;
+        control.direction = direction.clone();
+        control.lookAt = lookAt.clone();
+        control.upVector = upVector.clone();
+        control.rotation = rotation.clone();
+        control.initialDuration = initialDuration;
+        control.speed = speed;
+        control.loopMode = loopMode;
+        control.directionType = directionType;
+
+        return control;
+    }
+
+    @Override
+    public void onPlay() {
+        traveledDistance = 0;
+    }
+
+    @Override
+    public void onStop() {
+        currentWayPoint = 0;
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    /**
+     * this method is meant to be called by the motion path only
+     * @return
+     */
+    public float getCurrentValue() {
+        return currentValue;
+    }
+
+    /**
+     * this method is meant to be called by the motion path only
+     *
+     */
+    public void setCurrentValue(float currentValue) {
+        this.currentValue = currentValue;
+    }
+
+    /**
+     * this method is meant to be called by the motion path only
+     * @return
+     */
+    public int getCurrentWayPoint() {
+        return currentWayPoint;
+    }
+
+    /**
+     * this method is meant to be called by the motion path only
+     *
+     */
+    public void setCurrentWayPoint(int currentWayPoint) {
+        if (this.currentWayPoint != currentWayPoint) {
+            this.currentWayPoint = currentWayPoint;
+            path.triggerWayPointReach(currentWayPoint, this);
+        }
+    }
+
+    /**
+     * returns the direction the spatial is moving
+     * @return
+     */
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    /**
+     * Sets the direction of the spatial
+     * This method is used by the motion path.
+     * @param direction
+     */
+    public void setDirection(Vector3f direction) {
+        this.direction.set(direction);
+    }
+
+    /**
+     * returns the direction type of the target
+     * @return the direction type
+     */
+    public Direction getDirectionType() {
+        return directionType;
+    }
+
+    /**
+     * Sets the direction type of the target
+     * On each update the direction given to the target can have different behavior
+     * See the Direction Enum for explanations
+     * @param directionType the direction type
+     */
+    public void setDirectionType(Direction directionType) {
+        this.directionType = directionType;
+    }
+
+    /**
+     * Set the lookAt for the target
+     * This can be used only if direction Type is Direction.LookAt
+     * @param lookAt the position to look at
+     * @param upVector the up vector
+     */
+    public void setLookAt(Vector3f lookAt, Vector3f upVector) {
+        this.lookAt = lookAt;
+        this.upVector = upVector;
+    }
+
+    /**
+     * returns the rotation of the target
+     * @return the rotation quaternion
+     */
+    public Quaternion getRotation() {
+        return rotation;
+    }
+
+    /**
+     * sets the rotation of the target
+     * This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation
+     * With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion.
+     * With Rotation the rotation of the target will be set with the given Quaternion.
+     * @param rotation the rotation quaternion
+     */
+    public void setRotation(Quaternion rotation) {
+        this.rotation = rotation;
+    }
+
+    /**
+     * retun the motion path this control follows
+     * @return
+     */
+    public MotionPath getPath() {
+        return path;
+    }
+
+    /**
+     * Sets the motion path to follow
+     * @param path
+     */
+    public void setPath(MotionPath path) {
+        this.path = path;
+    }
+
+    public void setEnabled(boolean enabled) {
+        if (enabled) {
+            play();
+        } else {
+            pause();
+        }
+    }
+
+    public boolean isEnabled() {
+        return playState != PlayState.Stopped;
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+    }
+
+    public void setSpatial(Spatial spatial) {
+        this.spatial = spatial;
+    }
+
+    public Spatial getSpatial() {
+        return spatial;
+    }
+
+    /**
+     * return the distance traveled by the spatial on the path
+     * @return 
+     */
+    public float getTraveledDistance() {
+        return traveledDistance;
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/PositionTrack.java b/engine/src/core/com/jme3/cinematic/events/PositionTrack.java
new file mode 100644
index 0000000..abab712
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/PositionTrack.java
@@ -0,0 +1,122 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class PositionTrack extends AbstractCinematicEvent {
+
+    private static final Logger log = Logger.getLogger(PositionTrack.class.getName());
+    private Vector3f startPosition;
+    private Vector3f endPosition;
+    private Spatial spatial;
+    private String spatialName = "";
+    private float value = 0;
+
+    public PositionTrack() {
+    }
+
+    public PositionTrack(Spatial spatial, Vector3f endPosition) {
+        this.endPosition = endPosition;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        if (spatial == null) {
+            spatial = cinematic.getScene().getChild(spatialName);
+            if (spatial == null) {
+            } else {
+                log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+            }
+        }
+    }
+
+    public PositionTrack(Spatial spatial, Vector3f endPosition, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.endPosition = endPosition;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public PositionTrack(Spatial spatial, Vector3f endPosition, LoopMode loopMode) {
+        super(loopMode);
+        this.endPosition = endPosition;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public PositionTrack(Spatial spatial, Vector3f endPosition, float initialDuration) {
+        super(initialDuration);
+        this.endPosition = endPosition;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    @Override
+    public void onPlay() {
+        if (playState != playState.Paused) {
+            startPosition = spatial.getWorldTranslation().clone();
+        }
+        if (initialDuration == 0 && spatial != null) {
+
+            spatial.setLocalTranslation(endPosition);
+        }
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+        if (spatial != null) {
+            value = Math.min(time / initialDuration, 1.0f);
+            Vector3f pos = FastMath.interpolateLinear(value, startPosition, endPosition);
+            spatial.setLocalTranslation(pos);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        value = 0;
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(spatialName, "spatialName", "");
+        oc.write(endPosition, "endPosition", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        spatialName = ic.readString("spatialName", "");
+        endPosition = (Vector3f) ic.readSavable("endPosition", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/RotationTrack.java b/engine/src/core/com/jme3/cinematic/events/RotationTrack.java
new file mode 100644
index 0000000..60acf9d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/RotationTrack.java
@@ -0,0 +1,126 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class RotationTrack extends AbstractCinematicEvent {
+
+    private static final Logger log = Logger.getLogger(RotationTrack.class.getName());
+    private Quaternion startRotation = new Quaternion();
+    private Quaternion endRotation = new Quaternion();
+    private Spatial spatial;
+    private String spatialName = "";
+    private float value = 0;
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        if (spatial == null) {
+            spatial = cinematic.getScene().getChild(spatialName);
+            if (spatial == null) {
+            } else {
+                log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+            }
+        }
+    }
+
+    public RotationTrack() {
+    }
+
+    public RotationTrack(Spatial spatial, Quaternion endRotation) {
+        this.endRotation.set(endRotation);
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public RotationTrack(Spatial spatial, Quaternion endRotation, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.endRotation.set(endRotation);
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public RotationTrack(Spatial spatial, Quaternion endRotation, LoopMode loopMode) {
+        super(loopMode);
+        this.endRotation.set(endRotation);
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public RotationTrack(Spatial spatial, Quaternion endRotation, float initialDuration) {
+        super(initialDuration);
+        this.endRotation.set(endRotation);
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    @Override
+    public void onPlay() {
+        if (playState != playState.Paused) {
+            startRotation.set(spatial.getWorldRotation());
+        }
+        if (initialDuration == 0 && spatial != null) {
+            spatial.setLocalRotation(endRotation);
+            stop();
+        }
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+        if (spatial != null) {
+            value = Math.min(time / initialDuration, 1.0f);         
+            TempVars vars = TempVars.get();
+            Quaternion q = vars.quat1;
+            q.set(startRotation).slerp(endRotation, value);
+
+            spatial.setLocalRotation(q);
+            vars.release();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        value = 0;
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(spatialName, "spatialName", "");
+        oc.write(endRotation, "endRotation", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        spatialName = ic.readString("spatialName", "");
+        endRotation = (Quaternion) ic.readSavable("endRotation", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java b/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java
new file mode 100644
index 0000000..582adb5
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java
@@ -0,0 +1,121 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class ScaleTrack extends AbstractCinematicEvent {
+
+    private static final Logger log = Logger.getLogger(RotationTrack.class.getName());
+    private Vector3f startScale;
+    private Vector3f endScale;
+    private Spatial spatial;
+    private String spatialName = "";
+    private float value = 0;
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        if (spatial == null) {
+            spatial = cinematic.getScene().getChild(spatialName);
+            if (spatial == null) {
+            } else {
+                log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+            }
+        }
+    }
+
+    public ScaleTrack() {
+    }
+
+    public ScaleTrack(Spatial spatial, Vector3f endScale) {
+        this.endScale = endScale;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public ScaleTrack(Spatial spatial, Vector3f endScale, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.endScale = endScale;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public ScaleTrack(Spatial spatial, Vector3f endScale, LoopMode loopMode) {
+        super(loopMode);
+        this.endScale = endScale;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    public ScaleTrack(Spatial spatial, Vector3f endScale, float initialDuration) {
+        super(initialDuration);
+        this.endScale = endScale;
+        this.spatial = spatial;
+        spatialName = spatial.getName();
+    }
+
+    @Override
+    public void onPlay() {
+        if (playState != playState.Paused) {
+            startScale = spatial.getWorldScale().clone();
+        }
+        if (initialDuration == 0 && spatial != null) {
+            spatial.setLocalScale(endScale);
+            stop();
+        }
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+        if (spatial != null) {
+            value = Math.min(time / initialDuration, 1.0f);
+            spatial.setLocalScale(FastMath.interpolateLinear(value, startScale, endScale));
+        }
+    }
+
+    @Override
+    public void onStop() {
+        value = 0;
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(spatialName, "spatialName", "");
+        oc.write(endScale, "endScale", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        spatialName = ic.readString("spatialName", "");
+        endScale = (Vector3f) ic.readSavable("endScale", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/SoundTrack.java b/engine/src/core/com/jme3/cinematic/events/SoundTrack.java
new file mode 100644
index 0000000..77aae31
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/SoundTrack.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.audio.AudioNode;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * A sound track to be played in a cinematic.
+ * @author Nehon
+ */
+public class SoundTrack extends AbstractCinematicEvent {
+
+    protected String path;
+    protected AudioNode audioNode;
+    protected boolean stream = false;
+
+    /**
+     * creates a sound track from the given resource path
+     * @param path the path to an audi file (ie : "Sounds/mySound.wav")
+     */
+    public SoundTrack(String path) {
+        this.path = path;
+    }
+
+    /**
+     * creates a sound track from the given resource path
+     * @param path the path to an audi file (ie : "Sounds/mySound.wav")
+     * @param stream true to make the audio data streamed
+     */
+    public SoundTrack(String path, boolean stream) {
+        this(path);
+        this.stream = stream;
+    }
+
+    public SoundTrack(String path, boolean stream, float initialDuration) {
+        super(initialDuration);
+        this.path = path;
+        this.stream = stream;
+    }
+
+    public SoundTrack(String path, boolean stream, LoopMode loopMode) {
+        super(loopMode);
+        this.path = path;
+        this.stream = stream;
+    }
+
+    public SoundTrack(String path, boolean stream, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.path = path;
+        this.stream = stream;
+    }
+
+    public SoundTrack(String path, float initialDuration) {
+        super(initialDuration);
+        this.path = path;
+    }
+
+    public SoundTrack(String path, LoopMode loopMode) {
+        super(loopMode);
+        this.path = path;
+    }
+
+    public SoundTrack(String path, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.path = path;
+    }
+
+    public SoundTrack() {
+    }
+
+    @Override
+    public void initEvent(Application app, Cinematic cinematic) {
+        super.initEvent(app, cinematic);
+        audioNode = new AudioNode(app.getAssetManager(), path, stream);
+        setLoopMode(loopMode);
+    }
+
+    @Override
+    public void setTime(float time) {
+        super.setTime(time);
+        //can occur on rewind
+        if (time < 0) {            
+            stop();
+        }
+        audioNode.setTimeOffset(time);
+    }
+
+    @Override
+    public void onPlay() {
+        audioNode.play();
+    }
+
+    @Override
+    public void onStop() {
+        audioNode.stop();
+
+    }
+
+    @Override
+    public void onPause() {
+        audioNode.pause();
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+        if (audioNode.getStatus() == AudioNode.Status.Stopped) {
+            stop();
+        }
+    }
+
+    /**
+     *  Returns the underlying audion node of this sound track
+     * @return
+     */
+    public AudioNode getAudioNode() {
+        return audioNode;
+    }
+
+    @Override
+    public void setLoopMode(LoopMode loopMode) {
+        super.setLoopMode(loopMode);
+
+        if (loopMode != LoopMode.DontLoop) {
+            audioNode.setLooping(true);
+        } else {
+            audioNode.setLooping(false);
+        }
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(path, "path", "");
+        oc.write(stream, "stream", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        path = ic.readString("path", "");
+        stream = ic.readBoolean("stream", false);
+
+    }
+}
diff --git a/engine/src/core/com/jme3/collision/Collidable.java b/engine/src/core/com/jme3/collision/Collidable.java
new file mode 100644
index 0000000..65c0631
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/Collidable.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+/**
+ * Interface for Collidable objects.
+ * Classes that implement this interface are marked as collidable, meaning
+ * they support collision detection between other objects that are also
+ * collidable.
+ * 
+ * @author Kirill Vainer
+ */
+public interface Collidable {
+
+    /**
+     * Check collision with another Collidable.
+     * 
+     * @param other The object to check collision against
+     * @param results Will contain the list of {@link CollisionResult}s.
+     * @return how many collisions were found between this and other
+     */
+    public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException;
+}
diff --git a/engine/src/core/com/jme3/collision/CollisionResult.java b/engine/src/core/com/jme3/collision/CollisionResult.java
new file mode 100644
index 0000000..aef3936
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/CollisionResult.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+
+/**
+ * A <code>CollisionResult</code> represents a single collision instance
+ * between two {@link Collidable}. A collision check can result in many 
+ * collision instances (places where collision has occured).
+ * 
+ * @author Kirill Vainer
+ */
+public class CollisionResult implements Comparable<CollisionResult> {
+
+    private Geometry geometry;
+    private Vector3f contactPoint;
+    private Vector3f contactNormal;
+    private float distance;
+    private int triangleIndex;
+
+    public CollisionResult(Geometry geometry, Vector3f contactPoint, float distance, int triangleIndex) {
+        this.geometry = geometry;
+        this.contactPoint = contactPoint;
+        this.distance = distance;
+        this.triangleIndex = triangleIndex;
+    }
+
+    public CollisionResult(Vector3f contactPoint, float distance) {
+        this.contactPoint = contactPoint;
+        this.distance = distance;
+    }
+
+    public CollisionResult(){
+    }
+
+    public void setGeometry(Geometry geom){
+        this.geometry = geom;
+    }
+
+    public void setContactNormal(Vector3f norm){
+        this.contactNormal = norm;
+    }
+
+    public void setContactPoint(Vector3f point){
+        this.contactPoint = point;
+    }
+
+    public void setDistance(float dist){
+        this.distance = dist;
+    }
+
+    public void setTriangleIndex(int index){
+        this.triangleIndex = index;
+    }
+
+    public Triangle getTriangle(Triangle store){
+        if (store == null)
+            store = new Triangle();
+
+        Mesh m = geometry.getMesh();
+        m.getTriangle(triangleIndex, store);
+        store.calculateCenter();
+        store.calculateNormal();
+        return store;
+    }
+
+    public int compareTo(CollisionResult other) {
+        if (distance < other.distance)
+            return -1;
+        else if (distance > other.distance)
+            return 1;
+        else
+            return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if(obj instanceof CollisionResult){
+            return ((CollisionResult)obj).compareTo(this) == 0;
+        }
+        return super.equals(obj);
+    }
+    
+    public Vector3f getContactPoint() {
+        return contactPoint;
+    }
+
+    public Vector3f getContactNormal() {
+        return contactNormal;
+    }
+
+    public float getDistance() {
+        return distance;
+    }
+
+    public Geometry getGeometry() {
+        return geometry;
+    }
+
+    public int getTriangleIndex() {
+        return triangleIndex;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/collision/CollisionResults.java b/engine/src/core/com/jme3/collision/CollisionResults.java
new file mode 100644
index 0000000..ec691c1
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/CollisionResults.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * <code>CollisionResults</code> is a collection returned as a result of a 
+ * collision detection operation done by {@link Collidable}.
+ * 
+ * @author Kirill Vainer
+ */
+public class CollisionResults implements Iterable<CollisionResult> {
+
+    private final ArrayList<CollisionResult> results = new ArrayList<CollisionResult>();
+    private boolean sorted = true;
+
+    /**
+     * Clears all collision results added to this list
+     */
+    public void clear(){
+        results.clear();
+    }
+
+    /**
+     * Iterator for iterating over the collision results.
+     * 
+     * @return the iterator
+     */
+    public Iterator<CollisionResult> iterator() {
+        if (!sorted){
+            Collections.sort(results);
+            sorted = true;
+        }
+
+        return results.iterator();
+    }
+
+    public void addCollision(CollisionResult result){
+        results.add(result);
+        sorted = false;
+    }
+
+    public int size(){
+        return results.size();
+    }
+
+    public CollisionResult getClosestCollision(){
+        if (size() == 0)
+            return null;
+
+        if (!sorted){
+            Collections.sort(results);
+            sorted = true;
+        }
+
+        return results.get(0);
+    }
+
+    public CollisionResult getFarthestCollision(){
+        if (size() == 0)
+            return null;
+
+        if (!sorted){
+            Collections.sort(results);
+            sorted = true;
+        }
+
+        return results.get(size()-1);
+    }
+
+    public CollisionResult getCollision(int index){
+        if (!sorted){
+            Collections.sort(results);
+            sorted = true;
+        }
+
+        return results.get(index);
+    }
+
+    /**
+     * Internal use only.
+     * @param index
+     * @return
+     */
+    public CollisionResult getCollisionDirect(int index){
+        return results.get(index);
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        sb.append("CollisionResults[");
+        for (CollisionResult result : results){
+            sb.append(result).append(", ");
+        }
+        if (results.size() > 0)
+            sb.setLength(sb.length()-2);
+
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/engine/src/core/com/jme3/collision/MotionAllowedListener.java b/engine/src/core/com/jme3/collision/MotionAllowedListener.java
new file mode 100644
index 0000000..7703e5a
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/MotionAllowedListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.math.Vector3f;
+
+public interface MotionAllowedListener {
+
+    /**
+     * Check if motion allowed. Modify position and velocity vectors
+     * appropriately if not allowed..
+     * 
+     * @param position
+     * @param velocity
+     */
+    public void checkMotionAllowed(Vector3f position, Vector3f velocity);
+
+}
diff --git a/engine/src/core/com/jme3/collision/SweepSphere.java b/engine/src/core/com/jme3/collision/SweepSphere.java
new file mode 100644
index 0000000..9d43f89
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/SweepSphere.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.math.*;
+
+/**
+ * No longer public ..
+ *
+ * @author Kirill Vainer
+ */
+@Deprecated
+class SweepSphere implements Collidable {
+
+    private Vector3f velocity = new Vector3f();
+    private Vector3f center = new Vector3f();
+    private Vector3f dimension = new Vector3f();
+    private Vector3f invDim = new Vector3f();
+
+    private final Triangle scaledTri = new Triangle();
+    private final Plane triPlane = new Plane();
+    private final Vector3f temp1 = new Vector3f(),
+                           temp2 = new Vector3f(),
+                           temp3 = new Vector3f();
+    private final Vector3f sVelocity = new Vector3f(),
+                           sCenter = new Vector3f();
+
+    public Vector3f getCenter() {
+        return center;
+    }
+
+    public void setCenter(Vector3f center) {
+        this.center.set(center);
+    }
+
+    public Vector3f getDimension() {
+        return dimension;
+    }
+
+    public void setDimension(Vector3f dimension) {
+        this.dimension.set(dimension);
+        this.invDim.set(1,1,1).divideLocal(dimension);
+    }
+
+    public void setDimension(float x, float y, float z){
+        this.dimension.set(x,y,z);
+        this.invDim.set(1,1,1).divideLocal(dimension);
+    }
+
+    public void setDimension(float dim){
+        this.dimension.set(dim, dim, dim);
+        this.invDim.set(1,1,1).divideLocal(dimension);
+    }
+
+    public Vector3f getVelocity() {
+        return velocity;
+    }
+
+    public void setVelocity(Vector3f velocity) {
+        this.velocity.set(velocity);
+    }
+
+    private boolean pointsOnSameSide(Vector3f p1, Vector3f p2, Vector3f line1, Vector3f line2) {
+        // V1 = (line2 - line1) x (p1    - line1)
+        // V2 = (p2    - line1) x (line2 - line1)
+
+        temp1.set(line2).subtractLocal(line1);
+        temp3.set(temp1);
+        temp2.set(p1).subtractLocal(line1);
+        temp1.crossLocal(temp2);
+
+        temp2.set(p2).subtractLocal(line1);
+        temp3.crossLocal(temp2);
+
+        // V1 . V2 >= 0
+        return temp1.dot(temp3) >= 0;
+    }
+
+    private boolean isPointInTriangle(Vector3f point, AbstractTriangle tri) {
+            if (pointsOnSameSide(point, tri.get1(), tri.get2(), tri.get3())
+             && pointsOnSameSide(point, tri.get2(), tri.get1(), tri.get3())
+             && pointsOnSameSide(point, tri.get3(), tri.get1(), tri.get2()))
+                    return true;
+            return false;
+    }
+
+    private static float getLowestRoot(float a, float b, float c, float maxR) {
+        float determinant = b * b - 4f * a * c;
+        if (determinant < 0){
+            return Float.NaN;
+        }
+
+        float sqrtd = FastMath.sqrt(determinant);
+        float r1 = (-b - sqrtd) / (2f * a);
+        float r2 = (-b + sqrtd) / (2f * a);
+
+        if (r1 > r2){
+            float temp = r2;
+            r2 = r1;
+            r1 = temp;
+        }
+
+        if (r1 > 0 && r1 < maxR){
+            return r1;
+        }
+
+        if (r2 > 0 && r2 < maxR){
+            return r2;
+        }
+
+        return Float.NaN;
+    }
+
+    private float collideWithVertex(Vector3f sCenter, Vector3f sVelocity,
+                                    float velocitySquared, Vector3f vertex, float t) {
+        // A = velocity * velocity
+        // B = 2 * (velocity . (center - vertex));
+        // C = ||vertex - center||^2 - 1f;
+
+        temp1.set(sCenter).subtractLocal(vertex);
+        float a = velocitySquared;
+        float b = 2f * sVelocity.dot(temp1);
+        float c = temp1.negateLocal().lengthSquared() - 1f;
+        float newT = getLowestRoot(a, b, c, t);
+
+//        float A = velocitySquared;
+//        float B = sCenter.subtract(vertex).dot(sVelocity) * 2f;
+//        float C = vertex.subtract(sCenter).lengthSquared() - 1f;
+//
+//        float newT = getLowestRoot(A, B, C, Float.MAX_VALUE);
+//        if (newT > 1.0f)
+//            newT = Float.NaN;
+        
+        return newT;
+    }
+
+    private float collideWithSegment(Vector3f sCenter,
+                                     Vector3f sVelocity,
+                                     float velocitySquared,
+                                     Vector3f l1,
+                                     Vector3f l2,
+                                     float t,
+                                     Vector3f store) {
+        Vector3f edge = temp1.set(l2).subtractLocal(l1);
+        Vector3f base = temp2.set(l1).subtractLocal(sCenter);
+
+        float edgeSquared = edge.lengthSquared();
+        float baseSquared = base.lengthSquared();
+
+        float EdotV = edge.dot(sVelocity);
+        float EdotB = edge.dot(base);
+
+        float a = (edgeSquared * -velocitySquared) + EdotV * EdotV;
+        float b = (edgeSquared * 2f * sVelocity.dot(base))
+                - (2f * EdotV * EdotB);
+        float c = (edgeSquared * (1f - baseSquared)) + EdotB * EdotB;
+        float newT = getLowestRoot(a, b, c, t);
+        if (!Float.isNaN(newT)){
+            float f = (EdotV * newT - EdotB) / edgeSquared;
+            if (f >= 0f && f < 1f){
+                store.scaleAdd(f, edge, l1);
+                return newT;
+            }
+        }
+        return Float.NaN;
+    }
+
+    private CollisionResult collideWithTriangle(AbstractTriangle tri){
+        // scale scaledTriangle based on dimension
+        scaledTri.get1().set(tri.get1()).multLocal(invDim);
+        scaledTri.get2().set(tri.get2()).multLocal(invDim);
+        scaledTri.get3().set(tri.get3()).multLocal(invDim);
+//        Vector3f sVelocity = velocity.mult(invDim);
+//        Vector3f sCenter = center.mult(invDim);
+        velocity.mult(invDim, sVelocity);
+        center.mult(invDim, sCenter);
+
+        triPlane.setPlanePoints(scaledTri);
+
+        float normalDotVelocity = triPlane.getNormal().dot(sVelocity);
+        // XXX: sVelocity.normalize() needed?
+        // back facing scaledTriangles not considered
+        if (normalDotVelocity > 0f)
+            return null;
+
+        float t0, t1;
+        boolean embedded = false;
+
+        float signedDistanceToPlane = triPlane.pseudoDistance(sCenter);
+        if (normalDotVelocity == 0.0f){
+            // we are travelling exactly parrallel to the plane
+            if (FastMath.abs(signedDistanceToPlane) >= 1.0f){
+                // no collision possible
+                return null;
+            }else{
+                // we are embedded
+                t0 = 0;
+                t1 = 1;
+                embedded = true;
+                System.out.println("EMBEDDED");
+                return null;
+            }
+        }else{
+            t0 = (-1f - signedDistanceToPlane) / normalDotVelocity;
+            t1 = ( 1f - signedDistanceToPlane) / normalDotVelocity;
+
+            if (t0 > t1){
+                float tf = t1;
+                t1 = t0;
+                t0 = tf;
+            }
+
+            if (t0 > 1.0f || t1 < 0.0f){
+                // collision is out of this sVelocity range
+                return null;
+            }
+
+            // clamp the interval to [0, 1]
+            t0 = Math.max(t0, 0.0f);
+            t1 = Math.min(t1, 1.0f);
+        }
+
+        boolean foundCollision = false;
+        float minT = 1f;
+
+        Vector3f contactPoint = new Vector3f();
+        Vector3f contactNormal = new Vector3f();
+  
+//        if (!embedded){
+            // check against the inside of the scaledTriangle
+            // contactPoint = sCenter - p.normal + t0 * sVelocity
+            contactPoint.set(sVelocity);
+            contactPoint.multLocal(t0);
+            contactPoint.addLocal(sCenter);
+            contactPoint.subtractLocal(triPlane.getNormal());
+
+            // test to see if the collision is on a scaledTriangle interior
+            if (isPointInTriangle(contactPoint, scaledTri) && !embedded){
+                foundCollision = true;
+
+                minT = t0;
+
+                // scale collision point back into R3
+                contactPoint.multLocal(dimension);
+                contactNormal.set(velocity).multLocal(t0);
+                contactNormal.addLocal(center);
+                contactNormal.subtractLocal(contactPoint).normalizeLocal();
+
+//                contactNormal.set(triPlane.getNormal());
+                
+                CollisionResult result = new CollisionResult();
+                result.setContactPoint(contactPoint);
+                result.setContactNormal(contactNormal);
+                result.setDistance(minT * velocity.length());
+                return result;
+            }
+//        }
+
+        float velocitySquared = sVelocity.lengthSquared();
+
+        Vector3f v1 = scaledTri.get1();
+        Vector3f v2 = scaledTri.get2();
+        Vector3f v3 = scaledTri.get3();
+
+        // vertex 1
+        float newT;
+        newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v1, minT);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            contactPoint.set(v1);
+            foundCollision = true;
+        }
+
+        // vertex 2
+        newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v2, minT);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            contactPoint.set(v2);
+            foundCollision = true;
+        }
+
+        // vertex 3
+        newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v3, minT);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            contactPoint.set(v3);
+            foundCollision = true;
+        }
+
+        // edge 1-2
+        newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v1, v2, minT, contactPoint);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            foundCollision = true;
+        }
+
+        // edge 2-3
+        newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v2, v3, minT, contactPoint);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            foundCollision = true;
+        }
+
+        // edge 3-1
+        newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v3, v1, minT, contactPoint);
+        if (!Float.isNaN(newT)){
+            minT = newT;
+            foundCollision = true;
+        }
+
+        if (foundCollision){
+            // compute contact normal based on minimum t
+            contactPoint.multLocal(dimension);
+            contactNormal.set(velocity).multLocal(t0);
+            contactNormal.addLocal(center);
+            contactNormal.subtractLocal(contactPoint).normalizeLocal();
+
+            CollisionResult result = new CollisionResult();
+            result.setContactPoint(contactPoint);
+            result.setContactNormal(contactNormal);
+            result.setDistance(minT * velocity.length());
+
+            return result;
+        }else{
+            return null;
+        }
+    }
+
+    public CollisionResult collideWithSweepSphere(SweepSphere other){
+        temp1.set(velocity).subtractLocal(other.velocity);
+        temp2.set(center).subtractLocal(other.center);
+        temp3.set(dimension).addLocal(other.dimension);
+        // delta V
+        // delta C
+        // Rsum
+
+        float a = temp1.lengthSquared();
+        float b = 2f * temp1.dot(temp2);
+        float c = temp2.lengthSquared() - temp3.getX() * temp3.getX();
+        float t = getLowestRoot(a, b, c, 1);
+
+        // no collision
+        if (Float.isNaN(t))
+            return null;
+
+        CollisionResult result = new CollisionResult();
+        result.setDistance(velocity.length() * t);
+
+        temp1.set(velocity).multLocal(t).addLocal(center);
+        temp2.set(other.velocity).multLocal(t).addLocal(other.center);
+        temp3.set(temp2).subtractLocal(temp1);
+        // temp3 contains center to other.center vector
+
+        // normalize it to get normal
+        temp2.set(temp3).normalizeLocal();
+        result.setContactNormal(new Vector3f(temp2));
+
+        // temp3 is contact point
+        temp3.set(temp2).multLocal(dimension).addLocal(temp1);
+        result.setContactPoint(new Vector3f(temp3));
+        
+        return result;
+    }
+
+    public static void main(String[] args){
+        SweepSphere ss = new SweepSphere();
+        ss.setCenter(Vector3f.ZERO);
+        ss.setDimension(1);
+        ss.setVelocity(new Vector3f(10, 10, 10));
+
+        SweepSphere ss2 = new SweepSphere();
+        ss2.setCenter(new Vector3f(5, 5, 5));
+        ss2.setDimension(1);
+        ss2.setVelocity(new Vector3f(-10, -10, -10));
+
+        CollisionResults cr = new CollisionResults();
+        ss.collideWith(ss2, cr);
+        if (cr.size() > 0){
+            CollisionResult c = cr.getClosestCollision();
+            System.out.println("D = "+c.getDistance());
+            System.out.println("P = "+c.getContactPoint());
+            System.out.println("N = "+c.getContactNormal());
+        }
+    }
+
+    public int collideWith(Collidable other, CollisionResults results)
+            throws UnsupportedCollisionException {
+        if (other instanceof AbstractTriangle){
+            AbstractTriangle tri = (AbstractTriangle) other;
+            CollisionResult result = collideWithTriangle(tri);
+            if (result != null){
+                results.addCollision(result);
+                return 1;
+            }
+            return 0;
+        }else if (other instanceof SweepSphere){
+            SweepSphere sph = (SweepSphere) other;
+
+            CollisionResult result = collideWithSweepSphere(sph);
+            if (result != null){
+                results.addCollision(result);
+                return 1;
+            }
+            return 0;
+        }else{
+            throw new UnsupportedCollisionException();
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java b/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java
new file mode 100644
index 0000000..ddd6779
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+/**
+ * Thrown by {@link Collidable} when the requested collision query could not
+ * be completed because one of the collidables does not support colliding with
+ * the other.
+ * 
+ * @author Kirill Vainer
+ */
+public class UnsupportedCollisionException extends UnsupportedOperationException {
+
+    public UnsupportedCollisionException(Throwable arg0) {
+        super(arg0);
+    }
+
+    public UnsupportedCollisionException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    public UnsupportedCollisionException(String arg0) {
+        super(arg0);
+    }
+
+    public UnsupportedCollisionException() {
+        super();
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHNode.java b/engine/src/core/com/jme3/collision/bih/BIHNode.java
new file mode 100644
index 0000000..0f5c1c8
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHNode.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2009-2010 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.collision.bih;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.*;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+
+/**
+ * Bounding Interval Hierarchy.
+ * Based on:
+ *
+ * Instant Ray Tracing: The Bounding Interval Hierarchy
+ * By Carsten Wächter and Alexander Keller
+ */
+public final class BIHNode implements Savable {
+
+    private int leftIndex, rightIndex;
+    private BIHNode left;
+    private BIHNode right;
+    private float leftPlane;
+    private float rightPlane;
+    private int axis;
+    
+    //Do not do this: It increases memory usage of each BIHNode by at least 56 bytes!
+    //
+    //private Triangle tmpTriangle = new Triangle();
+
+    public BIHNode(int l, int r) {
+        leftIndex = l;
+        rightIndex = r;
+        axis = 3; // indicates leaf
+    }
+
+    public BIHNode(int axis) {
+        this.axis = axis;
+    }
+
+    public BIHNode() {
+    }
+
+    public BIHNode getLeftChild() {
+        return left;
+    }
+
+    public void setLeftChild(BIHNode left) {
+        this.left = left;
+    }
+
+    public float getLeftPlane() {
+        return leftPlane;
+    }
+
+    public void setLeftPlane(float leftPlane) {
+        this.leftPlane = leftPlane;
+    }
+
+    public BIHNode getRightChild() {
+        return right;
+    }
+
+    public void setRightChild(BIHNode right) {
+        this.right = right;
+    }
+
+    public float getRightPlane() {
+        return rightPlane;
+    }
+
+    public void setRightPlane(float rightPlane) {
+        this.rightPlane = rightPlane;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(leftIndex, "left_index", 0);
+        oc.write(rightIndex, "right_index", 0);
+        oc.write(leftPlane, "left_plane", 0);
+        oc.write(rightPlane, "right_plane", 0);
+        oc.write(axis, "axis", 0);
+        oc.write(left, "left_node", null);
+        oc.write(right, "right_node", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        leftIndex = ic.readInt("left_index", 0);
+        rightIndex = ic.readInt("right_index", 0);
+        leftPlane = ic.readFloat("left_plane", 0);
+        rightPlane = ic.readFloat("right_plane", 0);
+        axis = ic.readInt("axis", 0);
+        left = (BIHNode) ic.readSavable("left_node", null);
+        right = (BIHNode) ic.readSavable("right_node", null);
+    }
+
+    public static final class BIHStackData {
+
+        private final BIHNode node;
+        private final float min, max;
+
+        BIHStackData(BIHNode node, float min, float max) {
+            this.node = node;
+            this.min = min;
+            this.max = max;
+        }
+    }
+
+    public final int intersectWhere(Collidable col,
+            BoundingBox box,
+            Matrix4f worldMatrix,
+            BIHTree tree,
+            CollisionResults results) {
+
+        TempVars vars = TempVars.get();
+        ArrayList<BIHStackData> stack = vars.bihStack;
+        stack.clear();
+
+        float[] minExts = {box.getCenter().x - box.getXExtent(),
+            box.getCenter().y - box.getYExtent(),
+            box.getCenter().z - box.getZExtent()};
+
+        float[] maxExts = {box.getCenter().x + box.getXExtent(),
+            box.getCenter().y + box.getYExtent(),
+            box.getCenter().z + box.getZExtent()};
+
+        stack.add(new BIHStackData(this, 0, 0));
+
+        Triangle t = new Triangle();
+        int cols = 0;
+
+        stackloop:
+        while (stack.size() > 0) {
+            BIHNode node = stack.remove(stack.size() - 1).node;
+
+            while (node.axis != 3) {
+                int a = node.axis;
+
+                float maxExt = maxExts[a];
+                float minExt = minExts[a];
+
+                if (node.leftPlane < node.rightPlane) {
+                    // means there's a gap in the middle
+                    // if the box is in that gap, we stop there
+                    if (minExt > node.leftPlane
+                            && maxExt < node.rightPlane) {
+                        continue stackloop;
+                    }
+                }
+
+                if (maxExt < node.rightPlane) {
+                    node = node.left;
+                } else if (minExt > node.leftPlane) {
+                    node = node.right;
+                } else {
+                    stack.add(new BIHStackData(node.right, 0, 0));
+                    node = node.left;
+                }
+//                if (maxExt < node.leftPlane
+//                 && maxExt < node.rightPlane){
+//                    node = node.left;
+//                }else if (minExt > node.leftPlane
+//                       && minExt > node.rightPlane){
+//                    node = node.right;
+//                }else{
+
+//                }
+            }
+
+            for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+                tree.getTriangle(i, t.get1(), t.get2(), t.get3());
+                if (worldMatrix != null) {
+                    worldMatrix.mult(t.get1(), t.get1());
+                    worldMatrix.mult(t.get2(), t.get2());
+                    worldMatrix.mult(t.get3(), t.get3());
+                }
+
+                int added = col.collideWith(t, results);
+
+                if (added > 0) {
+                    int index = tree.getTriangleIndex(i);
+                    int start = results.size() - added;
+
+                    for (int j = start; j < results.size(); j++) {
+                        CollisionResult cr = results.getCollisionDirect(j);
+                        cr.setTriangleIndex(index);
+                    }
+
+                    cols += added;
+                }
+            }
+        }
+        vars.release();
+        return cols;
+    }
+
+    public final int intersectBrute(Ray r,
+            Matrix4f worldMatrix,
+            BIHTree tree,
+            float sceneMin,
+            float sceneMax,
+            CollisionResults results) {
+        float tHit = Float.POSITIVE_INFINITY;
+        
+        TempVars vars = TempVars.get();
+
+        Vector3f v1 = vars.vect1,
+                v2 = vars.vect2,
+                v3 = vars.vect3;
+
+        int cols = 0;
+
+        ArrayList<BIHStackData> stack = vars.bihStack;
+        stack.clear();
+        stack.add(new BIHStackData(this, 0, 0));
+        stackloop:
+        while (stack.size() > 0) {
+
+            BIHStackData data = stack.remove(stack.size() - 1);
+            BIHNode node = data.node;
+
+            leafloop:
+            while (node.axis != 3) { // while node is not a leaf
+                BIHNode nearNode, farNode;
+                nearNode = node.left;
+                farNode = node.right;
+
+                stack.add(new BIHStackData(farNode, 0, 0));
+                node = nearNode;
+            }
+
+            // a leaf
+            for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+                tree.getTriangle(i, v1, v2, v3);
+
+                if (worldMatrix != null) {
+                    worldMatrix.mult(v1, v1);
+                    worldMatrix.mult(v2, v2);
+                    worldMatrix.mult(v3, v3);
+                }
+
+                float t = r.intersects(v1, v2, v3);
+                if (t < tHit) {
+                    tHit = t;
+                    Vector3f contactPoint = new Vector3f(r.direction).multLocal(tHit).addLocal(r.origin);
+                    CollisionResult cr = new CollisionResult(contactPoint, tHit);
+                    cr.setTriangleIndex(tree.getTriangleIndex(i));
+                    results.addCollision(cr);
+                    cols++;
+                }
+            }
+        }
+        vars.release();
+        return cols;
+    }
+
+    public final int intersectWhere(Ray r,
+            Matrix4f worldMatrix,
+            BIHTree tree,
+            float sceneMin,
+            float sceneMax,
+            CollisionResults results) {
+
+        TempVars vars = TempVars.get();
+        ArrayList<BIHStackData> stack = vars.bihStack;
+        stack.clear();
+
+//        float tHit = Float.POSITIVE_INFINITY;
+
+        Vector3f o = vars.vect1.set(r.getOrigin());
+        Vector3f d =  vars.vect2.set(r.getDirection());
+
+        Matrix4f inv =vars.tempMat4.set(worldMatrix).invertLocal();
+
+        inv.mult(r.getOrigin(), r.getOrigin());
+
+        // Fixes rotation collision bug
+        inv.multNormal(r.getDirection(), r.getDirection());
+//        inv.multNormalAcross(r.getDirection(), r.getDirection());
+
+        float[] origins = {r.getOrigin().x,
+            r.getOrigin().y,
+            r.getOrigin().z};
+
+        float[] invDirections = {1f / r.getDirection().x,
+            1f / r.getDirection().y,
+            1f / r.getDirection().z};
+
+        r.getDirection().normalizeLocal();
+
+        Vector3f v1 = vars.vect3,
+                v2 = vars.vect4,
+                v3 = vars.vect5;
+        int cols = 0;
+
+        stack.add(new BIHStackData(this, sceneMin, sceneMax));
+        stackloop:
+        while (stack.size() > 0) {
+
+            BIHStackData data = stack.remove(stack.size() - 1);
+            BIHNode node = data.node;
+            float tMin = data.min,
+                    tMax = data.max;
+
+            if (tMax < tMin) {
+                continue;
+            }
+
+            leafloop:
+            while (node.axis != 3) { // while node is not a leaf
+                int a = node.axis;
+
+                // find the origin and direction value for the given axis
+                float origin = origins[a];
+                float invDirection = invDirections[a];
+
+                float tNearSplit, tFarSplit;
+                BIHNode nearNode, farNode;
+
+                tNearSplit = (node.leftPlane - origin) * invDirection;
+                tFarSplit = (node.rightPlane - origin) * invDirection;
+                nearNode = node.left;
+                farNode = node.right;
+
+                if (invDirection < 0) {
+                    float tmpSplit = tNearSplit;
+                    tNearSplit = tFarSplit;
+                    tFarSplit = tmpSplit;
+
+                    BIHNode tmpNode = nearNode;
+                    nearNode = farNode;
+                    farNode = tmpNode;
+                }
+
+                if (tMin > tNearSplit && tMax < tFarSplit) {
+                    continue stackloop;
+                }
+
+                if (tMin > tNearSplit) {
+                    tMin = max(tMin, tFarSplit);
+                    node = farNode;
+                } else if (tMax < tFarSplit) {
+                    tMax = min(tMax, tNearSplit);
+                    node = nearNode;
+                } else {
+                    stack.add(new BIHStackData(farNode, max(tMin, tFarSplit), tMax));
+                    tMax = min(tMax, tNearSplit);
+                    node = nearNode;
+                }
+            }
+
+//            if ( (node.rightIndex - node.leftIndex) > minTrisPerNode){
+//                // on demand subdivision
+//                node.subdivide();
+//                stack.add(new BIHStackData(node, tMin, tMax));
+//                continue stackloop;
+//            }
+
+            // a leaf
+            for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+                tree.getTriangle(i, v1, v2, v3);
+
+                float t = r.intersects(v1, v2, v3);
+                if (!Float.isInfinite(t)) {
+                    if (worldMatrix != null) {
+                        worldMatrix.mult(v1, v1);
+                        worldMatrix.mult(v2, v2);
+                        worldMatrix.mult(v3, v3);
+                        float t_world = new Ray(o, d).intersects(v1, v2, v3);
+                        t = t_world;
+                    }
+
+                    Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);
+                    Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);
+                    float worldSpaceDist = o.distance(contactPoint);
+
+                    CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);
+                    cr.setContactNormal(contactNormal);
+                    cr.setTriangleIndex(tree.getTriangleIndex(i));
+                    results.addCollision(cr);
+                    cols++;
+                }
+            }
+        }
+        vars.release();
+        r.setOrigin(o);
+        r.setDirection(d);
+
+        return cols;
+    }
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHTree.java b/engine/src/core/com/jme3/collision/bih/BIHTree.java
new file mode 100644
index 0000000..3660922
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHTree.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2009-2010 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.collision.bih;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.CollisionData;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.scene.mesh.VirtualIndexBuffer;
+import com.jme3.scene.mesh.WrappedIndexBuffer;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import static java.lang.Math.max;
+import java.nio.FloatBuffer;
+
+public class BIHTree implements CollisionData {
+
+    public static final int MAX_TREE_DEPTH = 100;
+    public static final int MAX_TRIS_PER_NODE = 21;
+    private Mesh mesh;
+    private BIHNode root;
+    private int maxTrisPerNode;
+    private int numTris;
+    private float[] pointData;
+    private int[] triIndices;
+    
+    private transient CollisionResults boundResults = new CollisionResults();
+    private transient float[] bihSwapTmp;
+    
+    private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[]
+    {
+        new TriangleAxisComparator(0),
+        new TriangleAxisComparator(1),
+        new TriangleAxisComparator(2)
+    };
+
+    private void initTriList(FloatBuffer vb, IndexBuffer ib) {
+        pointData = new float[numTris * 3 * 3];
+        int p = 0;
+        for (int i = 0; i < numTris * 3; i += 3) {
+            int vert = ib.get(i) * 3;
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert);
+
+            vert = ib.get(i + 1) * 3;
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert);
+
+            vert = ib.get(i + 2) * 3;
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert++);
+            pointData[p++] = vb.get(vert);
+        }
+
+        triIndices = new int[numTris];
+        for (int i = 0; i < numTris; i++) {
+            triIndices[i] = i;
+        }
+    }
+
+    public BIHTree(Mesh mesh, int maxTrisPerNode) {
+        this.mesh = mesh;
+        this.maxTrisPerNode = maxTrisPerNode;
+
+        if (maxTrisPerNode < 1 || mesh == null) {
+            throw new IllegalArgumentException();
+        }
+
+        bihSwapTmp = new float[9];
+
+        FloatBuffer vb = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        IndexBuffer ib = mesh.getIndexBuffer();
+        if (ib == null) {
+            ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode());
+        } else if (mesh.getMode() != Mode.Triangles) {
+            ib = new WrappedIndexBuffer(mesh);
+        }
+
+        numTris = ib.size() / 3;
+        initTriList(vb, ib);
+    }
+
+    public BIHTree(Mesh mesh) {
+        this(mesh, MAX_TRIS_PER_NODE);
+    }
+
+    public BIHTree() {
+    }
+
+    public void construct() {
+        BoundingBox sceneBbox = createBox(0, numTris - 1);
+        root = createNode(0, numTris - 1, sceneBbox, 0);
+    }
+
+    private BoundingBox createBox(int l, int r) {
+        TempVars vars = TempVars.get();
+
+        Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
+        Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
+
+        Vector3f v1 = vars.vect3,
+                v2 = vars.vect4,
+                v3 = vars.vect5;
+
+        for (int i = l; i <= r; i++) {
+            getTriangle(i, v1, v2, v3);
+            BoundingBox.checkMinMax(min, max, v1);
+            BoundingBox.checkMinMax(min, max, v2);
+            BoundingBox.checkMinMax(min, max, v3);
+        }
+
+        BoundingBox bbox = new BoundingBox(min, max);
+        vars.release();
+        return bbox;
+    }
+
+    int getTriangleIndex(int triIndex) {
+        return triIndices[triIndex];
+    }
+
+    private int sortTriangles(int l, int r, float split, int axis) {
+        int pivot = l;
+        int j = r;
+
+        TempVars vars = TempVars.get();
+
+        Vector3f v1 = vars.vect1,
+                v2 = vars.vect2,
+                v3 = vars.vect3;
+
+        while (pivot <= j) {
+            getTriangle(pivot, v1, v2, v3);
+            v1.addLocal(v2).addLocal(v3).multLocal(FastMath.ONE_THIRD);
+            if (v1.get(axis) > split) {
+                swapTriangles(pivot, j);
+                --j;
+            } else {
+                ++pivot;
+            }
+        }
+
+        vars.release();
+        pivot = (pivot == l && j < pivot) ? j : pivot;
+        return pivot;
+    }
+
+    private void setMinMax(BoundingBox bbox, boolean doMin, int axis, float value) {
+        Vector3f min = bbox.getMin(null);
+        Vector3f max = bbox.getMax(null);
+
+        if (doMin) {
+            min.set(axis, value);
+        } else {
+            max.set(axis, value);
+        }
+
+        bbox.setMinMax(min, max);
+    }
+
+    private float getMinMax(BoundingBox bbox, boolean doMin, int axis) {
+        if (doMin) {
+            return bbox.getMin(null).get(axis);
+        } else {
+            return bbox.getMax(null).get(axis);
+        }
+    }
+
+//    private BIHNode createNode2(int l, int r, BoundingBox nodeBbox, int depth){
+//        if ((r - l) < maxTrisPerNode || depth > 100)
+//            return createLeaf(l, r);
+//
+//        BoundingBox currentBox = createBox(l, r);
+//        int axis = depth % 3;
+//        float split = currentBox.getCenter().get(axis);
+//
+//        TriangleAxisComparator comparator = comparators[axis];
+//        Arrays.sort(tris, l, r, comparator);
+//        int splitIndex = -1;
+//
+//        float leftPlane, rightPlane = Float.POSITIVE_INFINITY;
+//        leftPlane = tris[l].getExtreme(axis, false);
+//        for (int i = l; i <= r; i++){
+//            BIHTriangle tri = tris[i];
+//            if (splitIndex == -1){
+//                float v = tri.getCenter().get(axis);
+//                if (v > split){
+//                    if (i == 0){
+//                        // no left plane
+//                        splitIndex = -2;
+//                    }else{
+//                        splitIndex = i;
+//                        // first triangle assigned to right
+//                        rightPlane = tri.getExtreme(axis, true);
+//                    }
+//                }else{
+//                    // triangle assigned to left
+//                    float ex = tri.getExtreme(axis, false);
+//                    if (ex > leftPlane)
+//                        leftPlane = ex;
+//                }
+//            }else{
+//                float ex = tri.getExtreme(axis, true);
+//                if (ex < rightPlane)
+//                    rightPlane = ex;
+//            }
+//        }
+//
+//        if (splitIndex < 0){
+//            splitIndex = (r - l) / 2;
+//
+//            leftPlane = Float.NEGATIVE_INFINITY;
+//            rightPlane = Float.POSITIVE_INFINITY;
+//
+//            for (int i = l; i < splitIndex; i++){
+//                float ex = tris[i].getExtreme(axis, false);
+//                if (ex > leftPlane){
+//                    leftPlane = ex;
+//                }
+//            }
+//            for (int i = splitIndex; i <= r; i++){
+//                float ex = tris[i].getExtreme(axis, true);
+//                if (ex < rightPlane){
+//                    rightPlane = ex;
+//                }
+//            }
+//        }
+//
+//        BIHNode node = new BIHNode(axis);
+//        node.leftPlane = leftPlane;
+//        node.rightPlane = rightPlane;
+//
+//        node.leftIndex = l;
+//        node.rightIndex = r;
+//
+//        BoundingBox leftBbox = new BoundingBox(currentBox);
+//        setMinMax(leftBbox, false, axis, split);
+//        node.left = createNode2(l, splitIndex-1, leftBbox, depth+1);
+//
+//        BoundingBox rightBbox = new BoundingBox(currentBox);
+//        setMinMax(rightBbox, true, axis, split);
+//        node.right = createNode2(splitIndex, r, rightBbox, depth+1);
+//
+//        return node;
+//    }
+    private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) {
+        if ((r - l) < maxTrisPerNode || depth > MAX_TREE_DEPTH) {
+            return new BIHNode(l, r);
+        }
+
+        BoundingBox currentBox = createBox(l, r);
+
+        Vector3f exteriorExt = nodeBbox.getExtent(null);
+        Vector3f interiorExt = currentBox.getExtent(null);
+        exteriorExt.subtractLocal(interiorExt);
+
+        int axis = 0;
+        if (exteriorExt.x > exteriorExt.y) {
+            if (exteriorExt.x > exteriorExt.z) {
+                axis = 0;
+            } else {
+                axis = 2;
+            }
+        } else {
+            if (exteriorExt.y > exteriorExt.z) {
+                axis = 1;
+            } else {
+                axis = 2;
+            }
+        }
+        if (exteriorExt.equals(Vector3f.ZERO)) {
+            axis = 0;
+        }
+
+//        Arrays.sort(tris, l, r, comparators[axis]);
+        float split = currentBox.getCenter().get(axis);
+        int pivot = sortTriangles(l, r, split, axis);
+        if (pivot == l || pivot == r) {
+            pivot = (r + l) / 2;
+        }
+
+        //If one of the partitions is empty, continue with recursion: same level but different bbox
+        if (pivot < l) {
+            //Only right
+            BoundingBox rbbox = new BoundingBox(currentBox);
+            setMinMax(rbbox, true, axis, split);
+            return createNode(l, r, rbbox, depth + 1);
+        } else if (pivot > r) {
+            //Only left
+            BoundingBox lbbox = new BoundingBox(currentBox);
+            setMinMax(lbbox, false, axis, split);
+            return createNode(l, r, lbbox, depth + 1);
+        } else {
+            //Build the node
+            BIHNode node = new BIHNode(axis);
+
+            //Left child
+            BoundingBox lbbox = new BoundingBox(currentBox);
+            setMinMax(lbbox, false, axis, split);
+
+            //The left node right border is the plane most right
+            node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis));
+            node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call
+
+            //Right Child
+            BoundingBox rbbox = new BoundingBox(currentBox);
+            setMinMax(rbbox, true, axis, split);
+            //The right node left border is the plane most left
+            node.setRightPlane(getMinMax(createBox(pivot, r), true, axis));
+            node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call
+
+            return node;
+        }
+    }
+
+    public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) {
+        int pointIndex = index * 9;
+
+        v1.x = pointData[pointIndex++];
+        v1.y = pointData[pointIndex++];
+        v1.z = pointData[pointIndex++];
+
+        v2.x = pointData[pointIndex++];
+        v2.y = pointData[pointIndex++];
+        v2.z = pointData[pointIndex++];
+
+        v3.x = pointData[pointIndex++];
+        v3.y = pointData[pointIndex++];
+        v3.z = pointData[pointIndex++];
+    }
+
+    public void swapTriangles(int index1, int index2) {
+        int p1 = index1 * 9;
+        int p2 = index2 * 9;
+
+        // store p1 in tmp
+        System.arraycopy(pointData, p1, bihSwapTmp, 0, 9);
+
+        // copy p2 to p1
+        System.arraycopy(pointData, p2, pointData, p1, 9);
+
+        // copy tmp to p2
+        System.arraycopy(bihSwapTmp, 0, pointData, p2, 9);
+
+        // swap indices
+        int tmp2 = triIndices[index1];
+        triIndices[index1] = triIndices[index2];
+        triIndices[index2] = tmp2;
+    }
+
+    private int collideWithRay(Ray r,
+            Matrix4f worldMatrix,
+            BoundingVolume worldBound,
+            CollisionResults results) {
+
+        boundResults.clear();
+        worldBound.collideWith(r, boundResults);
+        if (boundResults.size() > 0) {
+            float tMin = boundResults.getClosestCollision().getDistance();
+            float tMax = boundResults.getFarthestCollision().getDistance();
+
+            if (tMax <= 0) {
+                tMax = Float.POSITIVE_INFINITY;
+            } else if (tMin == tMax) {
+                tMin = 0;
+            }
+
+            if (tMin <= 0) {
+                tMin = 0;
+            }
+
+            if (r.getLimit() < Float.POSITIVE_INFINITY) {
+                tMax = Math.min(tMax, r.getLimit());
+                if (tMin > tMax){
+                    return 0;
+                }
+            }
+
+//            return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results);
+            return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results);
+        }
+        return 0;
+    }
+
+    private int collideWithBoundingVolume(BoundingVolume bv,
+            Matrix4f worldMatrix,
+            CollisionResults results) {
+        BoundingBox bbox;
+        if (bv instanceof BoundingSphere) {
+            BoundingSphere sphere = (BoundingSphere) bv;
+            bbox = new BoundingBox(bv.getCenter().clone(), sphere.getRadius(),
+                    sphere.getRadius(),
+                    sphere.getRadius());
+        } else if (bv instanceof BoundingBox) {
+            bbox = new BoundingBox((BoundingBox) bv);
+        } else {
+            throw new UnsupportedCollisionException();
+        }
+
+        bbox.transform(worldMatrix.invert(), bbox);
+        return root.intersectWhere(bv, bbox, worldMatrix, this, results);
+    }
+
+    public int collideWith(Collidable other,
+            Matrix4f worldMatrix,
+            BoundingVolume worldBound,
+            CollisionResults results) {
+
+        if (other instanceof Ray) {
+            Ray ray = (Ray) other;
+            return collideWithRay(ray, worldMatrix, worldBound, results);
+        } else if (other instanceof BoundingVolume) {
+            BoundingVolume bv = (BoundingVolume) other;
+            return collideWithBoundingVolume(bv, worldMatrix, results);
+        } else {
+            throw new UnsupportedCollisionException();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(mesh, "mesh", null);
+        oc.write(root, "root", null);
+        oc.write(maxTrisPerNode, "tris_per_node", 0);
+        oc.write(pointData, "points", null);
+        oc.write(triIndices, "indices", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        mesh = (Mesh) ic.readSavable("mesh", null);
+        root = (BIHNode) ic.readSavable("root", null);
+        maxTrisPerNode = ic.readInt("tris_per_node", 0);
+        pointData = ic.readFloatArray("points", null);
+        triIndices = ic.readIntArray("indices", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHTriangle.java b/engine/src/core/com/jme3/collision/bih/BIHTriangle.java
new file mode 100644
index 0000000..2229da0
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHTriangle.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.collision.bih;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+
+public final class BIHTriangle {
+
+    private final Vector3f pointa = new Vector3f();
+    private final Vector3f pointb = new Vector3f();
+    private final Vector3f pointc = new Vector3f();
+    private final Vector3f center = new Vector3f();
+
+    public BIHTriangle(Vector3f p1, Vector3f p2, Vector3f p3) {
+        pointa.set(p1);
+        pointb.set(p2);
+        pointc.set(p3);
+        center.set(pointa);
+        center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD);
+    }
+
+    public Vector3f get1(){
+        return pointa;
+    }
+
+    public Vector3f get2(){
+        return pointb;
+    }
+
+    public Vector3f get3(){
+        return pointc;
+    }
+
+    public Vector3f getCenter() {
+        return center;
+    }
+
+    public Vector3f getNormal(){
+        Vector3f normal = new Vector3f(pointb);
+        normal.subtractLocal(pointa).crossLocal(pointc.x-pointa.x, pointc.y-pointa.y, pointc.z-pointa.z);
+        normal.normalizeLocal();
+        return normal;
+    }
+
+    public float getExtreme(int axis, boolean left){
+        float v1, v2, v3;
+        switch (axis){
+            case 0: v1 = pointa.x; v2 = pointb.x; v3 = pointc.x; break;
+            case 1: v1 = pointa.y; v2 = pointb.y; v3 = pointc.y; break;
+            case 2: v1 = pointa.z; v2 = pointb.z; v3 = pointc.z; break;
+            default: assert false; return 0;
+        }
+        if (left){
+            if (v1 < v2){
+                if (v1 < v3)
+                    return v1;
+                else
+                    return v3;
+            }else{
+                if (v2 < v3)
+                    return v2;
+                else
+                    return v3;
+            }
+        }else{
+            if (v1 > v2){
+                if (v1 > v3)
+                    return v1;
+                else
+                    return v3;
+            }else{
+                if (v2 > v3)
+                    return v2;
+                else
+                    return v3;
+            }
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java b/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java
new file mode 100644
index 0000000..895dd5a
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 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.collision.bih;
+
+import com.jme3.math.Vector3f;
+import java.util.Comparator;
+
+public class TriangleAxisComparator implements Comparator<BIHTriangle> {
+
+    private final int axis;
+
+    public TriangleAxisComparator(int axis){
+        this.axis = axis;
+    }
+
+    public int compare(BIHTriangle o1, BIHTriangle o2) {
+        float v1, v2;
+        Vector3f c1 = o1.getCenter();
+        Vector3f c2 = o2.getCenter();
+        switch (axis){
+            case 0: v1 = c1.x; v2 = c2.x; break;
+            case 1: v1 = c1.y; v2 = c2.y; break;
+            case 2: v1 = c1.z; v2 = c2.z; break;
+            default: assert false; return 0;
+        }
+        if (v1 > v2)
+            return 1;
+        else if (v1 < v2)
+            return -1;
+        else
+            return 0;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/effect/Particle.java b/engine/src/core/com/jme3/effect/Particle.java
new file mode 100644
index 0000000..8e976f9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/Particle.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+
+/**
+ * Represents a single particle in a {@link ParticleEmitter}.
+ * 
+ * @author Kirill Vainer
+ */
+public class Particle {
+    
+    /**
+     * Particle velocity.
+     */
+    public final Vector3f velocity = new Vector3f();
+    
+    /**
+     * Current particle position
+     */
+    public final Vector3f position = new Vector3f();
+    
+    /**
+     * Particle color
+     */
+    public final ColorRGBA color = new ColorRGBA(0,0,0,0);
+    
+    /**
+     * Particle size or radius.
+     */
+    public float size;
+    
+    /**
+     * Particle remaining life, in seconds.
+     */
+    public float life;
+    
+    /**
+     * The initial particle life
+     */
+    public float startlife;
+    
+    /**
+     * Particle rotation angle (in radians).
+     */
+    public float angle;
+    
+    /**
+     * Particle rotation angle speed (in radians).
+     */
+    public float rotateSpeed;
+    
+    /**
+     * Particle image index. 
+     */
+    public int imageIndex = 0;
+    
+    /**
+     * Distance to camera. Only used for sorted particles.
+     */
+    //public float distToCam;
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleComparator.java b/engine/src/core/com/jme3/effect/ParticleComparator.java
new file mode 100644
index 0000000..dbe52dd
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleComparator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.renderer.Camera;
+import java.util.Comparator;
+
+@Deprecated
+class ParticleComparator implements Comparator<Particle> {
+
+    private Camera cam;
+
+    public void setCamera(Camera cam){
+        this.cam = cam;
+    }
+
+    public int compare(Particle p1, Particle p2) {
+        return 0; // unused
+        /*
+        if (p1.life <= 0 || p2.life <= 0)
+            return 0;
+
+//        if (p1.life <= 0)
+//            return 1;
+//        else if (p2.life <= 0)
+//            return -1;
+
+        float d1 = p1.distToCam, d2 = p2.distToCam;
+
+        if (d1 == -1){
+            d1 = cam.distanceToNearPlane(p1.position);
+            p1.distToCam = d1;
+        }
+        if (d2 == -1){
+            d2 = cam.distanceToNearPlane(p2.position);
+            p2.distToCam = d2;
+        }
+
+        if (d1 < d2)
+            return 1;
+        else if (d1 > d2)
+            return -1;
+        else
+            return 0;
+        */
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/effect/ParticleEmitter.java b/engine/src/core/com/jme3/effect/ParticleEmitter.java
new file mode 100644
index 0000000..c00e66e
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleEmitter.java
@@ -0,0 +1,1206 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.influencers.DefaultParticleInfluencer;
+import com.jme3.effect.influencers.ParticleInfluencer;
+import com.jme3.effect.shapes.EmitterPointShape;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <code>ParticleEmitter</code> is a special kind of geometry which simulates
+ * a particle system.
+ * <p>
+ * Particle emitters can be used to simulate various kinds of phenomena,
+ * such as fire, smoke, explosions and much more.
+ * <p>
+ * Particle emitters have many properties which are used to control the 
+ * simulation. The interpretation of these properties depends on the 
+ * {@link ParticleInfluencer} that has been assigned to the emitter via
+ * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
+ * By default the implementation {@link DefaultParticleInfluencer} is used.
+ * 
+ * @author Kirill Vainer
+ */
+public class ParticleEmitter extends Geometry {
+
+    private boolean enabled = true;
+    private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO);
+    private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer();
+    private ParticleEmitterControl control;
+    private EmitterShape shape = DEFAULT_SHAPE;
+    private ParticleMesh particleMesh;
+    private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
+    private ParticleMesh.Type meshType;
+    private Particle[] particles;
+    private int firstUnUsed;
+    private int lastUsed;
+//    private int next = 0;
+//    private ArrayList<Integer> unusedIndices = new ArrayList<Integer>();
+    private boolean randomAngle;
+    private boolean selectRandomImage;
+    private boolean facingVelocity;
+    private float particlesPerSec = 20;
+    private float timeDifference = 0;
+    private float lowLife = 3f;
+    private float highLife = 7f;
+    private Vector3f gravity = new Vector3f(0.0f, 0.1f, 0.0f);
+    private float rotateSpeed;
+    private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
+    private int imagesX = 1;
+    private int imagesY = 1;
+   
+    private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
+    private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
+    private float startSize = 0.2f;
+    private float endSize = 2f;
+    private boolean worldSpace = true;
+    //variable that helps with computations
+    private transient Vector3f temp = new Vector3f();
+
+    public static class ParticleEmitterControl implements Control {
+
+        ParticleEmitter parentEmitter;
+
+        public ParticleEmitterControl() {
+        }
+
+        public ParticleEmitterControl(ParticleEmitter parentEmitter) {
+            this.parentEmitter = parentEmitter;
+        }
+
+        public Control cloneForSpatial(Spatial spatial) {
+            return this; // WARNING: Sets wrong control on spatial. Will be
+            // fixed automatically by ParticleEmitter.clone() method.
+        }
+
+        public void setSpatial(Spatial spatial) {
+        }
+
+        public void setEnabled(boolean enabled) {
+            parentEmitter.setEnabled(enabled);
+        }
+
+        public boolean isEnabled() {
+            return parentEmitter.isEnabled();
+        }
+
+        public void update(float tpf) {
+            parentEmitter.updateFromControl(tpf);
+        }
+
+        public void render(RenderManager rm, ViewPort vp) {
+            parentEmitter.renderFromControl(rm, vp);
+        }
+
+        public void write(JmeExporter ex) throws IOException {
+        }
+
+        public void read(JmeImporter im) throws IOException {
+        }
+    }
+
+    @Override
+    public ParticleEmitter clone() {
+        return clone(true);
+    }
+
+    @Override
+    public ParticleEmitter clone(boolean cloneMaterial) {
+        ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
+        clone.shape = shape.deepClone();
+
+        // Reinitialize particle list
+        clone.setNumParticles(particles.length);
+
+        clone.faceNormal = faceNormal.clone();
+        clone.startColor = startColor.clone();
+        clone.endColor = endColor.clone();
+        clone.particleInfluencer = particleInfluencer.clone();
+
+        // remove wrong control
+        clone.controls.remove(control);
+
+        // put correct control
+        clone.controls.add(new ParticleEmitterControl(clone));
+
+        // Reinitialize particle mesh
+        switch (meshType) {
+            case Point:
+                clone.particleMesh = new ParticlePointMesh();
+                clone.setMesh(clone.particleMesh);
+                break;
+            case Triangle:
+                clone.particleMesh = new ParticleTriMesh();
+                clone.setMesh(clone.particleMesh);
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized particle type: " + meshType);
+        }
+        clone.particleMesh.initParticleData(clone, clone.particles.length);
+        clone.particleMesh.setImagesXY(clone.imagesX, clone.imagesY);
+
+        return clone;
+    }
+
+    public ParticleEmitter(String name, Type type, int numParticles) {
+        super(name);
+
+        // ignore world transform, unless user sets inLocalSpace
+        this.setIgnoreTransform(true);
+
+        // particles neither receive nor cast shadows
+        this.setShadowMode(ShadowMode.Off);
+
+        // particles are usually transparent
+        this.setQueueBucket(Bucket.Transparent);
+
+        meshType = type;
+
+        // Must create clone of shape/influencer so that a reference to a static is 
+        // not maintained
+        shape = shape.deepClone();
+        particleInfluencer = particleInfluencer.clone();
+
+        control = new ParticleEmitterControl(this);
+        controls.add(control);
+
+        switch (meshType) {
+            case Point:
+                particleMesh = new ParticlePointMesh();
+                this.setMesh(particleMesh);
+                break;
+            case Triangle:
+                particleMesh = new ParticleTriMesh();
+                this.setMesh(particleMesh);
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized particle type: " + meshType);
+        }
+        this.setNumParticles(numParticles);
+//        particleMesh.initParticleData(this, particles.length);
+    }
+
+    /**
+     * For serialization only. Do not use.
+     */
+    public ParticleEmitter() {
+        super();
+    }
+
+    public void setShape(EmitterShape shape) {
+        this.shape = shape;
+    }
+
+    public EmitterShape getShape() {
+        return shape;
+    }
+
+    /**
+     * Set the {@link ParticleInfluencer} to influence this particle emitter.
+     * 
+     * @param particleInfluencer the {@link ParticleInfluencer} to influence 
+     * this particle emitter.
+     * 
+     * @see ParticleInfluencer
+     */
+    public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
+        this.particleInfluencer = particleInfluencer;
+    }
+
+    /**
+     * Returns the {@link ParticleInfluencer} that influences this 
+     * particle emitter.
+     * 
+     * @return the {@link ParticleInfluencer} that influences this 
+     * particle emitter.
+     * 
+     * @see ParticleInfluencer
+     */
+    public ParticleInfluencer getParticleInfluencer() {
+        return particleInfluencer;
+    }
+
+    /**
+     * Returns the mesh type used by the particle emitter.
+     * 
+     * 
+     * @return the mesh type used by the particle emitter.
+     * 
+     * @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
+     * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int) 
+     */
+    public ParticleMesh.Type getMeshType() {
+        return meshType;
+    }
+
+    /**
+     * Sets the type of mesh used by the particle emitter.
+     * @param meshType The mesh type to use
+     */
+    public void setMeshType(ParticleMesh.Type meshType) {
+        this.meshType = meshType;
+        switch (meshType) {
+            case Point:
+                particleMesh = new ParticlePointMesh();
+                this.setMesh(particleMesh);
+                break;
+            case Triangle:
+                particleMesh = new ParticleTriMesh();
+                this.setMesh(particleMesh);
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized particle type: " + meshType);
+        }
+        this.setNumParticles(particles.length);
+    }
+
+    /**
+     * Returns true if particles should spawn in world space. 
+     * 
+     * @return true if particles should spawn in world space. 
+     * 
+     * @see ParticleEmitter#setInWorldSpace(boolean) 
+     */
+    public boolean isInWorldSpace() {
+        return worldSpace;
+    }
+
+    /**
+     * Set to true if particles should spawn in world space. 
+     * 
+     * <p>If set to true and the particle emitter is moved in the scene,
+     * then particles that have already spawned won't be effected by this
+     * motion. If set to false, the particles will emit in local space
+     * and when the emitter is moved, so are all the particles that
+     * were emitted previously.
+     * 
+     * @param worldSpace true if particles should spawn in world space. 
+     */
+    public void setInWorldSpace(boolean worldSpace) {
+        this.setIgnoreTransform(worldSpace);
+        this.worldSpace = worldSpace;
+    }
+
+    /**
+     * Returns the number of visible particles (spawned but not dead).
+     * 
+     * @return the number of visible particles
+     */
+    public int getNumVisibleParticles() {
+//        return unusedIndices.size() + next;
+        return lastUsed + 1;
+    }
+
+    /**
+     * Set the maximum amount of particles that
+     * can exist at the same time with this emitter.
+     * Calling this method many times is not recommended.
+     * 
+     * @param numParticles the maximum amount of particles that
+     * can exist at the same time with this emitter.
+     */
+    public final void setNumParticles(int numParticles) {
+        particles = new Particle[numParticles];
+        for (int i = 0; i < numParticles; i++) {
+            particles[i] = new Particle();
+        }
+        //We have to reinit the mesh's buffers with the new size
+        particleMesh.initParticleData(this, particles.length);
+        particleMesh.setImagesXY(this.imagesX, this.imagesY);
+        firstUnUsed = 0;
+        lastUsed = -1;
+    }
+
+    public int getMaxNumParticles() {
+        return particles.length;
+    }
+
+    /**
+     * Returns a list of all particles (shouldn't be used in most cases).
+     * 
+     * <p>
+     * This includes both existing and non-existing particles.
+     * The size of the array is set to the <code>numParticles</code> value
+     * specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
+     * method. 
+     * 
+     * @return a list of all particles.
+     */
+    public Particle[] getParticles() {
+        return particles;
+    }
+
+    /**
+     * Get the normal which particles are facing. 
+     * 
+     * @return the normal which particles are facing. 
+     * 
+     * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f) 
+     */
+    public Vector3f getFaceNormal() {
+        if (Vector3f.isValidVector(faceNormal)) {
+            return faceNormal;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the normal which particles are facing. 
+     * 
+     * <p>By default, particles
+     * will face the camera, but for some effects (e.g shockwave) it may
+     * be necessary to face a specific direction instead. To restore
+     * normal functionality, provide <code>null</code> as the argument for
+     * <code>faceNormal</code>.
+     *
+     * @param faceNormal The normals particles should face, or <code>null</code>
+     * if particles should face the camera.
+     */
+    public void setFaceNormal(Vector3f faceNormal) {
+        if (faceNormal == null || !Vector3f.isValidVector(faceNormal)) {
+            this.faceNormal.set(Vector3f.NAN);
+        } else {
+            this.faceNormal = faceNormal;
+        }
+    }
+
+    /**
+     * Returns the rotation speed in radians/sec for particles.
+     * 
+     * @return the rotation speed in radians/sec for particles.
+     * 
+     * @see ParticleEmitter#setRotateSpeed(float) 
+     */
+    public float getRotateSpeed() {
+        return rotateSpeed;
+    }
+
+    /**
+     * Set the rotation speed in radians/sec for particles
+     * spawned after the invocation of this method.
+     * 
+     * @param rotateSpeed the rotation speed in radians/sec for particles
+     * spawned after the invocation of this method.
+     */
+    public void setRotateSpeed(float rotateSpeed) {
+        this.rotateSpeed = rotateSpeed;
+    }
+
+    /**
+     * Returns true if every particle spawned
+     * should have a random facing angle. 
+     * 
+     * @return true if every particle spawned
+     * should have a random facing angle. 
+     * 
+     * @see ParticleEmitter#setRandomAngle(boolean) 
+     */
+    public boolean isRandomAngle() {
+        return randomAngle;
+    }
+
+    /**
+     * Set to true if every particle spawned
+     * should have a random facing angle. 
+     * 
+     * @param randomAngle if every particle spawned
+     * should have a random facing angle.
+     */
+    public void setRandomAngle(boolean randomAngle) {
+        this.randomAngle = randomAngle;
+    }
+
+    /**
+     * Returns true if every particle spawned should get a random
+     * image.
+     * 
+     * @return True if every particle spawned should get a random
+     * image.
+     * 
+     * @see ParticleEmitter#setSelectRandomImage(boolean) 
+     */
+    public boolean isSelectRandomImage() {
+        return selectRandomImage;
+    }
+
+    /**
+     * Set to true if every particle spawned
+     * should get a random image from a pool of images constructed from
+     * the texture, with X by Y possible images.
+     * 
+     * <p>By default, X and Y are equal
+     * to 1, thus allowing only 1 possible image to be selected, but if the
+     * particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
+     * and {#link ParticleEmitter#setImagesY(int) } methods, then multiple images
+     * can be selected. Setting to false will cause each particle to have an animation
+     * of images displayed, starting at image 1, and going until image X*Y when
+     * the particle reaches its end of life.
+     * 
+     * @param selectRandomImage True if every particle spawned should get a random
+     * image.
+     */
+    public void setSelectRandomImage(boolean selectRandomImage) {
+        this.selectRandomImage = selectRandomImage;
+    }
+
+    /**
+     * Check if particles spawned should face their velocity.
+     * 
+     * @return True if particles spawned should face their velocity.
+     * 
+     * @see ParticleEmitter#setFacingVelocity(boolean) 
+     */
+    public boolean isFacingVelocity() {
+        return facingVelocity;
+    }
+
+    /**
+     * Set to true if particles spawned should face
+     * their velocity (or direction to which they are moving towards).
+     * 
+     * <p>This is typically used for e.g spark effects.
+     * 
+     * @param followVelocity True if particles spawned should face their velocity.
+     * 
+     */
+    public void setFacingVelocity(boolean followVelocity) {
+        this.facingVelocity = followVelocity;
+    }
+
+    /**
+     * Get the end color of the particles spawned.
+     * 
+     * @return the end color of the particles spawned.
+     * 
+     * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA) 
+     */
+    public ColorRGBA getEndColor() {
+        return endColor;
+    }
+
+    /**
+     * Set the end color of the particles spawned.
+     * 
+     * <p>The
+     * particle color at any time is determined by blending the start color
+     * and end color based on the particle's current time of life relative
+     * to its end of life.
+     * 
+     * @param endColor the end color of the particles spawned.
+     */
+    public void setEndColor(ColorRGBA endColor) {
+        this.endColor.set(endColor);
+    }
+
+    /**
+     * Get the end size of the particles spawned.
+     * 
+     * @return the end size of the particles spawned.
+     * 
+     * @see ParticleEmitter#setEndSize(float) 
+     */
+    public float getEndSize() {
+        return endSize;
+    }
+
+    /**
+     * Set the end size of the particles spawned.
+     * 
+     * <p>The
+     * particle size at any time is determined by blending the start size
+     * and end size based on the particle's current time of life relative
+     * to its end of life.
+     * 
+     * @param endSize the end size of the particles spawned.
+     */
+    public void setEndSize(float endSize) {
+        this.endSize = endSize;
+    }
+
+    /**
+     * Get the gravity vector.
+     * 
+     * @return the gravity vector.
+     * 
+     * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f) 
+     */
+    public Vector3f getGravity() {
+        return gravity;
+    }
+
+    /**
+     * This method sets the gravity vector.
+     * 
+     * @param gravity the gravity vector
+     */
+    public void setGravity(Vector3f gravity) {
+        this.gravity.set(gravity);
+    }
+
+    /**
+     * Sets the gravity vector.
+     * 
+     * @param x the x component of the gravity vector
+     * @param y the y component of the gravity vector
+     * @param z the z component of the gravity vector
+     */
+    public void setGravity(float x, float y, float z) {
+        this.gravity.x = x;
+        this.gravity.y = y;
+        this.gravity.z = z;
+    }
+
+    /**
+     * Get the high value of life.
+     * 
+     * @return the high value of life.
+     * 
+     * @see ParticleEmitter#setHighLife(float) 
+     */
+    public float getHighLife() {
+        return highLife;
+    }
+
+    /**
+     * Set the high value of life.
+     * 
+     * <p>The particle's lifetime/expiration
+     * is determined by randomly selecting a time between low life and high life.
+     * 
+     * @param highLife the high value of life.
+     */
+    public void setHighLife(float highLife) {
+        this.highLife = highLife;
+    }
+
+    /**
+     * Get the number of images along the X axis (width).
+     * 
+     * @return the number of images along the X axis (width).
+     * 
+     * @see ParticleEmitter#setImagesX(int) 
+     */
+    public int getImagesX() {
+        return imagesX;
+    }
+
+    /**
+     * Set the number of images along the X axis (width).
+     * 
+     * <p>To determine
+     * how multiple particle images are selected and used, see the
+     * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+     * 
+     * @param imagesX the number of images along the X axis (width).
+     */
+    public void setImagesX(int imagesX) {
+        this.imagesX = imagesX;
+        particleMesh.setImagesXY(this.imagesX, this.imagesY);
+    }
+
+    /**
+     * Get the number of images along the Y axis (height).
+     * 
+     * @return the number of images along the Y axis (height).
+     * 
+     * @see ParticleEmitter#setImagesY(int) 
+     */
+    public int getImagesY() {
+        return imagesY;
+    }
+
+    /**
+     * Set the number of images along the Y axis (height).
+     * 
+     * <p>To determine how multiple particle images are selected and used, see the
+     * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+     * 
+     * @param imagesY the number of images along the Y axis (height).
+     */
+    public void setImagesY(int imagesY) {
+        this.imagesY = imagesY;
+        particleMesh.setImagesXY(this.imagesX, this.imagesY);
+    }
+
+    /**
+     * Get the low value of life.
+     * 
+     * @return the low value of life.
+     * 
+     * @see ParticleEmitter#setLowLife(float) 
+     */
+    public float getLowLife() {
+        return lowLife;
+    }
+
+    /**
+     * Set the low value of life.
+     * 
+     * <p>The particle's lifetime/expiration
+     * is determined by randomly selecting a time between low life and high life.
+     * 
+     * @param lowLife the low value of life.
+     */
+    public void setLowLife(float lowLife) {
+        this.lowLife = lowLife;
+    }
+
+    /**
+     * Get the number of particles to spawn per
+     * second.
+     * 
+     * @return the number of particles to spawn per
+     * second.
+     * 
+     * @see ParticleEmitter#setParticlesPerSec(float) 
+     */
+    public float getParticlesPerSec() {
+        return particlesPerSec;
+    }
+
+    /**
+     * Set the number of particles to spawn per
+     * second.
+     * 
+     * @param particlesPerSec the number of particles to spawn per
+     * second.
+     */
+    public void setParticlesPerSec(float particlesPerSec) {
+        this.particlesPerSec = particlesPerSec;
+    }
+
+    /**
+     * Get the start color of the particles spawned.
+     * 
+     * @return the start color of the particles spawned.
+     * 
+     * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA) 
+     */
+    public ColorRGBA getStartColor() {
+        return startColor;
+    }
+
+    /**
+     * Set the start color of the particles spawned.
+     * 
+     * <p>The particle color at any time is determined by blending the start color
+     * and end color based on the particle's current time of life relative
+     * to its end of life.
+     * 
+     * @param startColor the start color of the particles spawned
+     */
+    public void setStartColor(ColorRGBA startColor) {
+        this.startColor.set(startColor);
+    }
+
+    /**
+     * Get the start color of the particles spawned.
+     * 
+     * @return the start color of the particles spawned.
+     * 
+     * @see ParticleEmitter#setStartSize(float) 
+     */
+    public float getStartSize() {
+        return startSize;
+    }
+
+    /**
+     * Set the start size of the particles spawned.
+     * 
+     * <p>The particle size at any time is determined by blending the start size
+     * and end size based on the particle's current time of life relative
+     * to its end of life.
+     * 
+     * @param startSize the start size of the particles spawned.
+     */
+    public void setStartSize(float startSize) {
+        this.startSize = startSize;
+    }
+
+    /**
+     * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
+     */
+    @Deprecated
+    public Vector3f getInitialVelocity() {
+        return particleInfluencer.getInitialVelocity();
+    }
+
+    /**
+     * @param initialVelocity Set the initial velocity a particle is spawned with,
+     * the initial velocity given in the parameter will be varied according
+     * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
+     * A particle will move toward its velocity unless it is effected by the
+     * gravity.
+     *
+     * @deprecated
+     * This method is deprecated. 
+     * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
+     *
+     * @see ParticleEmitter#setVelocityVariation(float) 
+     * @see ParticleEmitter#setGravity(float)
+     */
+    @Deprecated
+    public void setInitialVelocity(Vector3f initialVelocity) {
+        this.particleInfluencer.setInitialVelocity(initialVelocity);
+    }
+
+    /**
+     * @deprecated
+     * This method is deprecated. 
+     * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
+     * @return the initial velocity variation factor
+     */
+    @Deprecated
+    public float getVelocityVariation() {
+        return particleInfluencer.getVelocityVariation();
+    }
+
+    /**
+     * @param variation Set the variation by which the initial velocity
+     * of the particle is determined. <code>variation</code> should be a value
+     * from 0 to 1, where 0 means particles are to spawn with exactly
+     * the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
+     * and 1 means particles are to spawn with a completely random velocity.
+     * 
+     * @deprecated
+     * This method is deprecated. 
+     * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
+     */
+    @Deprecated
+    public void setVelocityVariation(float variation) {
+        this.particleInfluencer.setVelocityVariation(variation);
+    }
+
+    private Particle emitParticle(Vector3f min, Vector3f max) {
+        int idx = lastUsed + 1;
+        if (idx >= particles.length) {
+            return null;
+        }
+
+        Particle p = particles[idx];
+        if (selectRandomImage) {
+            p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1);
+        }
+
+        p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife);
+        p.life = p.startlife;
+        p.color.set(startColor);
+        p.size = startSize;
+        //shape.getRandomPoint(p.position);
+        particleInfluencer.influenceParticle(p, shape);
+        if (worldSpace) {
+            worldTransform.transformVector(p.position, p.position);
+            worldTransform.getRotation().mult(p.velocity, p.velocity);
+            // TODO: Make scale relevant somehow??
+        }
+        if (randomAngle) {
+            p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI;
+        }
+        if (rotateSpeed != 0) {
+            p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
+        }
+
+        temp.set(p.position).addLocal(p.size, p.size, p.size);
+        max.maxLocal(temp);
+        temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+        min.minLocal(temp);
+
+        ++lastUsed;
+        firstUnUsed = idx + 1;
+        return p;
+    }
+
+    /**
+     * Instantly emits all the particles possible to be emitted. Any particles
+     * which are currently inactive will be spawned immediately.
+     */
+    public void emitAllParticles() {
+        // Force world transform to update
+        this.getWorldTransform();
+
+        TempVars vars = TempVars.get();
+
+        BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+
+        Vector3f min = vars.vect1;
+        Vector3f max = vars.vect2;
+
+        bbox.getMin(min);
+        bbox.getMax(max);
+
+        if (!Vector3f.isValidVector(min)) {
+            min.set(Vector3f.POSITIVE_INFINITY);
+        }
+        if (!Vector3f.isValidVector(max)) {
+            max.set(Vector3f.NEGATIVE_INFINITY);
+        }
+
+        while (emitParticle(min, max) != null);
+
+        bbox.setMinMax(min, max);
+        this.setBoundRefresh();
+
+        vars.release();
+    }
+
+    /**
+     * Instantly kills all active particles, after this method is called, all
+     * particles will be dead and no longer visible.
+     */
+    public void killAllParticles() {
+        for (int i = 0; i < particles.length; ++i) {
+            if (particles[i].life > 0) {
+                this.freeParticle(i);
+            }
+        }
+    }
+    
+    /**
+     * Kills the particle at the given index.
+     * 
+     * @param index The index of the particle to kill
+     * @see #getParticles() 
+     */
+    public void killParticle(int index){
+        freeParticle(index);
+    }
+
+    private void freeParticle(int idx) {
+        Particle p = particles[idx];
+        p.life = 0;
+        p.size = 0f;
+        p.color.set(0, 0, 0, 0);
+        p.imageIndex = 0;
+        p.angle = 0;
+        p.rotateSpeed = 0;
+
+        if (idx == lastUsed) {
+            while (lastUsed >= 0 && particles[lastUsed].life == 0) {
+                lastUsed--;
+            }
+        }
+        if (idx < firstUnUsed) {
+            firstUnUsed = idx;
+        }
+    }
+
+    private void swap(int idx1, int idx2) {
+        Particle p1 = particles[idx1];
+        particles[idx1] = particles[idx2];
+        particles[idx2] = p1;
+    }
+
+    private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){
+        // applying gravity
+        p.velocity.x -= gravity.x * tpf;
+        p.velocity.y -= gravity.y * tpf;
+        p.velocity.z -= gravity.z * tpf;
+        temp.set(p.velocity).multLocal(tpf);
+        p.position.addLocal(temp);
+
+        // affecting color, size and angle
+        float b = (p.startlife - p.life) / p.startlife;
+        p.color.interpolate(startColor, endColor, b);
+        p.size = FastMath.interpolateLinear(b, startSize, endSize);
+        p.angle += p.rotateSpeed * tpf;
+
+        // Computing bounding volume
+        temp.set(p.position).addLocal(p.size, p.size, p.size);
+        max.maxLocal(temp);
+        temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+        min.minLocal(temp);
+
+        if (!selectRandomImage) {
+            p.imageIndex = (int) (b * imagesX * imagesY);
+        }
+    }
+    
+    private void updateParticleState(float tpf) {
+        // Force world transform to update
+        this.getWorldTransform();
+
+        TempVars vars = TempVars.get();
+
+        Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY);
+        Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY);
+
+        for (int i = 0; i < particles.length; ++i) {
+            Particle p = particles[i];
+            if (p.life == 0) { // particle is dead
+//                assert i <= firstUnUsed;
+                continue;
+            }
+
+            p.life -= tpf;
+            if (p.life <= 0) {
+                this.freeParticle(i);
+                continue;
+            }
+
+            updateParticle(p, tpf, min, max);
+
+            if (firstUnUsed < i) {
+                this.swap(firstUnUsed, i);
+                if (i == lastUsed) {
+                    lastUsed = firstUnUsed;
+                }
+                firstUnUsed++;
+            }
+        }
+        
+        // Spawns particles within the tpf timeslot with proper age
+        float interval = 1f / particlesPerSec;
+        tpf += timeDifference;
+        while (tpf > interval){
+            tpf -= interval;
+            Particle p = emitParticle(min, max);
+            if (p != null){
+                p.life -= tpf;
+                if (p.life <= 0){
+                    freeParticle(lastUsed);
+                }else{
+                    updateParticle(p, tpf, min, max);
+                }
+            }
+        }
+        timeDifference = tpf;
+
+        BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+        bbox.setMinMax(min, max);
+        this.setBoundRefresh();
+
+        vars.release();
+    }
+
+    /**
+     * Set to enable or disable the particle emitter
+     * 
+     * <p>When a particle is
+     * disabled, it will be "frozen in time" and not update.
+     * 
+     * @param enabled True to enable the particle emitter
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * Check if a particle emitter is enabled for update.
+     * 
+     * @return True if a particle emitter is enabled for update.
+     * 
+     * @see ParticleEmitter#setEnabled(boolean) 
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Callback from Control.update(), do not use.
+     * @param tpf 
+     */
+    public void updateFromControl(float tpf) {
+        if (enabled) {
+            this.updateParticleState(tpf);
+        }
+    }
+
+    /**
+     * Callback from Control.render(), do not use.
+     * 
+     * @param rm
+     * @param vp 
+     */
+    private void renderFromControl(RenderManager rm, ViewPort vp) {
+        Camera cam = vp.getCamera();
+
+        if (meshType == ParticleMesh.Type.Point) {
+            float C = cam.getProjectionMatrix().m00;
+            C *= cam.getWidth() * 0.5f;
+
+            // send attenuation params
+            this.getMaterial().setFloat("Quadratic", C);
+        }
+
+        Matrix3f inverseRotation = Matrix3f.IDENTITY;
+        TempVars vars = null;
+        if (!worldSpace) {
+            vars = TempVars.get();
+
+            inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
+        }
+        particleMesh.updateParticleData(particles, cam, inverseRotation);
+        if (!worldSpace) {
+            vars.release();
+        }
+    }
+
+    public void preload(RenderManager rm, ViewPort vp) {
+        this.updateParticleState(0);
+        particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(shape, "shape", DEFAULT_SHAPE);
+        oc.write(meshType, "meshType", ParticleMesh.Type.Triangle);
+        oc.write(enabled, "enabled", true);
+        oc.write(particles.length, "numParticles", 0);
+        oc.write(particlesPerSec, "particlesPerSec", 0);
+        oc.write(lowLife, "lowLife", 0);
+        oc.write(highLife, "highLife", 0);
+        oc.write(gravity, "gravity", null);
+        oc.write(imagesX, "imagesX", 1);
+        oc.write(imagesY, "imagesY", 1);
+
+        oc.write(startColor, "startColor", null);
+        oc.write(endColor, "endColor", null);
+        oc.write(startSize, "startSize", 0);
+        oc.write(endSize, "endSize", 0);
+        oc.write(worldSpace, "worldSpace", false);
+        oc.write(facingVelocity, "facingVelocity", false);
+        oc.write(faceNormal, "faceNormal", new Vector3f(Vector3f.NAN));
+        oc.write(selectRandomImage, "selectRandomImage", false);
+        oc.write(randomAngle, "randomAngle", false);
+        oc.write(rotateSpeed, "rotateSpeed", 0);
+
+        oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE);
+
+        if (shape == DEFAULT_SHAPE) {
+            // Prevent reference to static
+            shape = shape.deepClone();
+        }
+
+        meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle);
+        int numParticles = ic.readInt("numParticles", 0);
+
+
+        enabled = ic.readBoolean("enabled", true);
+        particlesPerSec = ic.readFloat("particlesPerSec", 0);
+        lowLife = ic.readFloat("lowLife", 0);
+        highLife = ic.readFloat("highLife", 0);
+        gravity = (Vector3f) ic.readSavable("gravity", null);
+        imagesX = ic.readInt("imagesX", 1);
+        imagesY = ic.readInt("imagesY", 1);
+
+        startColor = (ColorRGBA) ic.readSavable("startColor", null);
+        endColor = (ColorRGBA) ic.readSavable("endColor", null);
+        startSize = ic.readFloat("startSize", 0);
+        endSize = ic.readFloat("endSize", 0);
+        worldSpace = ic.readBoolean("worldSpace", false);
+        this.setIgnoreTransform(worldSpace);
+        facingVelocity = ic.readBoolean("facingVelocity", false);
+        faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN));
+        selectRandomImage = ic.readBoolean("selectRandomImage", false);
+        randomAngle = ic.readBoolean("randomAngle", false);
+        rotateSpeed = ic.readFloat("rotateSpeed", 0);
+
+        switch (meshType) {
+            case Point:
+                particleMesh = new ParticlePointMesh();
+                this.setMesh(particleMesh);
+                break;
+            case Triangle:
+                particleMesh = new ParticleTriMesh();
+                this.setMesh(particleMesh);
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized particle type: " + meshType);
+        }
+        this.setNumParticles(numParticles);
+//        particleMesh.initParticleData(this, particles.length);
+//        particleMesh.setImagesXY(imagesX, imagesY);
+
+        particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
+        if (particleInfluencer == DEFAULT_INFLUENCER) {
+            particleInfluencer = particleInfluencer.clone();
+        }
+
+        if (im.getFormatVersion() == 0) {
+            // compatibility before the control inside particle emitter
+            // was changed:
+            // find it in the controls and take it out, then add the proper one in
+            for (int i = 0; i < controls.size(); i++) {
+                Object obj = controls.get(i);
+                if (obj instanceof ParticleEmitter) {
+                    controls.remove(i);
+                    // now add the proper one in
+                    controls.add(new ParticleEmitterControl(this));
+                    break;
+                }
+            }
+
+            // compatability before gravity was not a vector but a float
+            if (gravity == null) {
+                gravity = new Vector3f();
+                gravity.y = ic.readFloat("gravity", 0);
+            }
+        } else {
+            // since the parentEmitter is not loaded, it must be 
+            // loaded separately
+            control = getControl(ParticleEmitterControl.class);
+            control.parentEmitter = this;
+
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleMesh.java b/engine/src/core/com/jme3/effect/ParticleMesh.java
new file mode 100644
index 0000000..adace45
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleMesh.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Mesh;
+
+/**
+ * The <code>ParticleMesh</code> is the underlying visual implementation of a 
+ * {@link ParticleEmitter particle emitter}.
+ * 
+ * @author Kirill Vainer
+ */
+public abstract class ParticleMesh extends Mesh {
+
+    /**
+     * Type of particle mesh
+     */
+    public enum Type {
+        /**
+         * The particle mesh is composed of points. Each particle is a point.
+         * This can be used in conjuction with {@link RenderState#setPointSprite(boolean) point sprites}
+         * to render particles the usual way.
+         */
+        Point,
+        
+        /**
+         * The particle mesh is composed of triangles. Each particle is 
+         * two triangles making a single quad.
+         */
+        Triangle;
+    }
+
+    /**
+     * Initialize mesh data.
+     * 
+     * @param emitter The emitter which will use this <code>ParticleMesh</code>.
+     * @param numParticles The maxmimum number of particles to simulate
+     */
+    public abstract void initParticleData(ParticleEmitter emitter, int numParticles);
+    
+    /**
+     * Set the images on the X and Y coordinates
+     * @param imagesX Images on the X coordinate
+     * @param imagesY Images on the Y coordinate
+     */
+    public abstract void setImagesXY(int imagesX, int imagesY);
+    
+    /**
+     * Update the particle visual data. Typically called every frame.
+     */
+    public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation);
+
+}
diff --git a/engine/src/core/com/jme3/effect/ParticlePointMesh.java b/engine/src/core/com/jme3/effect/ParticlePointMesh.java
new file mode 100644
index 0000000..28db3e6
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticlePointMesh.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.math.Matrix3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+public class ParticlePointMesh extends ParticleMesh {
+
+    private ParticleEmitter emitter;
+
+    private int imagesX = 1;
+    private int imagesY = 1;
+
+    @Override
+    public void setImagesXY(int imagesX, int imagesY) {
+        this.imagesX = imagesX;
+        this.imagesY = imagesY;
+    }
+
+    @Override
+    public void initParticleData(ParticleEmitter emitter, int numParticles) {
+        setMode(Mode.Points);
+
+        this.emitter = emitter;
+
+        // set positions
+        FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles);
+        VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
+        pvb.setupData(Usage.Stream, 3, Format.Float, pb);
+         
+        //if the buffer is already set only update the data
+        VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
+        if (buf != null) {
+            buf.updateData(pb);
+        } else {
+            setBuffer(pvb);
+        }
+
+        // set colors
+        ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4);
+        VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
+        cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
+        cvb.setNormalized(true);
+        
+        buf = getBuffer(VertexBuffer.Type.Color);
+        if (buf != null) {
+            buf.updateData(cb);
+        } else {
+            setBuffer(cvb);
+        }
+
+        // set sizes
+        FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles);
+        VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
+        svb.setupData(Usage.Stream, 1, Format.Float, sb);
+                
+        buf = getBuffer(VertexBuffer.Type.Size);
+        if (buf != null) {
+            buf.updateData(sb);
+        } else {
+            setBuffer(svb);
+        }
+
+        // set UV-scale
+        FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4);
+        VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
+        tvb.setupData(Usage.Stream, 4, Format.Float, tb);
+        
+        buf = getBuffer(VertexBuffer.Type.TexCoord);
+        if (buf != null) {
+            buf.updateData(tb);
+        } else {
+            setBuffer(tvb);
+        }
+    }
+
+    @Override
+    public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
+        VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer positions = (FloatBuffer) pvb.getData();
+
+        VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
+        ByteBuffer colors = (ByteBuffer) cvb.getData();
+
+        VertexBuffer svb = getBuffer(VertexBuffer.Type.Size);
+        FloatBuffer sizes = (FloatBuffer) svb.getData();
+
+        VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
+        FloatBuffer texcoords = (FloatBuffer) tvb.getData();
+
+        float sizeScale = emitter.getWorldScale().x;
+
+        // update data in vertex buffers
+        positions.rewind();
+        colors.rewind();
+        sizes.rewind();
+        texcoords.rewind();
+        for (int i = 0; i < particles.length; i++){
+            Particle p = particles[i];
+            
+            positions.put(p.position.x)
+                     .put(p.position.y)
+                     .put(p.position.z);
+
+            sizes.put(p.size * sizeScale);
+            colors.putInt(p.color.asIntABGR());
+
+            int imgX = p.imageIndex % imagesX;
+            int imgY = (p.imageIndex - imgX) / imagesY;
+
+            float startX = ((float) imgX) / imagesX;
+            float startY = ((float) imgY) / imagesY;
+            float endX   = startX + (1f / imagesX);
+            float endY   = startY + (1f / imagesY);
+
+            texcoords.put(startX).put(startY).put(endX).put(endY);
+        }
+        positions.flip();
+        colors.flip();
+        sizes.flip();
+        texcoords.flip();
+
+        // force renderer to re-send data to GPU
+        pvb.updateData(positions);
+        cvb.updateData(colors);
+        svb.updateData(sizes);
+        tvb.updateData(texcoords);
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleTriMesh.java b/engine/src/core/com/jme3/effect/ParticleTriMesh.java
new file mode 100644
index 0000000..8d27838
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleTriMesh.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.SortUtil;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class ParticleTriMesh extends ParticleMesh {
+
+    private int imagesX = 1;
+    private int imagesY = 1;
+    private boolean uniqueTexCoords = false;
+    private ParticleComparator comparator = new ParticleComparator();
+    private ParticleEmitter emitter;
+    private Particle[] particlesCopy;
+
+    @Override
+    public void initParticleData(ParticleEmitter emitter, int numParticles) {
+        setMode(Mode.Triangles);
+
+        this.emitter = emitter;
+
+        particlesCopy = new Particle[numParticles];
+
+        // set positions
+        FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles * 4);
+        VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
+        pvb.setupData(Usage.Stream, 3, Format.Float, pb);
+        
+        //if the buffer is already set only update the data
+        VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
+        if (buf != null) {
+            buf.updateData(pb);
+        } else {
+            setBuffer(pvb);
+        }
+        
+        // set colors
+        ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4 * 4);
+        VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
+        cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
+        cvb.setNormalized(true);
+        
+        buf = getBuffer(VertexBuffer.Type.Color);
+        if (buf != null) {
+            buf.updateData(cb);
+        } else {
+            setBuffer(cvb);
+        }
+
+        // set texcoords
+        VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
+        FloatBuffer tb = BufferUtils.createVector2Buffer(numParticles * 4);
+        
+        uniqueTexCoords = false;
+        for (int i = 0; i < numParticles; i++){
+            tb.put(0f).put(1f);
+            tb.put(1f).put(1f);
+            tb.put(0f).put(0f);
+            tb.put(1f).put(0f);
+        }
+        tb.flip();
+        tvb.setupData(Usage.Static, 2, Format.Float, tb);
+        
+        buf = getBuffer(VertexBuffer.Type.TexCoord);
+        if (buf != null) {
+            buf.updateData(tb);
+        } else {
+            setBuffer(tvb);
+        }
+
+        // set indices
+        ShortBuffer ib = BufferUtils.createShortBuffer(numParticles * 6);
+        for (int i = 0; i < numParticles; i++){
+            int startIdx = (i * 4);
+
+            // triangle 1
+            ib.put((short)(startIdx + 1))
+              .put((short)(startIdx + 0))
+              .put((short)(startIdx + 2));
+
+            // triangle 2
+            ib.put((short)(startIdx + 1))
+              .put((short)(startIdx + 2))
+              .put((short)(startIdx + 3));
+        }
+        ib.flip();
+        
+        VertexBuffer ivb = new VertexBuffer(VertexBuffer.Type.Index);
+        ivb.setupData(Usage.Static, 3, Format.UnsignedShort, ib);
+        
+        buf = getBuffer(VertexBuffer.Type.Index);
+        if (buf != null) {
+            buf.updateData(ib);
+        } else {
+            setBuffer(ivb);
+        }
+        
+    }
+    
+    @Override
+    public void setImagesXY(int imagesX, int imagesY) {
+        this.imagesX = imagesX;
+        this.imagesY = imagesY;
+        if (imagesX != 1 || imagesY != 1){
+            uniqueTexCoords = true;
+            getBuffer(VertexBuffer.Type.TexCoord).setUsage(Usage.Stream);
+        }
+    }
+
+    @Override
+    public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
+        System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
+        comparator.setCamera(cam);
+//        Arrays.sort(particlesCopy, comparator);
+//        SortUtil.qsort(particlesCopy, comparator);
+        SortUtil.msort(particles, particlesCopy, comparator);
+        particles = particlesCopy;
+
+        VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer positions = (FloatBuffer) pvb.getData();
+
+        VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
+        ByteBuffer colors = (ByteBuffer) cvb.getData();
+
+        VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
+        FloatBuffer texcoords = (FloatBuffer) tvb.getData();
+
+        Vector3f camUp   = cam.getUp();
+        Vector3f camLeft = cam.getLeft();
+        Vector3f camDir  = cam.getDirection();
+
+        inverseRotation.multLocal(camUp);
+        inverseRotation.multLocal(camLeft);
+        inverseRotation.multLocal(camDir);
+
+        boolean facingVelocity = emitter.isFacingVelocity();
+
+        Vector3f up = new Vector3f(),
+                 left = new Vector3f();
+
+        if (!facingVelocity){
+            up.set(camUp);
+            left.set(camLeft);
+        }
+
+        // update data in vertex buffers
+        positions.clear();
+        colors.clear();
+        texcoords.clear();
+        Vector3f faceNormal = emitter.getFaceNormal();
+        
+        for (int i = 0; i < particles.length; i++){
+            Particle p = particles[i];
+            boolean dead = p.life == 0;
+            if (dead){
+                positions.put(0).put(0).put(0);
+                positions.put(0).put(0).put(0);
+                positions.put(0).put(0).put(0);
+                positions.put(0).put(0).put(0);
+                continue;
+            }
+            
+            if (facingVelocity){
+                left.set(p.velocity).normalizeLocal();
+                camDir.cross(left, up);
+                up.multLocal(p.size);
+                left.multLocal(p.size);
+            }else if (faceNormal != null){
+                up.set(faceNormal).crossLocal(Vector3f.UNIT_X);
+                faceNormal.cross(up, left);
+                up.multLocal(p.size);
+                left.multLocal(p.size);
+            }else if (p.angle != 0){
+                float cos = FastMath.cos(p.angle) * p.size;
+                float sin = FastMath.sin(p.angle) * p.size;
+
+                left.x = camLeft.x * cos + camUp.x * sin;
+                left.y = camLeft.y * cos + camUp.y * sin;
+                left.z = camLeft.z * cos + camUp.z * sin;
+
+                up.x = camLeft.x * -sin + camUp.x * cos;
+                up.y = camLeft.y * -sin + camUp.y * cos;
+                up.z = camLeft.z * -sin + camUp.z * cos;
+            }else{
+                up.set(camUp);
+                left.set(camLeft);
+                up.multLocal(p.size);
+                left.multLocal(p.size);
+            }
+
+            positions.put(p.position.x + left.x + up.x)
+                     .put(p.position.y + left.y + up.y)
+                     .put(p.position.z + left.z + up.z);
+
+            positions.put(p.position.x - left.x + up.x)
+                     .put(p.position.y - left.y + up.y)
+                     .put(p.position.z - left.z + up.z);
+
+            positions.put(p.position.x + left.x - up.x)
+                     .put(p.position.y + left.y - up.y)
+                     .put(p.position.z + left.z - up.z);
+
+            positions.put(p.position.x - left.x - up.x)
+                     .put(p.position.y - left.y - up.y)
+                     .put(p.position.z - left.z - up.z);
+
+            if (uniqueTexCoords){
+                int imgX = p.imageIndex % imagesX;
+                int imgY = (p.imageIndex - imgX) / imagesY;
+
+                float startX = ((float) imgX) / imagesX;
+                float startY = ((float) imgY) / imagesY;
+                float endX   = startX + (1f / imagesX);
+                float endY   = startY + (1f / imagesY);
+
+                texcoords.put(startX).put(endY);
+                texcoords.put(endX).put(endY);
+                texcoords.put(startX).put(startY);
+                texcoords.put(endX).put(startY);
+            }
+
+            int abgr = p.color.asIntABGR();
+            colors.putInt(abgr);
+            colors.putInt(abgr);
+            colors.putInt(abgr);
+            colors.putInt(abgr);
+        }
+
+        positions.clear();
+        colors.clear();
+        if (!uniqueTexCoords)
+            texcoords.clear();
+        else{
+            texcoords.clear();
+            tvb.updateData(texcoords);
+        }
+
+        // force renderer to re-send data to GPU
+        pvb.updateData(positions);
+        cvb.updateData(colors);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java
new file mode 100644
index 0000000..80f52d9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java
@@ -0,0 +1,92 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * This emitter influences the particles so that they move all in the same direction.
+ * The direction may vary a little if the velocity variation is non zero.
+ * This influencer is default for the particle emitter.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class DefaultParticleInfluencer implements ParticleInfluencer {
+
+    /** Temporary variable used to help with calculations. */
+    protected transient Vector3f temp = new Vector3f();
+    /** The initial velocity of the particles. */
+    protected Vector3f startVelocity = new Vector3f();
+    /** The velocity's variation of the particles. */
+    protected float velocityVariation = 0.2f;
+
+    @Override
+    public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+        emitterShape.getRandomPoint(particle.position);
+        this.applyVelocityVariation(particle);
+    }
+
+    /**
+     * This method applies the variation to the particle with already set velocity.
+     * @param particle
+     *        the particle to be affected
+     */
+    protected void applyVelocityVariation(Particle particle) {
+    	particle.velocity.set(startVelocity);
+        temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat());
+        temp.multLocal(2f);
+        temp.subtractLocal(1f, 1f, 1f);
+        temp.multLocal(startVelocity.length());
+        particle.velocity.interpolate(temp, velocityVariation);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(startVelocity, "startVelocity", Vector3f.ZERO);
+        oc.write(velocityVariation, "variation", 0.2f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        startVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone());
+        velocityVariation = ic.readFloat("variation", 0.2f);
+    }
+
+    @Override
+    public ParticleInfluencer clone() {
+        try {
+            DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone();
+            clone.startVelocity = startVelocity.clone();
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void setInitialVelocity(Vector3f initialVelocity) {
+        this.startVelocity.set(initialVelocity);
+    }
+
+    @Override
+    public Vector3f getInitialVelocity() {
+        return startVelocity;
+    }
+
+    @Override
+    public void setVelocityVariation(float variation) {
+        this.velocityVariation = variation;
+    }
+
+    @Override
+    public float getVelocityVariation() {
+        return velocityVariation;
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java
new file mode 100644
index 0000000..013a5db
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java
@@ -0,0 +1,55 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * This influencer does not influence particle at all.
+ * It makes particles not to move.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmptyParticleInfluencer implements ParticleInfluencer {
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+    }
+
+    @Override
+    public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+    }
+
+    @Override
+    public void setInitialVelocity(Vector3f initialVelocity) {
+    }
+
+    @Override
+    public Vector3f getInitialVelocity() {
+        return null;
+    }
+
+    @Override
+    public void setVelocityVariation(float variation) {
+    }
+
+    @Override
+    public float getVelocityVariation() {
+        return 0;
+    }
+
+    @Override
+    public ParticleInfluencer clone() {
+        try {
+            return (ParticleInfluencer) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
new file mode 100644
index 0000000..a2701be
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
@@ -0,0 +1,142 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import java.io.IOException;
+
+/**
+ * This influencer calculates initial velocity with the use of the emitter's shape.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class NewtonianParticleInfluencer extends DefaultParticleInfluencer {
+
+    /** Normal to emitter's shape factor. */
+    protected float normalVelocity;
+    /** Emitter's surface tangent factor. */
+    protected float surfaceTangentFactor;
+    /** Emitters tangent rotation factor. */
+    protected float surfaceTangentRotation;
+
+    /**
+     * Constructor. Sets velocity variation to 0.0f.
+     */
+    public NewtonianParticleInfluencer() {
+        this.velocityVariation = 0.0f;
+    }
+
+    @Override
+    public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+        emitterShape.getRandomPointAndNormal(particle.position, particle.velocity);
+        // influencing the particle's velocity
+        if (surfaceTangentFactor == 0.0f) {
+            particle.velocity.multLocal(normalVelocity);
+        } else {
+            // calculating surface tangent (velocity contains the 'normal' value)
+            temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor);
+            if (surfaceTangentRotation != 0.0f) {// rotating the tangent
+                Matrix3f m = new Matrix3f();
+                m.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity);
+                temp = m.multLocal(temp);
+            }
+            // applying normal factor (this must be done first)
+            particle.velocity.multLocal(normalVelocity);
+            // adding tangent vector
+            particle.velocity.addLocal(temp);
+        }
+        if (velocityVariation != 0.0f) {
+            this.applyVelocityVariation(particle);
+        }
+    }
+
+    /**
+     * This method returns the normal velocity factor.
+     * @return the normal velocity factor
+     */
+    public float getNormalVelocity() {
+        return normalVelocity;
+    }
+
+    /**
+     * This method sets the normal velocity factor.
+     * @param normalVelocity
+     *        the normal velocity factor
+     */
+    public void setNormalVelocity(float normalVelocity) {
+        this.normalVelocity = normalVelocity;
+    }
+
+    /**
+     * This method sets the surface tangent factor.
+     * @param surfaceTangentFactor
+     *        the surface tangent factor
+     */
+    public void setSurfaceTangentFactor(float surfaceTangentFactor) {
+        this.surfaceTangentFactor = surfaceTangentFactor;
+    }
+
+    /**
+     * This method returns the surface tangent factor.
+     * @return the surface tangent factor
+     */
+    public float getSurfaceTangentFactor() {
+        return surfaceTangentFactor;
+    }
+
+    /**
+     * This method sets the surface tangent rotation factor.
+     * @param surfaceTangentRotation
+     *        the surface tangent rotation factor
+     */
+    public void setSurfaceTangentRotation(float surfaceTangentRotation) {
+        this.surfaceTangentRotation = surfaceTangentRotation;
+    }
+
+    /**
+     * This method returns the surface tangent rotation factor.
+     * @return the surface tangent rotation factor
+     */
+    public float getSurfaceTangentRotation() {
+        return surfaceTangentRotation;
+    }
+
+    @Override
+    protected void applyVelocityVariation(Particle particle) {
+        temp.set(FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation);
+        particle.velocity.addLocal(temp);
+    }
+
+    @Override
+    public ParticleInfluencer clone() {
+        NewtonianParticleInfluencer result = new NewtonianParticleInfluencer();
+        result.normalVelocity = normalVelocity;
+        result.startVelocity = startVelocity;
+        result.velocityVariation = velocityVariation;
+        result.surfaceTangentFactor = surfaceTangentFactor;
+        result.surfaceTangentRotation = surfaceTangentRotation;
+        return result;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(normalVelocity, "normalVelocity", 0.0f);
+        oc.write(surfaceTangentFactor, "surfaceTangentFactor", 0.0f);
+        oc.write(surfaceTangentRotation, "surfaceTangentRotation", 0.0f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        normalVelocity = ic.readFloat("normalVelocity", 0.0f);
+        surfaceTangentFactor = ic.readFloat("surfaceTangentFactor", 0.0f);
+        surfaceTangentRotation = ic.readFloat("surfaceTangentRotation", 0.0f);
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java
new file mode 100644
index 0000000..56c8cf9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java
@@ -0,0 +1,61 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ * An interface that defines the methods to affect initial velocity of the particles.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public interface ParticleInfluencer extends Savable, Cloneable {
+
+    /**
+     * This method influences the particle.
+     * @param particle
+     *        particle to be influenced
+     * @param emitterShape
+     *        the shape of it emitter
+     */
+    void influenceParticle(Particle particle, EmitterShape emitterShape);
+
+    /**
+     * This method clones the influencer instance.
+     * @return cloned instance
+     */
+    public ParticleInfluencer clone();
+
+    /**
+     * @param initialVelocity
+     *        Set the initial velocity a particle is spawned with,
+     *        the initial velocity given in the parameter will be varied according
+     *        to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
+     *        A particle will move toward its velocity unless it is effected by the
+     *        gravity.
+     */
+    void setInitialVelocity(Vector3f initialVelocity);
+
+    /**
+     * This method returns the initial velocity.
+     * @return the initial velocity
+     */
+    Vector3f getInitialVelocity();
+
+    /**
+     * @param variation
+     *        Set the variation by which the initial velocity
+     *        of the particle is determined. <code>variation</code> should be a value
+     *        from 0 to 1, where 0 means particles are to spawn with exactly
+     *        the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
+     *        and 1 means particles are to spawn with a completely random velocity.
+     */
+    void setVelocityVariation(float variation);
+
+    /**
+     * This method returns the velocity variation.
+     * @return the velocity variation
+     */
+    float getVelocityVariation();
+}
diff --git a/engine/src/core/com/jme3/effect/package.html b/engine/src/core/com/jme3/effect/package.html
new file mode 100644
index 0000000..dd16da7
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.effect</code> package allows particle emitter effects to be
+used with a jME3 application. <br/>
+<p>
+    The <code>ParticleEmitter</code> class is the primary class used to create
+    particle emitter effects. See the <code>jme3test.effect</code> package
+    for examples on how to use <code>ParticleEmitter</code>s.
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
new file mode 100644
index 0000000..9838dd2
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009-2010 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.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterBoxShape implements EmitterShape {
+
+    private Vector3f min, len;
+
+    public EmitterBoxShape() {
+    }
+
+    public EmitterBoxShape(Vector3f min, Vector3f max) {
+        if (min == null || max == null) {
+            throw new NullPointerException();
+        }
+
+        this.min = min;
+        this.len = new Vector3f();
+        this.len.set(max).subtractLocal(min);
+    }
+
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        store.x = min.x + len.x * FastMath.nextRandomFloat();
+        store.y = min.y + len.y * FastMath.nextRandomFloat();
+        store.z = min.z + len.z * FastMath.nextRandomFloat();
+    }
+
+    /**
+     * This method fills the point with data.
+     * It does not fill the normal.
+     * @param store the variable to store the point data
+     * @param normal not used in this class
+     */
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        this.getRandomPoint(store);
+    }
+
+    @Override
+    public EmitterShape deepClone() {
+        try {
+            EmitterBoxShape clone = (EmitterBoxShape) super.clone();
+            clone.min = min.clone();
+            clone.len = len.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    public Vector3f getMin() {
+        return min;
+    }
+
+    public void setMin(Vector3f min) {
+        this.min = min;
+    }
+
+    public Vector3f getLen() {
+        return len;
+    }
+
+    public void setLen(Vector3f len) {
+        this.len = len;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(min, "min", null);
+        oc.write(len, "length", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        min = (Vector3f) ic.readSavable("min", null);
+        len = (Vector3f) ic.readSavable("length", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java
new file mode 100644
index 0000000..1c5d687
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java
@@ -0,0 +1,63 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.util.List;
+
+/**
+ * This emiter shape emits the particles from the given shape's interior constrained by its convex hull
+ * (a geometry that tightly wraps the mesh). So in case of multiple meshes some vertices may appear
+ * in a space between them.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshConvexHullShape extends EmitterMeshFaceShape {
+
+    /**
+     * Empty constructor. Sets nothing.
+     */
+    public EmitterMeshConvexHullShape() {
+    }
+
+    /**
+     * Constructor. It stores a copy of vertex list of all meshes.
+     * @param meshes
+     *        a list of meshes that will form the emitter's shape
+     */
+    public EmitterMeshConvexHullShape(List<Mesh> meshes) {
+        super(meshes);
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected point inside a convex hull
+     * of randomly selected mesh.
+     * @param store
+     *        the variable to store with coordinates of randomly selected selected point inside a convex hull
+     *        of randomly selected mesh
+     */
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        super.getRandomPoint(store);
+        // now move the point from the meshe's face towards the center of the mesh
+        // the center is in (0, 0, 0) in the local coordinates
+        store.multLocal(FastMath.nextRandomFloat());
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected point inside a convex hull
+     * of randomly selected mesh.
+     * The normal param is not used.
+     * @param store
+     *        the variable to store with coordinates of randomly selected selected point inside a convex hull
+     *        of randomly selected mesh
+     * @param normal
+     *        not used in this class
+     */
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        super.getRandomPointAndNormal(store, normal);
+        // now move the point from the meshe's face towards the center of the mesh
+        // the center is in (0, 0, 0) in the local coordinates
+        store.multLocal(FastMath.nextRandomFloat());
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java
new file mode 100644
index 0000000..023ca5b
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java
@@ -0,0 +1,97 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This emiter shape emits the particles from the given shape's faces.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshFaceShape extends EmitterMeshVertexShape {
+
+    /**
+     * Empty constructor. Sets nothing.
+     */
+    public EmitterMeshFaceShape() {
+    }
+
+    /**
+     * Constructor. It stores a copy of vertex list of all meshes.
+     * @param meshes
+     *        a list of meshes that will form the emitter's shape
+     */
+    public EmitterMeshFaceShape(List<Mesh> meshes) {
+        super(meshes);
+    }
+
+    @Override
+    public void setMeshes(List<Mesh> meshes) {
+        this.vertices = new ArrayList<List<Vector3f>>(meshes.size());
+        this.normals = new ArrayList<List<Vector3f>>(meshes.size());
+        for (Mesh mesh : meshes) {
+            Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position));
+            int[] indices = new int[3];
+            List<Vector3f> vertices = new ArrayList<Vector3f>(mesh.getTriangleCount() * 3);
+            List<Vector3f> normals = new ArrayList<Vector3f>(mesh.getTriangleCount());
+            for (int i = 0; i < mesh.getTriangleCount(); ++i) {
+                mesh.getTriangle(i, indices);
+                vertices.add(vertexTable[indices[0]]);
+                vertices.add(vertexTable[indices[1]]);
+                vertices.add(vertexTable[indices[2]]);
+                normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]]));
+            }
+            this.vertices.add(vertices);
+            this.normals.add(normals);
+        }
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected point on a random face.
+     * @param store
+     *        the variable to store with coordinates of randomly selected selected point on a random face
+     */
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+        // the index of the first vertex of a face (must be dividable by 3)
+        int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3;
+        // put the point somewhere between the first and the second vertex of a face
+        float moveFactor = FastMath.nextRandomFloat();
+        store.set(Vector3f.ZERO);
+        store.addLocal(vertices.get(meshIndex).get(vertIndex));
+        store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
+        // move the result towards the last face vertex
+        moveFactor = FastMath.nextRandomFloat();
+        store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected point on a random face.
+     * The normal param is filled with selected face's normal.
+     * @param store
+     *        the variable to store with coordinates of randomly selected selected point on a random face
+     * @param normal
+     *        filled with selected face's normal
+     */
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+        // the index of the first vertex of a face (must be dividable by 3)
+        int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1);
+        int vertIndex = faceIndex * 3;
+        // put the point somewhere between the first and the second vertex of a face
+        float moveFactor = FastMath.nextRandomFloat();
+        store.set(Vector3f.ZERO);
+        store.addLocal(vertices.get(meshIndex).get(vertIndex));
+        store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
+        // move the result towards the last face vertex
+        moveFactor = FastMath.nextRandomFloat();
+        store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);
+        normal.set(normals.get(meshIndex).get(faceIndex));
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
new file mode 100644
index 0000000..28ee8b4
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
@@ -0,0 +1,158 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This emiter shape emits the particles from the given shape's vertices
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshVertexShape implements EmitterShape {
+
+    protected List<List<Vector3f>> vertices;
+    protected List<List<Vector3f>> normals;
+
+    /**
+     * Empty constructor. Sets nothing.
+     */
+    public EmitterMeshVertexShape() {
+    }
+
+    /**
+     * Constructor. It stores a copy of vertex list of all meshes.
+     * @param meshes
+     *        a list of meshes that will form the emitter's shape
+     */
+    public EmitterMeshVertexShape(List<Mesh> meshes) {
+        this.setMeshes(meshes);
+    }
+
+    /**
+     * This method sets the meshes that will form the emiter's shape.
+     * @param meshes
+     *        a list of meshes that will form the emitter's shape
+     */
+    public void setMeshes(List<Mesh> meshes) {
+        Map<Vector3f, Vector3f> vertToNormalMap = new HashMap<Vector3f, Vector3f>();
+
+        this.vertices = new ArrayList<List<Vector3f>>(meshes.size());
+        this.normals = new ArrayList<List<Vector3f>>(meshes.size());
+        for (Mesh mesh : meshes) {
+            // fetching the data
+            float[] vertexTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position));
+            float[] normalTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal));
+
+            // unifying normals
+            for (int i = 0; i < vertexTable.length; i += 3) {// the tables should have the same size and be dividable by 3
+                Vector3f vert = new Vector3f(vertexTable[i], vertexTable[i + 1], vertexTable[i + 2]);
+                Vector3f norm = vertToNormalMap.get(vert);
+                if (norm == null) {
+                    norm = new Vector3f(normalTable[i], normalTable[i + 1], normalTable[i + 2]);
+                    vertToNormalMap.put(vert, norm);
+                } else {
+                    norm.addLocal(normalTable[i], normalTable[i + 1], normalTable[i + 2]);
+                }
+            }
+
+            // adding data to vertices and normals
+            List<Vector3f> vertices = new ArrayList<Vector3f>(vertToNormalMap.size());
+            List<Vector3f> normals = new ArrayList<Vector3f>(vertToNormalMap.size());
+            for (Entry<Vector3f, Vector3f> entry : vertToNormalMap.entrySet()) {
+                vertices.add(entry.getKey());
+                normals.add(entry.getValue().normalizeLocal());
+            }
+            this.vertices.add(vertices);
+            this.normals.add(normals);
+        }
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected mesh vertex.
+     * @param store
+     *        the variable to store with coordinates of randomly selected mesh vertex
+     */
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+        int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1);
+        store.set(vertices.get(meshIndex).get(vertIndex));
+    }
+
+    /**
+     * This method fills the point with coordinates of randomly selected mesh vertex.
+     * The normal param is filled with selected vertex's normal.
+     * @param store
+     *        the variable to store with coordinates of randomly selected mesh vertex
+     * @param normal
+     *        filled with selected vertex's normal
+     */
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+        int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1);
+        store.set(vertices.get(meshIndex).get(vertIndex));
+        normal.set(normals.get(meshIndex).get(vertIndex));
+    }
+
+    @Override
+    public EmitterShape deepClone() {
+        try {
+            EmitterMeshVertexShape clone = (EmitterMeshVertexShape) super.clone();
+            if (this.vertices != null) {
+                clone.vertices = new ArrayList<List<Vector3f>>(vertices.size());
+                for (List<Vector3f> list : vertices) {
+                    List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size());
+                    for (Vector3f vector : list) {
+                        vectorList.add(vector.clone());
+                    }
+                    clone.vertices.add(vectorList);
+                }
+            }
+            if (this.normals != null) {
+                clone.normals = new ArrayList<List<Vector3f>>(normals.size());
+                for (List<Vector3f> list : normals) {
+                    List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size());
+                    for (Vector3f vector : list) {
+                        vectorList.add(vector.clone());
+                    }
+                    clone.normals.add(vectorList);
+                }
+            }
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.writeSavableArrayList((ArrayList<List<Vector3f>>) vertices, "vertices", null);
+        oc.writeSavableArrayList((ArrayList<List<Vector3f>>) normals, "normals", null);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        this.vertices = ic.readSavableArrayList("vertices", null);
+        
+        List<List<Vector3f>> tmpNormals = ic.readSavableArrayList("normals", null);
+        if (tmpNormals != null){
+            this.normals = tmpNormals;
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java
new file mode 100644
index 0000000..f8ba70e
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 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.effect.shapes;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterPointShape implements EmitterShape {
+
+    private Vector3f point;
+
+    public EmitterPointShape() {
+    }
+
+    public EmitterPointShape(Vector3f point) {
+        this.point = point;
+    }
+
+    @Override
+    public EmitterShape deepClone() {
+        try {
+            EmitterPointShape clone = (EmitterPointShape) super.clone();
+            clone.point = point.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        store.set(point);
+    }
+
+    /**
+     * This method fills the point with data.
+     * It does not fill the normal.
+     * @param store the variable to store the point data
+     * @param normal not used in this class
+     */
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        store.set(point);
+    }
+
+    public Vector3f getPoint() {
+        return point;
+    }
+
+    public void setPoint(Vector3f point) {
+        this.point = point;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(point, "point", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        this.point = (Vector3f) im.getCapsule(this).readSavable("point", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java
new file mode 100644
index 0000000..c23c19d
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2010 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.effect.shapes;
+
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ * This interface declares methods used by all shapes that represent particle emitters.
+ * @author Kirill
+ */
+public interface EmitterShape extends Savable, Cloneable {
+
+    /**
+     * This method fills in the initial position of the particle.
+     * @param store
+     *        store variable for initial position
+     */
+    public void getRandomPoint(Vector3f store);
+
+    /**
+     * This method fills in the initial position of the particle and its normal vector.
+     * @param store
+     *        store variable for initial position
+     * @param normal
+     *        store variable for initial normal
+     */
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal);
+
+    /**
+     * This method creates a deep clone of the current instance of the emitter shape.
+     * @return deep clone of the current instance of the emitter shape
+     */
+    public EmitterShape deepClone();
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
new file mode 100644
index 0000000..642b279
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterSphereShape implements EmitterShape {
+
+    private Vector3f center;
+    private float radius;
+
+    public EmitterSphereShape() {
+    }
+
+    public EmitterSphereShape(Vector3f center, float radius) {
+        if (center == null) {
+            throw new NullPointerException();
+        }
+
+        if (radius <= 0) {
+            throw new IllegalArgumentException("Radius must be greater than 0");
+        }
+
+        this.center = center;
+        this.radius = radius;
+    }
+
+    @Override
+    public EmitterShape deepClone() {
+        try {
+            EmitterSphereShape clone = (EmitterSphereShape) super.clone();
+            clone.center = center.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void getRandomPoint(Vector3f store) {
+        do {
+            store.x = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+            store.y = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+            store.z = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+        } while (store.distance(center) > radius);
+    }
+
+    @Override
+    public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+        this.getRandomPoint(store);
+    }
+
+    public Vector3f getCenter() {
+        return center;
+    }
+
+    public void setCenter(Vector3f center) {
+        this.center = center;
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void setRadius(float radius) {
+        this.radius = radius;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(center, "center", null);
+        oc.write(radius, "radius", 0);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        center = (Vector3f) ic.readSavable("center", null);
+        radius = ic.readFloat("radius", 0);
+    }
+}
diff --git a/engine/src/core/com/jme3/export/FormatVersion.java b/engine/src/core/com/jme3/export/FormatVersion.java
new file mode 100644
index 0000000..843248d
--- /dev/null
+++ b/engine/src/core/com/jme3/export/FormatVersion.java
@@ -0,0 +1,22 @@
+package com.jme3.export;
+
+/**
+ * Specifies the version of the format for jME3 object (j3o) files.
+ * 
+ * @author Kirill Vainer
+ */
+public final class FormatVersion {
+    
+    /**
+     * Version number of the format
+     */
+    public static final int VERSION = 2;
+    
+    /**
+     * Signature of the format. Currently "JME3" as ASCII
+     */
+    public static final int SIGNATURE = 0x4A4D4533;
+    
+    private FormatVersion(){
+    }
+}
diff --git a/engine/src/core/com/jme3/export/InputCapsule.java b/engine/src/core/com/jme3/export/InputCapsule.java
new file mode 100644
index 0000000..c7c7e16
--- /dev/null
+++ b/engine/src/core/com/jme3/export/InputCapsule.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.util.IntMap;
+import java.io.IOException;
+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.Map;
+
+/**
+ * @author Joshua Slack
+ */
+public interface InputCapsule {
+
+    public int getSavableVersion(Class<? extends Savable> clazz);
+    
+    // byte primitive
+
+    public byte readByte(String name, byte defVal) throws IOException;
+    public byte[] readByteArray(String name, byte[] defVal) throws IOException;
+    public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException;
+
+    // int primitive
+
+    public int readInt(String name, int defVal) throws IOException;
+    public int[] readIntArray(String name, int[] defVal) throws IOException;
+    public int[][] readIntArray2D(String name, int[][] defVal) throws IOException;
+
+
+    // float primitive
+
+    public float readFloat(String name, float defVal) throws IOException;
+    public float[] readFloatArray(String name, float[] defVal) throws IOException;
+    public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException;
+
+
+    // double primitive
+
+    public double readDouble(String name, double defVal) throws IOException;
+    public double[] readDoubleArray(String name, double[] defVal) throws IOException;
+    public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException;
+
+
+    // long primitive
+
+    public long readLong(String name, long defVal) throws IOException;
+    public long[] readLongArray(String name, long[] defVal) throws IOException;
+    public long[][] readLongArray2D(String name, long[][] defVal) throws IOException;
+
+
+    // short primitive
+
+    public short readShort(String name, short defVal) throws IOException;
+    public short[] readShortArray(String name, short[] defVal) throws IOException;
+    public short[][] readShortArray2D(String name, short[][] defVal) throws IOException;
+
+
+    // boolean primitive
+
+    public boolean readBoolean(String name, boolean defVal) throws IOException;
+    public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException;
+    public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException;
+
+
+    // String
+
+    public String readString(String name, String defVal) throws IOException;
+    public String[] readStringArray(String name, String[] defVal) throws IOException;
+    public String[][] readStringArray2D(String name, String[][] defVal) throws IOException;
+
+
+    // BitSet
+
+    public BitSet readBitSet(String name, BitSet defVal) throws IOException;
+
+
+    // BinarySavable
+
+    public Savable readSavable(String name, Savable defVal) throws IOException;
+    public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException;
+    public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException;
+
+
+    // ArrayLists
+
+    public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException;
+    public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException;
+    public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException;
+
+    public ArrayList<FloatBuffer> readFloatBufferArrayList(String name, ArrayList<FloatBuffer> defVal) throws IOException;
+    public ArrayList<ByteBuffer> readByteBufferArrayList(String name, ArrayList<ByteBuffer> defVal) throws IOException;
+
+
+    // Maps
+
+    public Map<? extends Savable, ? extends Savable> readSavableMap(String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException;
+    public Map<String, ? extends Savable> readStringSavableMap(String name, Map<String, ? extends Savable> defVal) throws IOException;
+    public IntMap<? extends Savable> readIntSavableMap(String name, IntMap<? extends Savable> defVal) throws IOException;
+
+    // NIO BUFFERS
+    // float buffer
+
+    public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException;
+
+
+    // int buffer
+
+    public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException;
+
+
+    // byte buffer
+
+    public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException;
+
+
+    // short buffer
+
+    public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException;
+
+
+    // enums
+
+    public <T extends Enum<T>> T readEnum(String name, Class<T> enumType, T defVal) throws IOException;
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/export/JmeExporter.java b/engine/src/core/com/jme3/export/JmeExporter.java
new file mode 100644
index 0000000..956dfd8
--- /dev/null
+++ b/engine/src/core/com/jme3/export/JmeExporter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * <code>JmeExporter</code> specifies an export implementation for jME3 
+ * data.
+ */
+public interface JmeExporter {
+    
+    /**
+     * Export the {@link Savable} to an OutputStream.
+     * 
+     * @param object The savable to export
+     * @param f The output stream
+     * @return Always returns true. If an error occurs during export, 
+     * an exception is thrown
+     * @throws IOException If an io exception occurs during export
+     */
+    public boolean save(Savable object, OutputStream f) throws IOException;
+    
+    /**
+     * Export the {@link Savable} to a file.
+     * 
+     * @param object The savable to export
+     * @param f The file to export to
+     * @return Always returns true. If an error occurs during export, 
+     * an exception is thrown
+     * @throws IOException If an io exception occurs during export
+     */
+    public boolean save(Savable object, File f) throws IOException;
+    
+    /**
+     * Returns the {@link OutputCapsule} for the given savable object.
+     * 
+     * @param object The object to retrieve an output capsule for.
+     * @return  
+     */
+    public OutputCapsule getCapsule(Savable object);
+}
diff --git a/engine/src/core/com/jme3/export/JmeImporter.java b/engine/src/core/com/jme3/export/JmeImporter.java
new file mode 100644
index 0000000..4986c85
--- /dev/null
+++ b/engine/src/core/com/jme3/export/JmeImporter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+
+public interface JmeImporter extends AssetLoader {
+    public InputCapsule getCapsule(Savable id);
+    public AssetManager getAssetManager();
+    
+    /**
+     * Returns the version number written in the header of the J3O/XML
+     * file.
+     * 
+     * @return Global version number for the file
+     */
+    public int getFormatVersion();
+}
diff --git a/engine/src/core/com/jme3/export/OutputCapsule.java b/engine/src/core/com/jme3/export/OutputCapsule.java
new file mode 100644
index 0000000..fecda9b
--- /dev/null
+++ b/engine/src/core/com/jme3/export/OutputCapsule.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.util.IntMap;
+import java.io.IOException;
+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.Map;
+
+/**
+ * @author Joshua Slack
+ */
+public interface OutputCapsule {
+
+    // byte primitive
+
+    public void write(byte value, String name, byte defVal) throws IOException;
+    public void write(byte[] value, String name, byte[] defVal) throws IOException;
+    public void write(byte[][] value, String name, byte[][] defVal) throws IOException;
+
+
+    // int primitive
+
+    public void write(int value, String name, int defVal) throws IOException;
+    public void write(int[] value, String name, int[] defVal) throws IOException;
+    public void write(int[][] value, String name, int[][] defVal) throws IOException;
+
+
+    // float primitive
+
+    public void write(float value, String name, float defVal) throws IOException;
+    public void write(float[] value, String name, float[] defVal) throws IOException;
+    public void write(float[][] value, String name, float[][] defVal) throws IOException;
+
+
+    // double primitive
+
+    public void write(double value, String name, double defVal) throws IOException;
+    public void write(double[] value, String name, double[] defVal) throws IOException;
+    public void write(double[][] value, String name, double[][] defVal) throws IOException;
+
+
+    // long primitive
+
+    public void write(long value, String name, long defVal) throws IOException;
+    public void write(long[] value, String name, long[] defVal) throws IOException;
+    public void write(long[][] value, String name, long[][] defVal) throws IOException;
+
+
+    // short primitive
+
+    public void write(short value, String name, short defVal) throws IOException;
+    public void write(short[] value, String name, short[] defVal) throws IOException;
+    public void write(short[][] value, String name, short[][] defVal) throws IOException;
+
+
+    // boolean primitive
+
+    public void write(boolean value, String name, boolean defVal) throws IOException;
+    public void write(boolean[] value, String name, boolean[] defVal) throws IOException;
+    public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException;
+
+
+    // String
+
+    public void write(String value, String name, String defVal) throws IOException;
+    public void write(String[] value, String name, String[] defVal) throws IOException;
+    public void write(String[][] value, String name, String[][] defVal) throws IOException;
+
+
+    // BitSet
+
+    public void write(BitSet value, String name, BitSet defVal) throws IOException;
+
+
+    // BinarySavable
+
+    public void write(Savable object, String name, Savable defVal) throws IOException;
+    public void write(Savable[] objects, String name, Savable[] defVal) throws IOException;
+    public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException;
+
+
+    // ArrayLists
+
+    public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException;
+    public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException;
+    public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException;
+
+    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array, String name, ArrayList<FloatBuffer> defVal) throws IOException;
+    public void writeByteBufferArrayList(ArrayList<ByteBuffer> array, String name, ArrayList<ByteBuffer> defVal) throws IOException;
+
+
+    // Maps
+
+    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException;
+    public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException;
+    public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException;
+
+    // NIO BUFFERS
+    // float buffer
+
+    public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException;
+
+
+    // int buffer
+
+    public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException;
+
+
+    // byte buffer
+
+    public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException;
+
+
+    // short buffer
+
+    public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException;
+
+
+    // enums
+
+    public void write(Enum value, String name, Enum defVal) throws IOException;
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/export/ReadListener.java b/engine/src/core/com/jme3/export/ReadListener.java
new file mode 100644
index 0000000..bb87b3a
--- /dev/null
+++ b/engine/src/core/com/jme3/export/ReadListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+public interface ReadListener {
+
+    public void readBytes(int bytes);
+    
+}
diff --git a/engine/src/core/com/jme3/export/Savable.java b/engine/src/core/com/jme3/export/Savable.java
new file mode 100644
index 0000000..0e90e30
--- /dev/null
+++ b/engine/src/core/com/jme3/export/Savable.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import java.io.IOException;
+
+/**
+ * <code>Savable</code> is an interface for objects that can be serialized
+ * using jME's serialization system. 
+ * 
+ * @author Kirill Vainer
+ */
+public interface Savable {
+    void write(JmeExporter ex) throws IOException;
+    void read(JmeImporter im) throws IOException;
+}
diff --git a/engine/src/core/com/jme3/export/SavableClassUtil.java b/engine/src/core/com/jme3/export/SavableClassUtil.java
new file mode 100644
index 0000000..cda328a
--- /dev/null
+++ b/engine/src/core/com/jme3/export/SavableClassUtil.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.animation.Animation;
+import com.jme3.effect.shapes.*;
+import com.jme3.material.MatParamTexture;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>SavableClassUtil</code> contains various utilities to handle
+ * Savable classes. The methods are general enough to not be specific to any
+ * particular implementation.
+ * Currently it will remap any classes from old paths to new paths
+ * so that old J3O models can still be loaded.
+ *
+ * @author mpowell
+ * @author Kirill Vainer
+ */
+public class SavableClassUtil {
+
+    private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
+    
+    private static void addRemapping(String oldClass, Class<? extends Savable> newClass){
+        classRemappings.put(oldClass, newClass.getName());
+    }
+    
+    static {
+        addRemapping("com.jme3.effect.EmitterSphereShape", EmitterSphereShape.class);
+        addRemapping("com.jme3.effect.EmitterBoxShape", EmitterBoxShape.class);
+        addRemapping("com.jme3.effect.EmitterMeshConvexHullShape", EmitterMeshConvexHullShape.class);
+        addRemapping("com.jme3.effect.EmitterMeshFaceShape", EmitterMeshFaceShape.class);
+        addRemapping("com.jme3.effect.EmitterMeshVertexShape", EmitterMeshVertexShape.class);
+        addRemapping("com.jme3.effect.EmitterPointShape", EmitterPointShape.class);
+        addRemapping("com.jme3.material.Material$MatParamTexture", MatParamTexture.class);
+        addRemapping("com.jme3.animation.BoneAnimation", Animation.class);
+        addRemapping("com.jme3.animation.SpatialAnimation", Animation.class);
+    }
+    
+    private static String remapClass(String className) throws ClassNotFoundException {
+        String result = classRemappings.get(className);
+        if (result == null) {
+            return className;
+        } else {
+            return result;
+        }
+    }
+    
+    public static boolean isImplementingSavable(Class clazz){
+        boolean result = Savable.class.isAssignableFrom(clazz);
+        return result;
+    }
+
+    public static int[] getSavableVersions(Class<? extends Savable> clazz) throws IOException{
+        ArrayList<Integer> versionList = new ArrayList<Integer>();
+        Class superclass = clazz;
+        do {
+            versionList.add(getSavableVersion(superclass));
+            superclass = superclass.getSuperclass();
+        } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass));
+        
+        int[] versions = new int[versionList.size()];
+        for (int i = 0; i < versionList.size(); i++){
+            versions[i] = versionList.get(i);
+        }
+        return versions;
+    }
+    
+    public static int getSavableVersion(Class<? extends Savable> clazz) throws IOException{
+        try {
+            Field field = clazz.getField("SAVABLE_VERSION");
+            Class<? extends Savable> declaringClass = (Class<? extends Savable>) field.getDeclaringClass();
+            if (declaringClass == clazz){
+                return field.getInt(null); 
+            }else{
+                return 0; // This class doesn't declare this field, e.g. version == 0
+            }
+        } catch (IllegalAccessException ex) {
+            IOException ioEx = new IOException();
+            ioEx.initCause(ex);
+            throw ioEx;
+        } catch (IllegalArgumentException ex) {
+            throw ex; // can happen if SAVABLE_VERSION is not static
+        } catch (NoSuchFieldException ex) {
+            return 0; // not using versions
+        }
+    }
+    
+    public static int getSavedSavableVersion(Object savable, Class<? extends Savable> desiredClass, int[] versions, int formatVersion){
+        Class thisClass = savable.getClass();
+        int count = 0;
+        
+        while (thisClass != desiredClass) {
+            thisClass = thisClass.getSuperclass();
+            if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)){
+                count ++;
+            }else{
+                break;
+            }
+        }
+
+        if (thisClass == null){
+            throw new IllegalArgumentException(savable.getClass().getName() + 
+                                               " does not extend " + 
+                                               desiredClass.getName() + "!");
+        }else if (count >= versions.length){
+            if (formatVersion <= 1){
+                return 0; // for buggy versions of j3o
+            }else{
+                throw new IllegalArgumentException(savable.getClass().getName() + 
+                                                   " cannot access version of " +
+                                                   desiredClass.getName() + 
+                                                   " because it doesn't implement Savable");
+            }
+        }
+        return versions[count];
+    }
+    
+    /**
+     * fromName creates a new Savable from the provided class name. First registered modules
+     * are checked to handle special cases, if the modules do not handle the class name, the
+     * class is instantiated directly. 
+     * @param className the class name to create.
+     * @param inputCapsule the InputCapsule that will be used for loading the Savable (to look up ctor parameters)
+     * @return the Savable instance of the class.
+     * @throws InstantiationException thrown if the class does not have an empty constructor.
+     * @throws IllegalAccessException thrown if the class is not accessable.
+     * @throws ClassNotFoundException thrown if the class name is not in the classpath.
+     * @throws IOException when loading ctor parameters fails
+     */
+    public static Savable fromName(String className) throws InstantiationException,
+            IllegalAccessException, ClassNotFoundException, IOException {
+
+        className = remapClass(className);
+        try {
+            return (Savable) Class.forName(className).newInstance();
+        } catch (InstantiationException e) {
+            Logger.getLogger(SavableClassUtil.class.getName()).log(
+                    Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n"
+                    + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className);
+            throw e;
+        } catch (IllegalAccessException e) {
+            Logger.getLogger(SavableClassUtil.class.getName()).log(
+                    Level.SEVERE, "{0} \n"
+                    + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage());
+            throw e;
+        }
+    }
+
+    public static Savable fromName(String className, List<ClassLoader> loaders) throws InstantiationException,
+            IllegalAccessException, ClassNotFoundException, IOException {
+        if (loaders == null) {
+            return fromName(className);
+        }
+        
+        String newClassName = remapClass(className);
+        synchronized(loaders) {
+            for (ClassLoader classLoader : loaders){
+                try {
+                    return (Savable) classLoader.loadClass(newClassName).newInstance();
+                } catch (InstantiationException e) {
+                } catch (IllegalAccessException e) {
+                }
+
+            }
+        }
+
+        return fromName(className);
+    }
+}
diff --git a/engine/src/core/com/jme3/font/BitmapCharacter.java b/engine/src/core/com/jme3/font/BitmapCharacter.java
new file mode 100644
index 0000000..8e06b87
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapCharacter.java
@@ -0,0 +1,199 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.export.*;

+import com.jme3.util.IntMap;

+import com.jme3.util.IntMap.Entry;

+import java.io.IOException;

+

+/**

+ * Represents a single bitmap character.

+ */

+public class BitmapCharacter implements Savable, Cloneable {

+    private char c;

+    private int x;

+    private int y;

+    private int width;

+    private int height;

+    private int xOffset;

+    private int yOffset;

+    private int xAdvance;

+    private IntMap<Integer> kerning = new IntMap<Integer>();

+    private int page;

+    

+    public BitmapCharacter() {}

+    

+    public BitmapCharacter(char c) {

+        this.c = c;

+    }

+

+    @Override

+    public BitmapCharacter clone() {

+        try {

+            BitmapCharacter result = (BitmapCharacter) super.clone();

+            result.kerning = kerning.clone();

+            return result;

+        } catch (CloneNotSupportedException ex) {

+            throw new AssertionError();

+        }

+    }

+

+    public int getX() {

+        return x;

+    }

+

+    public void setX(int x) {

+        this.x = x;

+    }

+

+    public int getY() {

+        return y;

+    }

+

+    public void setY(int y) {

+        this.y = y;

+    }

+

+    public int getWidth() {

+        return width;

+    }

+

+    public void setWidth(int width) {

+        this.width = width;

+    }

+

+    public int getHeight() {

+        return height;

+    }

+

+    public void setHeight(int height) {

+        this.height = height;

+    }

+

+    public int getXOffset() {

+        return xOffset;

+    }

+

+    public void setXOffset(int offset) {

+        xOffset = offset;

+    }

+

+    public int getYOffset() {

+        return yOffset;

+    }

+

+    public void setYOffset(int offset) {

+        yOffset = offset;

+    }

+

+    public int getXAdvance() {

+        return xAdvance;

+    }

+

+    public void setXAdvance(int advance) {

+        xAdvance = advance;

+    }

+

+    public void setPage(int page) {

+        this.page = page;

+    }

+

+    public int getPage() {

+        return page;

+    }

+    

+    public char getChar() {

+        return c;

+    }

+    

+    public void setChar(char c) {

+        this.c = c;

+    }

+

+    public void addKerning(int second, int amount){

+        kerning.put(second, amount);

+    }

+

+    public int getKerning(int second){

+        Integer i = kerning.get(second);

+        if (i == null)

+            return 0;

+        else

+            return i.intValue();

+    }

+

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(c, "c", 0);

+        oc.write(x, "x", 0);

+        oc.write(y, "y", 0);

+        oc.write(width, "width", 0);

+        oc.write(height, "height", 0);

+        oc.write(xOffset, "xOffset", 0);

+        oc.write(yOffset, "yOffset", 0);

+        oc.write(xAdvance, "xAdvance", 0);

+

+        int[] seconds = new int[kerning.size()];

+        int[] amounts = new int[seconds.length];

+

+        int i = 0;

+        for (Entry<Integer> entry : kerning){

+            seconds[i] = entry.getKey();

+            amounts[i] = entry.getValue();

+            i++;

+        }

+

+        oc.write(seconds, "seconds", null);

+        oc.write(amounts, "amounts", null);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        c = (char) ic.readInt("c", 0);

+        x = ic.readInt("x", 0);

+        y = ic.readInt("y", 0);

+        width = ic.readInt("width", 0);

+        height = ic.readInt("height", 0);

+        xOffset = ic.readInt("xOffset", 0);

+        yOffset = ic.readInt("yOffset", 0);

+        xAdvance = ic.readInt("xAdvance", 0);

+

+        int[] seconds = ic.readIntArray("seconds", null);

+        int[] amounts = ic.readIntArray("amounts", null);

+

+        for (int i = 0; i < seconds.length; i++){

+            kerning.put(seconds[i], amounts[i]);

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapCharacterSet.java b/engine/src/core/com/jme3/font/BitmapCharacterSet.java
new file mode 100644
index 0000000..be51554
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapCharacterSet.java
@@ -0,0 +1,223 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.export.*;

+import com.jme3.util.IntMap;

+import com.jme3.util.IntMap.Entry;

+import java.io.IOException;

+

+public class BitmapCharacterSet implements Savable {

+

+    private int lineHeight;

+    private int base;

+    private int renderedSize;

+    private int width;

+    private int height;

+    private IntMap<IntMap<BitmapCharacter>> characters;

+    private int pageSize;

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(lineHeight, "lineHeight", 0);

+        oc.write(base, "base", 0);

+        oc.write(renderedSize, "renderedSize", 0);

+        oc.write(width, "width", 0);

+        oc.write(height, "height", 0);

+        oc.write(pageSize, "pageSize", 0);

+

+        int[] styles = new int[characters.size()];

+        int index = 0;

+        for (Entry<IntMap<BitmapCharacter>> entry : characters) {

+            int style = entry.getKey();

+            styles[index] = style;

+            index++;

+            IntMap<BitmapCharacter> charset = entry.getValue();

+            writeCharset(oc, style, charset);

+        }

+        oc.write(styles, "styles", null);

+    }

+

+    protected void writeCharset(OutputCapsule oc, int style, IntMap<BitmapCharacter> charset) throws IOException {

+        int size = charset.size();

+        short[] indexes = new short[size];

+        BitmapCharacter[] chars = new BitmapCharacter[size];

+        int i = 0;

+        for (Entry<BitmapCharacter> chr : charset){

+            indexes[i] = (short) chr.getKey();

+            chars[i] = chr.getValue();

+            i++;

+        }

+

+        oc.write(indexes, "indexes"+style, null);

+        oc.write(chars,   "chars"+style,   null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        lineHeight = ic.readInt("lineHeight", 0);

+        base = ic.readInt("base", 0);

+        renderedSize = ic.readInt("renderedSize", 0);

+        width = ic.readInt("width", 0);

+        height = ic.readInt("height", 0);

+        pageSize = ic.readInt("pageSize", 0);

+        int[] styles = ic.readIntArray("styles", null);

+

+        for (int style : styles) {

+            characters.put(style, readCharset(ic, style));

+        }

+    }

+

+    private IntMap<BitmapCharacter> readCharset(InputCapsule ic, int style) throws IOException {

+        IntMap<BitmapCharacter> charset = new IntMap<BitmapCharacter>();

+        short[] indexes = ic.readShortArray("indexes"+style, null);

+        Savable[] chars = ic.readSavableArray("chars"+style, null);

+

+        for (int i = 0; i < indexes.length; i++){

+            int index = indexes[i] & 0xFFFF;

+            BitmapCharacter chr = (BitmapCharacter) chars[i];

+            charset.put(index, chr);

+        }

+        return charset;

+    }

+

+    public BitmapCharacterSet() {

+        characters = new IntMap<IntMap<BitmapCharacter>>();

+    }

+

+    public BitmapCharacter getCharacter(int index){

+        return getCharacter(index, 0);

+    }

+    

+    public BitmapCharacter getCharacter(int index, int style){

+        IntMap<BitmapCharacter> map = getCharacterSet(style);

+        return map.get(index);

+    }

+

+    private IntMap<BitmapCharacter> getCharacterSet(int style) {

+        if (characters.size() == 0) {

+            characters.put(style, new IntMap<BitmapCharacter>());

+        }

+        return characters.get(style);

+    }

+

+    public void addCharacter(int index, BitmapCharacter ch){

+        getCharacterSet(0).put(index, ch);

+    }

+

+    public int getLineHeight() {

+        return lineHeight;

+    }

+

+    public void setLineHeight(int lineHeight) {

+        this.lineHeight = lineHeight;

+    }

+

+    public int getBase() {

+        return base;

+    }

+

+    public void setBase(int base) {

+        this.base = base;

+    }

+

+    public int getRenderedSize() {

+        return renderedSize;

+    }

+

+    public void setRenderedSize(int renderedSize) {

+        this.renderedSize = renderedSize;

+    }

+

+    public int getWidth() {

+        return width;

+    }

+

+    public void setWidth(int width) {

+        this.width = width;

+    }

+

+    public int getHeight() {

+        return height;

+    }

+

+    public void setHeight(int height) {

+        this.height = height;

+    }

+    

+    /**

+     * Merge two fonts.

+     * If two font have the same style, merge will fail.

+     * @param styleSet Style must be assigned to this.

+     * @author Yonghoon

+     */

+    public void merge(BitmapCharacterSet styleSet) {

+        if (this.renderedSize != styleSet.renderedSize) {

+            throw new RuntimeException("Only support same font size");

+        }

+        for (Entry<IntMap<BitmapCharacter>> entry : styleSet.characters) {

+            int style = entry.getKey();

+            if (style == 0) {

+                throw new RuntimeException("Style must be set first. use setStyle(int)");

+            }

+            IntMap<BitmapCharacter> charset = entry.getValue();

+            this.lineHeight = Math.max(this.lineHeight, styleSet.lineHeight);

+            IntMap<BitmapCharacter> old = this.characters.put(style, charset);

+            if (old != null) {

+                throw new RuntimeException("Can't override old style");

+            }

+            

+            for (Entry<BitmapCharacter> charEntry : charset) {

+                BitmapCharacter ch = charEntry.getValue();

+                ch.setPage(ch.getPage() + this.pageSize);

+            }

+        }

+        this.pageSize += styleSet.pageSize;

+    }

+

+    public void setStyle(int style) {

+        if (characters.size() > 1) {

+            throw new RuntimeException("Applicable only for single style font");

+        }

+        Entry<IntMap<BitmapCharacter>> entry = characters.iterator().next();

+        IntMap<BitmapCharacter> charset = entry.getValue();

+        characters.remove(entry.getKey());

+        characters.put(style, charset);

+    }

+

+    void setPageSize(int pageSize) {

+        this.pageSize = pageSize;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapFont.java b/engine/src/core/com/jme3/font/BitmapFont.java
new file mode 100644
index 0000000..ce532e9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapFont.java
@@ -0,0 +1,286 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.export.*;

+import com.jme3.material.Material;

+import java.io.IOException;

+

+/**

+ * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator

+ * @author dhdd

+ */

+public class BitmapFont implements Savable {

+

+    /**

+     * Specifies horizontal alignment for text.

+     * 

+     * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align) 

+     */

+    public enum Align {

+        

+        /**

+         * Align text on the left of the text block

+         */

+        Left, 

+        

+        /**

+         * Align text in the center of the text block

+         */

+        Center, 

+        

+        /**

+         * Align text on the right of the text block

+         */

+        Right

+    }

+    

+    /**

+     * Specifies vertical alignment for text.

+     * 

+     * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign) 

+     */

+    public enum VAlign {

+        /**

+         * Align text on the top of the text block

+         */

+        Top, 

+        

+        /**

+         * Align text in the center of the text block

+         */

+        Center, 

+        

+        /**

+         * Align text at the bottom of the text block

+         */

+        Bottom

+    }

+

+    private BitmapCharacterSet charSet;

+    private Material[] pages;

+

+    public BitmapFont() {

+    }

+

+    public BitmapText createLabel(String content){

+        BitmapText label = new BitmapText(this);

+        label.setSize(getCharSet().getRenderedSize());

+        label.setText(content);

+        return label;

+    }

+

+    public float getPreferredSize(){

+        return getCharSet().getRenderedSize();

+    }

+

+    public void setCharSet(BitmapCharacterSet charSet) {

+        this.charSet = charSet;

+    }

+

+    public void setPages(Material[] pages) {

+        this.pages = pages;

+        charSet.setPageSize(pages.length);

+    }

+

+    public Material getPage(int index) {

+        return pages[index];

+    }

+

+    public int getPageSize() {

+        return pages.length;

+    }

+

+    public BitmapCharacterSet getCharSet() {

+        return charSet;

+    }

+    

+    /**

+     * Gets the line height of a StringBlock.

+     * @param sb

+     * @return

+     */

+    public float getLineHeight(StringBlock sb) {

+        return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize());

+    }

+

+    public float getCharacterAdvance(char curChar, char nextChar, float size){

+        BitmapCharacter c = charSet.getCharacter(curChar);

+        if (c == null)

+            return 0f;

+

+        float advance = size * c.getXAdvance();

+        advance += c.getKerning(nextChar) * size;

+        return advance;

+    }

+

+    private int findKerningAmount(int newLineLastChar, int nextChar) {

+        BitmapCharacter c = charSet.getCharacter(newLineLastChar);

+        if (c == null)

+            return 0;

+        return c.getKerning(nextChar);

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(charSet, "charSet", null);

+        oc.write(pages, "pages", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        charSet = (BitmapCharacterSet) ic.readSavable("charSet", null);

+        Savable[] pagesSavable = ic.readSavableArray("pages", null);

+        pages = new Material[pagesSavable.length];

+        System.arraycopy(pagesSavable, 0, pages, 0, pages.length);

+    }

+

+    public float getLineWidth(CharSequence text){

+    

+        // This method will probably always be a bit of a maintenance

+        // nightmare since it basis its calculation on a different 

+        // routine than the Letters class.  The ideal situation would

+        // be to abstract out letter position and size into its own

+        // class that both BitmapFont and Letters could use for

+        // positioning.

+        // If getLineWidth() here ever again returns a different value

+        // than Letters does with the same text then it might be better

+        // just to create a Letters object for the sole purpose of

+        // getting a text size.  It's less efficient but at least it

+        // would be accurate.  

+        

+        // And here I am mucking around in here again...

+        //

+        // A font character has a few values that are pertinent to the

+        // line width:

+        //  xOffset

+        //  xAdvance

+        //  kerningAmount(nextChar)

+        //

+        // The way BitmapText ultimately works is that the first character

+        // starts with xOffset included (ie: it is rendered at -xOffset).

+        // Its xAdvance is wider to accomodate that initial offset.

+        // The cursor position is advanced by xAdvance each time.

+        //

+        // So, a width should be calculated in a similar way.  Start with

+        // -xOffset + xAdvance for the first character and then each subsequent

+        // character is just xAdvance more 'width'.

+        // 

+        // The kerning amount from one character to the next affects the

+        // cursor position of that next character and thus the ultimate width 

+        // and so must be factored in also.

+        

+        float lineWidth = 0f;

+        float maxLineWidth = 0f;

+        char lastChar = 0;

+        boolean firstCharOfLine = true;

+//        float sizeScale = (float) block.getSize() / charSet.getRenderedSize();

+        float sizeScale = 1f;

+        for (int i = 0; i < text.length(); i++){

+            char theChar = text.charAt(i);

+            if (theChar == '\n'){

+                maxLineWidth = Math.max(maxLineWidth, lineWidth);

+                lineWidth = 0f;

+                firstCharOfLine = true;

+                continue;

+            }

+            BitmapCharacter c = charSet.getCharacter((int) theChar);

+            if (c != null){

+                if (theChar == '\\' && i<text.length()-1 && text.charAt(i+1)=='#'){

+                    if (i+5<text.length() && text.charAt(i+5)=='#'){

+                        i+=5;

+                        continue;

+                    }else if (i+8<text.length() && text.charAt(i+8)=='#'){

+                        i+=8;

+                        continue;

+                    }

+                }

+                if (!firstCharOfLine){

+                    lineWidth += findKerningAmount(lastChar, theChar) * sizeScale;                    

+                } else {

+                    // The first character needs to add in its xOffset but it

+                    // is the only one... and negative offsets = postive width

+                    // because we're trying to account for the part that hangs

+                    // over the left.  So we subtract. 

+                    lineWidth -= c.getXOffset() * sizeScale;                    

+                    firstCharOfLine = false;

+                }

+                float xAdvance = c.getXAdvance() * sizeScale;

+                

+                // If this is the last character, then we really should have

+                // only add its width.  The advance may include extra spacing

+                // that we don't care about.

+                if (i == text.length() - 1) {

+                    lineWidth += c.getWidth() * sizeScale;

+                    

+                    // Since theh width includes the xOffset then we need

+                    // to take it out again by adding it, ie: offset the width

+                    // we just added by the appropriate amount.

+                    lineWidth += c.getXOffset() * sizeScale;                      

+                } else {                 

+                    lineWidth += xAdvance;

+                }

+            }

+        }

+        return Math.max(maxLineWidth, lineWidth);

+    }

+

+

+    /**

+     * Merge two fonts.

+     * If two font have the same style, merge will fail.

+     * @param styleSet Style must be assigned to this.

+     * @author Yonghoon

+     */

+    public void merge(BitmapFont newFont) {

+        charSet.merge(newFont.charSet);

+        final int size1 = this.pages.length;

+        final int size2 = newFont.pages.length;

+        

+        Material[] tmp = new Material[size1+size2];

+        System.arraycopy(this.pages, 0, tmp, 0, size1);

+        System.arraycopy(newFont.pages, 0, tmp, size1, size2);

+        

+        this.pages = tmp;

+        

+//        this.pages = Arrays.copyOf(this.pages, size1+size2);

+//        System.arraycopy(newFont.pages, 0, this.pages, size1, size2);

+    }

+

+    public void setStyle(int style) {

+        charSet.setStyle(style);

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapText.java b/engine/src/core/com/jme3/font/BitmapText.java
new file mode 100644
index 0000000..7601324
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapText.java
@@ -0,0 +1,361 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.font.BitmapFont.Align;

+import com.jme3.font.BitmapFont.VAlign;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.renderer.RenderManager;

+import com.jme3.scene.Node;

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+/**

+ * @author YongHoon

+ */

+public class BitmapText extends Node {

+    private BitmapFont font;

+    private StringBlock block;

+    private boolean needRefresh = true;

+    private final BitmapTextPage[] textPages;

+    private Letters letters;

+

+    public BitmapText(BitmapFont font) {

+        this(font, false, false);

+    }

+

+    public BitmapText(BitmapFont font, boolean rightToLeft) {

+        this(font, rightToLeft, false);

+    }

+

+    public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) {

+        textPages = new BitmapTextPage[font.getPageSize()];

+        for (int page = 0; page < textPages.length; page++) {

+            textPages[page] = new BitmapTextPage(font, arrayBased, page);

+            attachChild(textPages[page]);

+        }

+

+        this.font = font;

+        this.block = new StringBlock();

+        block.setSize(font.getPreferredSize());

+        letters = new Letters(font, block, rightToLeft);

+    }

+

+    @Override

+    public BitmapText clone() {

+        BitmapText clone = (BitmapText) super.clone();

+        for (int i = 0; i < textPages.length; i++) {

+            clone.textPages[i] = textPages[i].clone();

+        }

+        clone.block = block.clone();

+        clone.needRefresh = true;

+        return clone;

+    }

+

+    public BitmapFont getFont() {

+        return font;

+    }

+    

+    /**

+     * Changes text size

+     * @param size text size

+     */

+    public void setSize(float size) {

+        block.setSize(size);

+        needRefresh = true;

+        letters.invalidate();

+    }

+

+    /**

+     *

+     * @param text charsequence to change text to

+     */

+    public void setText(CharSequence text) {

+        // note: text.toString() is free if text is already a java.lang.String.

+        setText( text != null ? text.toString() : null );

+    }

+

+    /**

+     *

+     * @param text String to change text to

+     */

+    public void setText(String text) {

+        text = text == null ? "" : text;

+        if (text == block.getText() || block.getText().equals(text)) {

+            return;

+        }

+

+        block.setText(text);

+        letters.setText(text);

+        needRefresh = true;

+    }

+

+    /**

+     * @return returns text

+     */

+    public String getText() {

+        return block.getText();

+    }

+

+    /**

+     * @return color of the text

+     */

+    public ColorRGBA getColor() {

+        return letters.getBaseColor();

+    }

+

+    /**

+     * changes text color. all substring colors are deleted.

+     * @param color new color of text

+     */

+    public void setColor(ColorRGBA color) {

+        letters.setColor(color);

+        letters.invalidate(); // TODO: Don't have to align.

+        needRefresh = true;

+    }

+

+    /**

+     * Define area where bitmaptext will be rendered

+     * @param rect position and size box where text is rendered

+     */

+    public void setBox(Rectangle rect) {

+        block.setTextBox(rect);

+        letters.invalidate();

+        needRefresh = true;

+    }

+    

+    /**

+     * @return height of the line

+     */

+    public float getLineHeight() {

+        return font.getLineHeight(block);

+    }

+    

+    /**

+     * @return height of whole textblock

+     */

+    public float getHeight() {

+        if (needRefresh) {

+            assemble();

+        }

+        float height = getLineHeight()*block.getLineCount();

+        Rectangle textBox = block.getTextBox();

+        if (textBox != null) {

+            return Math.max(height, textBox.height);

+        }

+        return height;

+    }

+    

+    /**

+     * @return width of line

+     */

+    public float getLineWidth() {

+        if (needRefresh) {

+            assemble();

+        }

+        Rectangle textBox = block.getTextBox();

+        if (textBox != null) {

+            return Math.max(letters.getTotalWidth(), textBox.width);

+        }

+        return letters.getTotalWidth();

+    }

+    

+    /**

+     * @return line count

+     */

+    public int getLineCount() {

+        if (needRefresh) {

+            assemble();

+        }

+        return block.getLineCount();

+    }

+    

+    public LineWrapMode getLineWrapMode() {

+        return block.getLineWrapMode();

+    }

+    

+    /**

+     * Set horizontal alignment. Applicable only when text bound is set.

+     * @param align

+     */

+    public void setAlignment(BitmapFont.Align align) {

+        if (block.getTextBox() == null && align != Align.Left) {

+            throw new RuntimeException("Bound is not set");

+        }

+        block.setAlignment(align);

+        letters.invalidate();

+        needRefresh = true;

+    }

+    

+    /**

+     * Set vertical alignment. Applicable only when text bound is set.

+     * @param align

+     */

+    public void setVerticalAlignment(BitmapFont.VAlign align) {

+        if (block.getTextBox() == null && align != VAlign.Top) {

+            throw new RuntimeException("Bound is not set");

+        }

+        block.setVerticalAlignment(align);

+        letters.invalidate();

+        needRefresh = true;

+    }

+    

+    public BitmapFont.Align getAlignment() {

+        return block.getAlignment();

+    }

+    

+    public BitmapFont.VAlign getVerticalAlignment() {

+        return block.getVerticalAlignment();

+    }

+    

+    /**

+     * Set the font style of substring. If font doesn't contain style, default style is used

+     * @param start start index to set style. inclusive.

+     * @param end   end index to set style. EXCLUSIVE.

+     * @param style

+     */

+    public void setStyle(int start, int end, int style) {

+        letters.setStyle(start, end, style);

+    }

+    

+    /**

+     * Set the font style of substring. If font doesn't contain style, default style is applied

+     * @param regexp regular expression

+     * @param style

+     */

+    public void setStyle(String regexp, int style) {

+        Pattern p = Pattern.compile(regexp);

+        Matcher m = p.matcher(block.getText());

+        while (m.find()) {

+            setStyle(m.start(), m.end(), style);

+        }

+    }

+    

+    /**

+     * Set the color of substring.

+     * @param start start index to set style. inclusive.

+     * @param end   end index to set style. EXCLUSIVE.

+     * @param color

+     */

+    public void setColor(int start, int end, ColorRGBA color) {

+        letters.setColor(start, end, color);

+        letters.invalidate();

+        needRefresh = true;

+    }

+    

+    /**

+     * Set the color of substring.

+     * @param regexp regular expression

+     * @param color

+     */

+    public void setColor(String regexp, ColorRGBA color) {

+        Pattern p = Pattern.compile(regexp);

+        Matcher m = p.matcher(block.getText());

+        while (m.find()) {

+            letters.setColor(m.start(), m.end(), color);

+        }

+        letters.invalidate();

+        needRefresh = true;

+    }

+    

+    /**

+     * @param tabs tab positions

+     */

+    public void setTabPosition(float... tabs) {

+        block.setTabPosition(tabs);

+        letters.invalidate();

+        needRefresh = false;

+    }

+    

+    /**

+     * used for the tabs over the last tab position.

+     * @param width tab size

+     */

+    public void setTabWidth(float width) {

+        block.setTabWidth(width);

+        letters.invalidate();

+        needRefresh = false;

+    }

+    

+    /**

+     * for setLineWrapType(LineWrapType.NoWrap),

+     * set the last character when the text exceeds the bound.

+     * @param c

+     */

+    public void setEllipsisChar(char c) {

+        block.setEllipsisChar(c);

+        letters.invalidate();

+        needRefresh = false;

+    }

+

+    /**

+     * Available only when bounding is set. <code>setBox()</code> method call is needed in advance.

+     * true when

+     * @param wrap NoWrap   : Letters over the text bound is not shown. the last character is set to '...'(0x2026)

+     *             Character: Character is split at the end of the line.

+     *             Word     : Word is split at the end of the line.

+     */

+    public void setLineWrapMode(LineWrapMode wrap) {

+        if (block.getLineWrapMode() != wrap) {

+            block.setLineWrapMode(wrap);

+            letters.invalidate();

+            needRefresh = true;

+        }

+    }

+

+    @Override

+    public void updateLogicalState(float tpf) {

+        super.updateLogicalState(tpf);

+        if (needRefresh) {

+            assemble();

+        }

+    }

+

+    private void assemble() {

+        // first generate quadlist

+        letters.update();

+        

+        for (int i = 0; i < textPages.length; i++) {

+            textPages[i].assemble(letters);

+        }

+        needRefresh = false;

+    }

+    

+    public void render(RenderManager rm) {

+        for (BitmapTextPage page : textPages) {

+            Material mat = page.getMaterial();

+            mat.setTexture("Texture", page.getTexture());

+            mat.render(page, rm);

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/font/BitmapTextPage.java b/engine/src/core/com/jme3/font/BitmapTextPage.java
new file mode 100644
index 0000000..f49b5c9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapTextPage.java
@@ -0,0 +1,197 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.material.Material;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.texture.Texture2D;

+import com.jme3.util.BufferUtils;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.nio.ShortBuffer;

+import java.util.LinkedList;

+

+/**

+ * One page per BitmapText Font Texture.

+ * @author Lim, YongHoon

+ */

+class BitmapTextPage extends Geometry {

+    

+    private final float[] pos;

+    private final float[] tc;

+    private final short[] idx;

+    private final byte[] color;

+    private final int page;

+    private final Texture2D texture;

+    private final LinkedList<LetterQuad> pageQuads = new LinkedList<LetterQuad>();

+

+    BitmapTextPage(BitmapFont font, boolean arrayBased, int page) {

+        super("BitmapFont", new Mesh());

+

+        if (font == null) {

+            throw new NullPointerException("'font' cannot be null.");

+        }

+

+        this.page = page;

+

+        Material mat = font.getPage(page);

+        if (mat == null) {

+            throw new IllegalStateException("The font's texture was not found!");

+        }

+

+        setMaterial(mat);

+        this.texture = (Texture2D) mat.getTextureParam("ColorMap").getTextureValue();

+

+        // initialize buffers

+        Mesh m = getMesh();

+        m.setBuffer(Type.Position, 3, new float[0]);

+        m.setBuffer(Type.TexCoord, 2, new float[0]);

+        m.setBuffer(Type.Color, 4, new byte[0]);

+        m.setBuffer(Type.Index, 3, new short[0]);

+

+        // scale colors from 0 - 255 range into 0 - 1

+        m.getBuffer(Type.Color).setNormalized(true);

+

+        arrayBased = true;

+

+        if (arrayBased) {

+            pos = new float[4 * 3];  // 4 verticies * 3 floats

+            tc = new float[4 * 2];  // 4 verticies * 2 floats

+            idx = new short[2 * 3];  // 2 triangles * 3 indices

+            color = new byte[4 * 4];   // 4 verticies * 4 bytes

+        } else {

+            pos = null;

+            tc = null;

+            idx = null;

+            color = null;

+        }

+    }

+    

+    BitmapTextPage(BitmapFont font, boolean arrayBased) {

+        this(font, arrayBased, 0);

+    }

+

+    BitmapTextPage(BitmapFont font) {

+        this(font, false, 0);

+    }

+    

+    Texture2D getTexture() {

+        return texture;

+    }

+

+    @Override

+    public BitmapTextPage clone() {

+        BitmapTextPage clone = (BitmapTextPage) super.clone();

+        clone.mesh = mesh.deepClone();

+        return clone;

+    }

+

+    void assemble(Letters quads) {

+        pageQuads.clear();

+        quads.rewind();

+        

+        while (quads.nextCharacter()) {

+            if (quads.isPrintable()) {

+                if (quads.getCharacterSetPage() == page) {

+                    pageQuads.add(quads.getQuad());

+                }

+            }

+        }

+        

+        Mesh m = getMesh();

+        int vertCount = pageQuads.size() * 4;

+        int triCount = pageQuads.size() * 2;

+

+        VertexBuffer pb = m.getBuffer(Type.Position);

+        VertexBuffer tb = m.getBuffer(Type.TexCoord);

+        VertexBuffer ib = m.getBuffer(Type.Index);

+        VertexBuffer cb = m.getBuffer(Type.Color);

+

+        FloatBuffer fpb = (FloatBuffer) pb.getData();

+        FloatBuffer ftb = (FloatBuffer) tb.getData();

+        ShortBuffer sib = (ShortBuffer) ib.getData();

+        ByteBuffer bcb = (ByteBuffer) cb.getData();

+

+        // increase capacity of buffers as needed

+        fpb.rewind();

+        fpb = BufferUtils.ensureLargeEnough(fpb, vertCount * 3);

+        fpb.limit(vertCount * 3);

+        pb.updateData(fpb);

+

+        ftb.rewind();

+        ftb = BufferUtils.ensureLargeEnough(ftb, vertCount * 2);

+        ftb.limit(vertCount * 2);

+        tb.updateData(ftb);

+

+        bcb.rewind();

+        bcb = BufferUtils.ensureLargeEnough(bcb, vertCount * 4);

+        bcb.limit(vertCount * 4);

+        cb.updateData(bcb);

+

+        sib.rewind();

+        sib = BufferUtils.ensureLargeEnough(sib, triCount * 3);

+        sib.limit(triCount * 3);

+        ib.updateData(sib);

+

+        m.updateCounts();

+

+        // go for each quad and append it to the buffers

+        if (pos != null) {

+            for (int i = 0; i < pageQuads.size(); i++) {

+                LetterQuad fq = pageQuads.get(i);

+                fq.storeToArrays(pos, tc, idx, color, i);

+                fpb.put(pos);

+                ftb.put(tc);

+                sib.put(idx);

+                bcb.put(color);

+            }

+        } else {

+            for (int i = 0; i < pageQuads.size(); i++) {

+                LetterQuad fq = pageQuads.get(i);

+                fq.appendPositions(fpb);

+                fq.appendTexCoords(ftb);

+                fq.appendIndices(sib, i);

+                fq.appendColors(bcb);

+            }

+        }

+

+        fpb.rewind();

+        ftb.rewind();

+        sib.rewind();

+        bcb.rewind();

+        

+        updateModelBound();

+    }

+}

diff --git a/engine/src/core/com/jme3/font/ColorTags.java b/engine/src/core/com/jme3/font/ColorTags.java
new file mode 100644
index 0000000..01f15c3
--- /dev/null
+++ b/engine/src/core/com/jme3/font/ColorTags.java
@@ -0,0 +1,91 @@
+package com.jme3.font;

+

+import com.jme3.math.ColorRGBA;

+import java.util.LinkedList;

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+/**

+ * Contains the color information tagged in a text string

+ * Format: \#rgb#

+ *         \#rgba#

+ *         \#rrggbb#

+ *         \#rrggbbaa#

+ * @author YongHoon

+ */

+class ColorTags {

+    private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" +

+    		                                                    "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#");

+    private LinkedList<Range> colors = new LinkedList<Range>();

+    private String text;

+

+    ColorTags() { }

+

+    ColorTags(String seq) {

+        setText(seq);

+    }

+    

+    /**

+     * @return text without color tags

+     */

+    String getPlainText() {

+        return text;

+    }

+    

+    LinkedList<Range> getTags() {

+        return colors;

+    }

+

+    void setText(final String charSeq) {

+        colors.clear();

+        if (charSeq == null) {

+            return;

+        }

+        Matcher m = colorPattern.matcher(charSeq);

+        if (m.find()) {

+            StringBuilder builder = new StringBuilder(charSeq.length()-7);

+            int startIndex = 0;

+            do {

+                String colorStr = null;

+                for (int i = 1; i <= 4 && colorStr==null; i++) {

+                    colorStr = m.group(i);

+                }

+                builder.append(charSeq.subSequence(startIndex, m.start()));

+                Range range = new Range(builder.length(), colorStr);

+                startIndex = m.end();

+                colors.add(range);

+            } while (m.find());

+            builder.append(charSeq.subSequence(startIndex, charSeq.length()));

+            text = builder.toString();

+        } else {

+            text = charSeq;

+        }

+    }

+    

+    class Range {

+        int start;

+        ColorRGBA color;

+        Range(int start, String colorStr) {

+            this.start = start;

+            this.color = new ColorRGBA();

+            if (colorStr.length() >= 6) {

+                color.set(Integer.parseInt(colorStr.subSequence(0,2).toString(), 16) / 255f,

+                          Integer.parseInt(colorStr.subSequence(2,4).toString(), 16) / 255f,

+                          Integer.parseInt(colorStr.subSequence(4,6).toString(), 16) / 255f,

+                          1);

+                if (colorStr.length() == 8) {

+                    color.a = Integer.parseInt(colorStr.subSequence(6,8).toString(), 16) / 255f;

+                }

+            } else {

+                color.set(Integer.parseInt(Character.toString(colorStr.charAt(0)), 16) / 15f,

+                          Integer.parseInt(Character.toString(colorStr.charAt(1)), 16) / 15f,

+                          Integer.parseInt(Character.toString(colorStr.charAt(2)), 16) / 15f,

+                          1);

+                if (colorStr.length() == 4) {

+                    color.a = Integer.parseInt(Character.toString(colorStr.charAt(3)), 16) / 15f;

+                }

+            }

+            

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/font/Kerning.java b/engine/src/core/com/jme3/font/Kerning.java
new file mode 100644
index 0000000..e4be815
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Kerning.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2010 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.font;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+
+/**
+ * Represents kerning information for a character.
+ */
+public class Kerning implements Savable {
+
+    private int second;
+    private int amount;
+
+    public int getSecond() {
+        return second;
+    }
+
+    public void setSecond(int second) {
+        this.second = second;
+    }
+
+    public int getAmount() {
+        return amount;
+    }
+
+    public void setAmount(int amount) {
+        this.amount = amount;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(second, "second", 0);
+        oc.write(amount, "amount", 0);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        second = ic.readInt("second", 0);
+        amount = ic.readInt("amount", 0);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/LetterQuad.java b/engine/src/core/com/jme3/font/LetterQuad.java
new file mode 100644
index 0000000..09f0e24
--- /dev/null
+++ b/engine/src/core/com/jme3/font/LetterQuad.java
@@ -0,0 +1,496 @@
+package com.jme3.font;

+

+import com.jme3.math.ColorRGBA;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.nio.ShortBuffer;

+

+/**

+ * LetterQuad contains the position, color, uv texture information for a character in text.

+ * @author YongHoon

+ */

+class LetterQuad {

+    private static final Rectangle UNBOUNDED = new Rectangle(0, 0, Float.MAX_VALUE, Float.MAX_VALUE);

+    private static final float LINE_DIR = -1;

+

+    private final BitmapFont font;

+    private final char c;

+    private final int index;

+    private int style;

+

+    private BitmapCharacter bitmapChar = null;

+    private float x0 = Integer.MIN_VALUE;

+    private float y0 = Integer.MIN_VALUE;

+    private float width = Integer.MIN_VALUE;

+    private float height = Integer.MIN_VALUE;

+    private float xAdvance = 0;

+    private float u0;

+    private float v0;

+    private float u1;

+    private float v1;

+    private float lineY;

+    private boolean eol;

+

+    private LetterQuad previous;

+    private LetterQuad next;

+    private int colorInt = 0xFFFFFFFF;

+

+    private boolean rightToLeft;

+    private float alignX;

+    private float alignY;

+    private float sizeScale = 1;

+    

+    /**

+     * create head / tail

+     * @param font

+     * @param rightToLeft

+     */

+    protected LetterQuad(BitmapFont font, boolean rightToLeft) {

+        this.font = font;

+        this.c = Character.MIN_VALUE;

+        this.rightToLeft = rightToLeft;

+        this.index = -1;

+        setBitmapChar(null);

+    }

+

+    /**

+     * create letter and append to previous LetterQuad

+     * 

+     * @param c

+     * @param prev previous character

+     */

+    protected LetterQuad(char c, LetterQuad prev) {

+        this.font = prev.font;

+        this.rightToLeft = prev.rightToLeft;

+        this.c = c;

+        this.index = prev.index+1;

+        this.eol = isLineFeed();

+        setBitmapChar(c);

+        prev.insert(this);

+    }

+    

+    LetterQuad addNextCharacter(char c) {

+        LetterQuad n = new LetterQuad(c, this);

+        return n;

+    }

+

+    BitmapCharacter getBitmapChar() {

+        return bitmapChar;

+    }

+    

+    char getChar() {

+        return c;

+    }

+    

+    int getIndex() {

+        return index;

+    }

+

+    private Rectangle getBound(StringBlock block) {

+        if (block.getTextBox() != null) {

+            return block.getTextBox();

+        }

+        return UNBOUNDED;

+    }

+

+    LetterQuad getPrevious() {

+        return previous;

+    }

+

+    LetterQuad getNext() {

+        return next;

+    }

+

+    public float getU0() {

+        return u0;

+    }

+

+    float getU1() {

+        return u1;

+    }

+

+    float getV0() {

+        return v0;

+    }

+

+    float getV1() {

+        return v1;

+    }

+    

+    boolean isInvalid() {

+        return x0 == Integer.MIN_VALUE;

+    }

+

+    boolean isInvalid(StringBlock block) {

+        return isInvalid(block, 0);

+    }

+    

+    boolean isInvalid(StringBlock block, float gap) {

+        if (isHead() || isTail())

+            return false;

+        if (x0 == Integer.MIN_VALUE || y0 == Integer.MIN_VALUE) {

+            return true;

+        }

+        Rectangle bound = block.getTextBox();

+        if (bound == null) {

+            return false;

+        }

+        return x0 > 0 && bound.x+bound.width-gap < getX1();

+    }

+    

+    float getX0() {

+        return x0;

+    }

+

+    float getX1() {

+        return x0+width;

+    }

+    

+    float getNextX() {

+        return x0+xAdvance;

+    }

+    

+    float getNextLine() {

+        return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;

+    }

+

+    float getY0() {

+        return y0;

+    }

+

+    float getY1() {

+        return y0-height;

+    }

+    

+    float getWidth() {

+        return width;

+    }

+    

+    float getHeight() {

+        return height;

+    }

+

+    void insert(LetterQuad ins) {

+        LetterQuad n = next;

+        next = ins;

+        ins.next = n;

+        ins.previous = this;

+        n.previous = ins;

+    }

+    

+    void invalidate() {

+        eol = isLineFeed();

+        setBitmapChar(font.getCharSet().getCharacter(c, style));

+    }

+

+    boolean isTail() {

+        return next == null;

+    }

+

+    boolean isHead() {

+        return previous == null;

+    }

+

+    /**

+     * @return next letter

+     */

+    LetterQuad remove() {

+        this.previous.next = next;

+        this.next.previous = previous;

+        return next;

+    }

+

+    void setPrevious(LetterQuad before) {

+        this.previous = before;

+    }

+    

+    void setStyle(int style) {

+        this.style = style;

+        invalidate();

+    }

+    

+    void setColor(ColorRGBA color) {

+        this.colorInt = color.asIntRGBA();

+        invalidate();

+    }

+

+    void setBitmapChar(char c) {

+        BitmapCharacterSet charSet = font.getCharSet();

+        BitmapCharacter bm = charSet.getCharacter(c, style);

+        setBitmapChar(bm);

+    }

+    

+    void setBitmapChar(BitmapCharacter bitmapChar) {

+        x0 = Integer.MIN_VALUE;

+        y0 = Integer.MIN_VALUE;

+        width = Integer.MIN_VALUE;

+        height = Integer.MIN_VALUE;

+        alignX = 0;

+        alignY = 0;

+        

+        BitmapCharacterSet charSet = font.getCharSet();

+        this.bitmapChar = bitmapChar;

+        if (bitmapChar != null) {

+            u0 = (float) bitmapChar.getX() / charSet.getWidth();

+            v0 = (float) bitmapChar.getY() / charSet.getHeight();

+            u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth();

+            v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight();

+        } else {

+            u0 = 0;

+            v0 = 0;

+            u1 = 0;

+            v1 = 0;

+        }

+    }

+

+    void setNext(LetterQuad next) {

+        this.next = next;

+    }

+

+    void update(StringBlock block) {

+        final float[] tabs = block.getTabPosition();

+        final float tabWidth = block.getTabWidth();

+        final Rectangle bound = getBound(block);

+        sizeScale = block.getSize() / font.getCharSet().getRenderedSize();

+        lineY = computeLineY(block);

+

+        if (isHead()) {

+            x0 = getBound(block).x;

+            y0 = lineY;

+            width = 0;

+            height = 0;

+            xAdvance = 0;

+        } else if (isTab()) {

+            x0 = previous.getNextX();

+            width = tabWidth;

+            y0 = lineY;

+            height = 0;

+            if (tabs != null && x0 < tabs[tabs.length-1]) {

+                for (int i = 0; i < tabs.length-1; i++) {

+                    if (x0 > tabs[i] && x0 < tabs[i+1]) {

+                        width = tabs[i+1] - x0;

+                    }

+                }

+            }

+            xAdvance = width;

+        } else if (bitmapChar == null) {

+            x0 = getPrevious().getX1();

+            y0 = lineY;

+            width = 0;

+            height = 0;

+            xAdvance = 0;

+        } else {

+            float xOffset = bitmapChar.getXOffset() * sizeScale;

+            float yOffset = bitmapChar.getYOffset() * sizeScale;

+            xAdvance = bitmapChar.getXAdvance() * sizeScale;

+            width = bitmapChar.getWidth() * sizeScale;

+            height = bitmapChar.getHeight() * sizeScale;

+            float incrScale = rightToLeft ? -1f : 1f;

+            float kernAmount = 0f;

+

+            if (previous.isHead() || previous.eol) {

+                x0 = bound.x;

+                

+                // The first letter quad will be drawn right at the first

+                // position... but it does not offset by the characters offset

+                // amount.  This means that we've potentially accumulated extra

+                // pixels and the next letter won't get drawn far enough unless

+                // we add this offset back into xAdvance.. by subtracting it.

+                // This is the same thing that's done below because we've

+                // technically baked the offset in just like below.  It doesn't

+                // look like it at first glance so I'm keeping it separate with

+                // this comment.

+                xAdvance -= xOffset * incrScale; 

+                

+            } else {

+                x0 = previous.getNextX() + xOffset * incrScale;

+                

+                // Since x0 will have offset baked into it then we

+                // need to counteract that in xAdvance.  This is better

+                // than removing it in getNextX() because we also need

+                // to take kerning into account below... which will also

+                // get baked in.

+                // Without this, getNextX() will return values too far to

+                // the left, for example.

+                xAdvance -= xOffset * incrScale; 

+            }

+            y0 = lineY + LINE_DIR*yOffset;

+

+            // Adjust for kerning

+            BitmapCharacter lastChar = previous.getBitmapChar();

+            if (lastChar != null && block.isKerning()) {

+                kernAmount = lastChar.getKerning(c) * sizeScale;

+                x0 += kernAmount * incrScale;

+                

+                // Need to unbake the kerning from xAdvance since it

+                // is baked into x0... see above.

+                //xAdvance -= kernAmount * incrScale;

+                // No, kerning is an inter-character spacing and _does_ affect

+                // all subsequent cursor positions. 

+            }

+        }

+        if (isEndOfLine()) {

+            xAdvance = bound.x-x0;

+        }

+    }

+    

+    /**

+     * add temporary linewrap indicator

+     */

+    void setEndOfLine() {

+        this.eol = true;

+    }

+    

+    boolean isEndOfLine() {

+        return eol;

+    }

+    

+    boolean isLineWrap() {

+        return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE;

+    }

+    

+    private float computeLineY(StringBlock block) {

+        if (isHead()) {

+            return getBound(block).y;

+        } else if (previous.eol) {

+            return previous.getNextLine();

+        } else {

+            return previous.lineY;

+        }

+    }

+

+    

+    boolean isLineStart() {

+        return x0 == 0 || (previous != null && previous.eol);

+    }

+    

+    boolean isBlank() {

+        return c == ' ' || isTab();

+    }

+    

+    public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){

+        float x = x0+alignX;

+        float y = y0-alignY;

+        float xpw = x+width;

+        float ymh = y-height;

+

+        pos[0] = x;   pos[1]  = y;   pos[2]  = 0;

+        pos[3] = x;   pos[4]  = ymh; pos[5]  = 0;

+        pos[6] = xpw; pos[7]  = ymh; pos[8]  = 0;

+        pos[9] = xpw; pos[10] = y;   pos[11] = 0;

+

+        float v0 = 1f - this.v0;

+        float v1 = 1f - this.v1;

+

+        tc[0] = u0; tc[1] = v0;

+        tc[2] = u0; tc[3] = v1;

+        tc[4] = u1; tc[5] = v1;

+        tc[6] = u1; tc[7] = v0;

+

+        colors[3] = (byte) (colorInt & 0xff);

+        colors[2] = (byte) ((colorInt >> 8) & 0xff);

+        colors[1] = (byte) ((colorInt >> 16) & 0xff);

+        colors[0] = (byte) ((colorInt >> 24) & 0xff);

+        System.arraycopy(colors, 0, colors, 4,  4);

+        System.arraycopy(colors, 0, colors, 8,  4);

+        System.arraycopy(colors, 0, colors, 12, 4);

+

+        short i0 = (short) (quadIdx * 4);

+        short i1 = (short) (i0 + 1);

+        short i2 = (short) (i0 + 2);

+        short i3 = (short) (i0 + 3);

+

+        idx[0] = i0; idx[1] = i1; idx[2] = i2;

+        idx[3] = i0; idx[4] = i2; idx[5] = i3;

+    }

+    

+    public void appendPositions(FloatBuffer fb){

+        float sx = x0+alignX;

+        float sy = y0-alignY;

+        float ex = sx+width;

+        float ey = sy-height;

+        // NOTE: subtracting the height here

+        // because OGL's Ortho origin is at lower-left

+        fb.put(sx).put(sy).put(0f);

+        fb.put(sx).put(ey).put(0f);

+        fb.put(ex).put(ey).put(0f);

+        fb.put(ex).put(sy).put(0f);

+    }

+

+    public void appendPositions(ShortBuffer sb){

+        final float x1 = getX1();

+        final float y1 = getY1();

+        short x = (short) x0;

+        short y = (short) y0;

+        short xpw = (short) (x1);

+        short ymh = (short) (y1);

+        

+        sb.put(x).put(y).put((short)0);

+        sb.put(x).put(ymh).put((short)0);

+        sb.put(xpw).put(ymh).put((short)0);

+        sb.put(xpw).put(y).put((short)0);

+    }

+

+    public void appendTexCoords(FloatBuffer fb){

+        // flip coords to be compatible with OGL

+        float v0 = 1 - this.v0;

+        float v1 = 1 - this.v1;

+

+        // upper left

+        fb.put(u0).put(v0);

+        // lower left

+        fb.put(u0).put(v1);

+        // lower right

+        fb.put(u1).put(v1);

+        // upper right

+        fb.put(u1).put(v0);

+    }

+

+    public void appendColors(ByteBuffer bb){

+        bb.putInt(colorInt);

+        bb.putInt(colorInt);

+        bb.putInt(colorInt);

+        bb.putInt(colorInt);

+    }

+

+    public void appendIndices(ShortBuffer sb, int quadIndex){

+        // each quad has 4 indices

+        short v0 = (short) (quadIndex * 4);

+        short v1 = (short) (v0 + 1);

+        short v2 = (short) (v0 + 2);

+        short v3 = (short) (v0 + 3);

+

+        sb.put(v0).put(v1).put(v2);

+        sb.put(v0).put(v2).put(v3);

+//        sb.put(new short[]{ v0, v1, v2,

+//                            v0, v2, v3 });

+    }

+

+

+    @Override

+    public String toString() {

+        return String.valueOf(c);

+    }

+

+    void setAlignment(float alignX, float alignY) {

+        this.alignX = alignX;

+        this.alignY = alignY;

+    }

+

+    float getAlignX() {

+        return alignX;

+    }

+

+    float getAlignY() {

+        return alignY;

+    }

+

+    boolean isLineFeed() {

+        return c == '\n';

+    }

+    

+    boolean isTab() {

+        return c == '\t';

+    }

+    

+}

diff --git a/engine/src/core/com/jme3/font/Letters.java b/engine/src/core/com/jme3/font/Letters.java
new file mode 100644
index 0000000..dbb9ef9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Letters.java
@@ -0,0 +1,331 @@
+package com.jme3.font;

+

+import com.jme3.font.BitmapFont.Align;

+import com.jme3.font.BitmapFont.VAlign;

+import com.jme3.font.ColorTags.Range;

+import com.jme3.math.ColorRGBA;

+import java.util.LinkedList;

+

+/**

+ * Manage and align LetterQuads

+ * @author YongHoon

+ */

+class Letters {

+    private final LetterQuad head;

+    private final LetterQuad tail;

+    private final BitmapFont font;

+    private LetterQuad current;

+    private StringBlock block;

+    private float totalWidth;

+    private float totalHeight;

+    private ColorTags colorTags = new ColorTags();

+    private ColorRGBA baseColor = null;

+    

+    Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {

+        final String text = bound.getText();

+        this.block = bound;

+        this.font = font;

+        head = new LetterQuad(font, rightToLeft);

+        tail = new LetterQuad(font, rightToLeft);

+        setText(text);

+    }

+

+    void setText(final String text) {

+        colorTags.setText(text);

+        String plainText = colorTags.getPlainText();

+

+        head.setNext(tail);

+        tail.setPrevious(head);

+        current = head;

+        if (text != null && plainText.length() > 0) {

+            LetterQuad l = head;

+            for (int i = 0; i < plainText.length(); i++) {

+                l = l.addNextCharacter(plainText.charAt(i));

+                if (baseColor != null) {

+                    // Give the letter a default color if

+                    // one has been provided.

+                    l.setColor( baseColor );

+                }                

+            }

+        }

+        

+        LinkedList<Range> ranges = colorTags.getTags();

+        if (!ranges.isEmpty()) {

+            for (int i = 0; i < ranges.size()-1; i++) {

+                Range start = ranges.get(i);

+                Range end = ranges.get(i+1);

+                setColor(start.start, end.start, start.color);

+            }

+            Range end = ranges.getLast();

+            setColor(end.start, plainText.length(), end.color);

+        }

+        

+        invalidate();

+    }

+

+    LetterQuad getHead() {

+        return head;

+    }

+

+    LetterQuad getTail() {

+        return tail;

+    }

+    

+    void update() {

+        LetterQuad l = head;

+        int lineCount = 1;

+        BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());

+        float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;

+ 

+        while (!l.isTail()) {

+            if (l.isInvalid()) {

+                l.update(block);

+                

+                if (l.isInvalid(block)) {

+                    switch (block.getLineWrapMode()) {

+                    case Character:

+                        lineWrap(l);

+                        lineCount++;

+                        break;

+                    case Word:

+                        if (!l.isBlank()) {

+                            // search last blank character before this word

+                            LetterQuad blank = l;

+                            while (!blank.isBlank()) {

+                                if (blank.isLineStart() || blank.isHead()) {

+                                    lineWrap(l);

+                                    lineCount++;

+                                    blank = null;

+                                    break;

+                                }

+                                blank = blank.getPrevious();

+                            }

+                            if (blank != null) {

+                                blank.setEndOfLine();

+                                lineCount++;

+                                while (blank != l) {

+                                    blank = blank.getNext();

+                                    blank.invalidate();

+                                    blank.update(block);

+                                }

+                            }

+                        }

+                        break;

+                    case NoWrap:

+                        // search last blank character before this word

+                        LetterQuad cursor = l.getPrevious();

+                        while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {

+                            cursor = cursor.getPrevious();

+                        }

+                        cursor.setBitmapChar(ellipsis);

+                        cursor.update(block);

+                        cursor = cursor.getNext();

+                        while (!cursor.isTail() && !cursor.isLineFeed()) {

+                            cursor.setBitmapChar(null);

+                            cursor.update(block);

+                            cursor = cursor.getNext();

+                        }

+                        break;

+                    }

+                }

+            } else if (current.isInvalid(block)) {

+                invalidate(current);

+            }

+            if (l.isEndOfLine()) {

+                lineCount++;

+            }

+            l = l.getNext();

+        }

+        

+        align();

+        block.setLineCount(lineCount);

+        rewind();

+    }

+    

+    private void align() {

+        final Align alignment = block.getAlignment();

+        final VAlign valignment = block.getVerticalAlignment();

+        if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top))

+            return;

+        LetterQuad cursor = tail.getPrevious();

+        cursor.setEndOfLine();

+        final float width = block.getTextBox().width;

+        final float height = block.getTextBox().height;

+        float lineWidth = 0;

+        float gapX = 0;

+        float gapY = 0;

+        validateSize();

+        if (totalHeight < height) { // align vertically only for no overflow

+            switch (valignment) {

+            case Top:

+                gapY = 0;

+                break;

+            case Center:

+                gapY = (height-totalHeight)*0.5f;

+                break;

+            case Bottom:

+                gapY = height-totalHeight;

+                break;

+            }

+        }

+        while (!cursor.isHead()) {

+            if (cursor.isEndOfLine()) {

+                lineWidth = cursor.getX1()-block.getTextBox().x;

+                if (alignment == Align.Center) {

+                    gapX = (width-lineWidth)/2;

+                } else if (alignment == Align.Right) {

+                    gapX = width-lineWidth;

+                } else {

+                    gapX = 0;

+                }

+            }

+            cursor.setAlignment(gapX, gapY);

+            cursor = cursor.getPrevious();

+        }

+    }

+

+    private void lineWrap(LetterQuad l) {

+        if (l.isHead() || l.isBlank())

+            return;

+        l.getPrevious().setEndOfLine();

+        l.invalidate();

+        l.update(block); // TODO: update from l

+    }

+    

+    float getCharacterX0() {

+        return current.getX0();

+    }

+

+    float getCharacterY0() {

+        return current.getY0();

+    }

+    

+    float getCharacterX1() {

+        return current.getX1();

+    }

+    

+    float getCharacterY1() {

+        return current.getY1();

+    }

+    

+    float getCharacterAlignX() {

+        return current.getAlignX();

+    }

+    

+    float getCharacterAlignY() {

+        return current.getAlignY();

+    }

+    

+    float getCharacterWidth() {

+        return current.getWidth();

+    }

+    

+    float getCharacterHeight() {

+        return current.getHeight();

+    }

+    

+    public boolean nextCharacter() {

+        if (current.isTail())

+            return false;

+        current = current.getNext();

+        return true;

+    }

+    

+    public int getCharacterSetPage() {

+        return current.getBitmapChar().getPage();

+    }

+    

+    public LetterQuad getQuad() {

+        return current;

+    }

+    

+    public void rewind() {

+        current = head;

+    }

+    

+    public void invalidate() {

+        invalidate(head);

+    }

+    

+    public void invalidate(LetterQuad cursor) {

+        totalWidth = -1;

+        totalHeight = -1;

+

+        while (!cursor.isTail() && !cursor.isInvalid()) {

+            cursor.invalidate();

+            cursor = cursor.getNext();

+        }

+    }

+    

+    float getScale() {

+        return block.getSize() / font.getCharSet().getRenderedSize();

+    }

+

+    public boolean isPrintable() {

+        return current.getBitmapChar() != null;

+    }

+    

+    float getTotalWidth() {

+        validateSize();

+        return totalWidth;

+    }

+

+    float getTotalHeight() {

+        validateSize();

+        return totalHeight;

+    }

+    

+    void validateSize() {

+        if (totalWidth < 0) {

+            LetterQuad l = head;

+            while (!l.isTail()) {

+                totalWidth = Math.max(totalWidth, l.getX1());

+                l = l.getNext();

+                totalHeight = Math.max(totalHeight, -l.getY1());

+            }

+        }

+    }

+

+    /**

+     * @param start start index to set style. inclusive.

+     * @param end   end index to set style. EXCLUSIVE.

+     * @param style

+     */

+    void setStyle(int start, int end, int style) {

+        LetterQuad cursor = head.getNext();

+        while (!cursor.isTail()) {

+            if (cursor.getIndex() >= start && cursor.getIndex() < end) {

+                cursor.setStyle(style);

+            }

+            cursor = cursor.getNext();

+        }

+    }

+

+    /**

+     * Sets the base color for all new letter quads and resets

+     * the color of existing letter quads.

+     */

+    void setColor( ColorRGBA color ) {

+        baseColor = color;

+        setColor( 0, block.getText().length(), color );

+    }

+

+    ColorRGBA getBaseColor() {

+        return baseColor;

+    }

+

+    /**

+     * @param start start index to set style. inclusive.

+     * @param end   end index to set style. EXCLUSIVE.

+     * @param color

+     */

+    void setColor(int start, int end, ColorRGBA color) {

+        LetterQuad cursor = head.getNext();

+        while (!cursor.isTail()) {

+            if (cursor.getIndex() >= start && cursor.getIndex() < end) {

+                cursor.setColor(color);

+            }

+            cursor = cursor.getNext();

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/LineWrapMode.java b/engine/src/core/com/jme3/font/LineWrapMode.java
new file mode 100644
index 0000000..3c77d6e
--- /dev/null
+++ b/engine/src/core/com/jme3/font/LineWrapMode.java
@@ -0,0 +1,11 @@
+package com.jme3.font;
+
+/**
+ * Line-wrap type for BitmapText
+ * @author YongHoon
+ */
+public enum LineWrapMode {
+    NoWrap,
+    Character,
+    Word
+}
diff --git a/engine/src/core/com/jme3/font/Rectangle.java b/engine/src/core/com/jme3/font/Rectangle.java
new file mode 100644
index 0000000..33b47c5
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Rectangle.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 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.font;
+
+/**
+ * Defines a rectangle that can constrict a text paragraph.
+ * @author dhdd
+ */
+public class Rectangle implements Cloneable {
+
+    public final float x,  y,  width,  height;
+
+    /**
+     *
+     * @param x the X value of the upper left corner of the rectangle
+     * @param y the Y value of the upper left corner of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public Rectangle(float x, float y, float width, float height) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public Rectangle clone(){
+        try {
+            return (Rectangle) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/StringBlock.java b/engine/src/core/com/jme3/font/StringBlock.java
new file mode 100644
index 0000000..9f6f055
--- /dev/null
+++ b/engine/src/core/com/jme3/font/StringBlock.java
@@ -0,0 +1,200 @@
+/*

+ * Copyright (c) 2009-2010 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.font;

+

+import com.jme3.font.BitmapFont.Align;

+import com.jme3.font.BitmapFont.VAlign;

+import com.jme3.math.ColorRGBA;

+

+/**

+ * Defines a String that is to be drawn in one block that can be constrained by a {@link Rectangle}. Also holds

+ * formatting information for the StringBlock

+ *

+ * @author dhdd

+ */

+class StringBlock implements Cloneable {

+

+    private String text;

+    private Rectangle textBox;

+    private Align alignment = Align.Left;

+    private VAlign valignment = VAlign.Top;

+    private float size;

+    private ColorRGBA color = new ColorRGBA(ColorRGBA.White);

+    private boolean kerning;

+    private int lineCount;

+    private LineWrapMode wrapType = LineWrapMode.Word;

+    private float[] tabPos;

+    private float tabWidth = 50;

+    private char ellipsisChar = 0x2026;

+

+    /**

+     *

+     * @param text the text that the StringBlock will hold

+     * @param textBox the rectangle that constrains the text

+     * @param alignment the initial alignment of the text

+     * @param size the size in pixels (vertical size of a single line)

+     * @param color the initial color of the text

+     * @param kerning

+     */

+    StringBlock(String text, Rectangle textBox, BitmapFont.Align alignment, float size, ColorRGBA color,

+            boolean kerning) {

+        this.text = text;

+        this.textBox = textBox;

+        this.alignment = alignment;

+        this.size = size;

+        this.color.set(color);

+        this.kerning = kerning;

+    }

+

+    StringBlock(){

+        this.text = "";

+        this.textBox = null;

+        this.alignment = Align.Left;

+        this.size = 100;

+        this.color.set(ColorRGBA.White);

+        this.kerning = true;

+    }

+

+    @Override

+    public StringBlock clone(){

+        try {

+            StringBlock clone = (StringBlock) super.clone();

+            clone.color = color.clone();

+            if (textBox != null)

+                clone.textBox = textBox.clone();

+            return clone;

+        } catch (CloneNotSupportedException ex) {

+            throw new AssertionError();

+        }

+    }

+

+    String getText() {

+        return text;

+    }

+

+    void setText(String text){

+        this.text = text == null ? "" : text;

+    }

+

+    Rectangle getTextBox() {

+        return textBox;

+    }

+

+    void setTextBox(Rectangle textBox) {

+        this.textBox = textBox;

+    }

+

+    BitmapFont.Align getAlignment() {

+        return alignment;

+    }

+    

+    BitmapFont.VAlign getVerticalAlignment() {

+        return valignment;

+    }

+

+    void setAlignment(BitmapFont.Align alignment) {

+        this.alignment = alignment;

+    }

+    

+    void setVerticalAlignment(BitmapFont.VAlign alignment) {

+        this.valignment = alignment;

+    }

+

+    float getSize() {

+        return size;

+    }

+

+    void setSize(float size) {

+        this.size = size;

+    }

+

+    ColorRGBA getColor() {

+        return color;

+    }

+

+    void setColor(ColorRGBA color) {

+        this.color.set(color);

+    }

+

+    boolean isKerning() {

+        return kerning;

+    }

+

+    void setKerning(boolean kerning) {

+        this.kerning = kerning;

+    }

+

+    int getLineCount() {

+        return lineCount;

+    }

+

+    void setLineCount(int lineCount) {

+        this.lineCount = lineCount;

+    }

+    

+    LineWrapMode getLineWrapMode() {

+        return wrapType;

+    }

+    

+    /**

+     * available only when bounding is set. <code>setBox()</code> method call is needed in advance. 

+     * @param wrap true when word need not be split at the end of the line.

+     */

+    void setLineWrapMode(LineWrapMode wrap) {

+        this.wrapType = wrap;

+    }

+    

+    void setTabWidth(float tabWidth) {

+        this.tabWidth = tabWidth;

+    }

+

+    void setTabPosition(float[] tabs) {

+        this.tabPos = tabs;

+    }

+    

+    float getTabWidth() {

+        return tabWidth;

+    }

+    

+    float[] getTabPosition() {

+        return tabPos;

+    }

+

+    void setEllipsisChar(char c) {

+        this.ellipsisChar = c;

+    }

+

+    int getEllipsisChar() {

+        return ellipsisChar;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/ChaseCamera.java b/engine/src/core/com/jme3/input/ChaseCamera.java
new file mode 100644
index 0000000..b3b649d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/ChaseCamera.java
@@ -0,0 +1,875 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.input;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.input.controls.*;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.ViewPort;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.control.Control;

+import java.io.IOException;

+

+/**

+ * A camera that follows a spatial and can turn around it by dragging the mouse

+ * @author nehon

+ */

+public class ChaseCamera implements ActionListener, AnalogListener, Control {

+

+    protected Spatial target = null;

+    protected float minVerticalRotation = 0.00f;

+    protected float maxVerticalRotation = FastMath.PI / 2;

+    protected float minDistance = 1.0f;

+    protected float maxDistance = 40.0f;

+    protected float distance = 20;

+    protected float zoomSpeed = 2f;

+    protected float rotationSpeed = 1.0f;

+    protected float rotation = 0;

+    protected float trailingRotationInertia = 0.05f;

+    protected float zoomSensitivity = 5f;

+    protected float rotationSensitivity = 5f;

+    protected float chasingSensitivity = 5f;

+    protected float trailingSensitivity = 0.5f;

+    protected float vRotation = FastMath.PI / 6;

+    protected boolean smoothMotion = false;

+    protected boolean trailingEnabled = true;

+    protected float rotationLerpFactor = 0;

+    protected float trailingLerpFactor = 0;

+    protected boolean rotating = false;

+    protected boolean vRotating = false;

+    protected float targetRotation = rotation;

+    protected InputManager inputManager;

+    protected Vector3f initialUpVec;

+    protected float targetVRotation = vRotation;

+    protected float vRotationLerpFactor = 0;

+    protected float targetDistance = distance;

+    protected float distanceLerpFactor = 0;

+    protected boolean zooming = false;

+    protected boolean trailing = false;

+    protected boolean chasing = false;

+    protected boolean canRotate;

+    protected float offsetDistance = 0.002f;

+    protected Vector3f prevPos;

+    protected boolean targetMoves = false;

+    protected boolean enabled = true;

+    protected Camera cam = null;

+    protected final Vector3f targetDir = new Vector3f();

+    protected float previousTargetRotation;

+    protected final Vector3f pos = new Vector3f();

+    protected Vector3f targetLocation = new Vector3f(0, 0, 0);

+    protected boolean dragToRotate = true;

+    protected Vector3f lookAtOffset = new Vector3f(0, 0, 0);

+    protected boolean leftClickRotate = true;

+    protected boolean rightClickRotate = true;

+    protected Vector3f temp = new Vector3f(0, 0, 0);

+    protected boolean invertYaxis = false;

+    protected boolean invertXaxis = false;

+    protected final static String ChaseCamDown = "ChaseCamDown";

+    protected final static String ChaseCamUp = "ChaseCamUp";

+    protected final static String ChaseCamZoomIn = "ChaseCamZoomIn";

+    protected final static String ChaseCamZoomOut = "ChaseCamZoomOut";

+    protected final static String ChaseCamMoveLeft = "ChaseCamMoveLeft";

+    protected final static String ChaseCamMoveRight = "ChaseCamMoveRight";

+    protected final static String ChaseCamToggleRotate = "ChaseCamToggleRotate";

+

+    /**

+     * Constructs the chase camera

+     * @param cam the application camera

+     * @param target the spatial to follow

+     */

+    public ChaseCamera(Camera cam, final Spatial target) {

+        this(cam);

+        target.addControl(this);

+    }

+

+    /**

+     * Constructs the chase camera

+     * if you use this constructor you have to attach the cam later to a spatial

+     * doing spatial.addControl(chaseCamera);

+     * @param cam the application camera

+     */

+    public ChaseCamera(Camera cam) {

+        this.cam = cam;

+        initialUpVec = cam.getUp().clone();

+    }

+

+    /**

+     * Constructs the chase camera, and registers inputs

+     * if you use this constructor you have to attach the cam later to a spatial

+     * doing spatial.addControl(chaseCamera);

+     * @param cam the application camera     

+     * @param inputManager the inputManager of the application to register inputs

+     */

+    public ChaseCamera(Camera cam, InputManager inputManager) {

+        this(cam);

+        registerWithInput(inputManager);

+    }

+

+    /**

+     * Constructs the chase camera, and registers inputs

+     * @param cam the application camera

+     * @param target the spatial to follow

+     * @param inputManager the inputManager of the application to register inputs

+     */

+    public ChaseCamera(Camera cam, final Spatial target, InputManager inputManager) {

+        this(cam, target);

+        registerWithInput(inputManager);

+    }

+

+    public void onAction(String name, boolean keyPressed, float tpf) {

+        if (dragToRotate) {

+            if (name.equals(ChaseCamToggleRotate) && enabled) {

+                if (keyPressed) {

+                    canRotate = true;

+                    inputManager.setCursorVisible(false);

+                } else {

+                    canRotate = false;

+                    inputManager.setCursorVisible(true);

+                }

+            }

+        }

+

+    }

+    private boolean zoomin;

+

+    public void onAnalog(String name, float value, float tpf) {

+        if (name.equals(ChaseCamMoveLeft)) {

+            rotateCamera(-value);

+        } else if (name.equals(ChaseCamMoveRight)) {

+            rotateCamera(value);

+        } else if (name.equals(ChaseCamUp)) {

+            vRotateCamera(value);

+        } else if (name.equals(ChaseCamDown)) {

+            vRotateCamera(-value);

+        } else if (name.equals(ChaseCamZoomIn)) {

+            zoomCamera(-value);

+            if (zoomin == false) {

+                distanceLerpFactor = 0;

+            }

+            zoomin = true;

+        } else if (name.equals(ChaseCamZoomOut)) {

+            zoomCamera(+value);

+            if (zoomin == true) {

+                distanceLerpFactor = 0;

+            }

+            zoomin = false;

+        }

+    }

+

+    /**

+     * Registers inputs with the input manager

+     * @param inputManager

+     */

+    public final void registerWithInput(InputManager inputManager) {

+

+        String[] inputs = {ChaseCamToggleRotate,

+            ChaseCamDown,

+            ChaseCamUp,

+            ChaseCamMoveLeft,

+            ChaseCamMoveRight,

+            ChaseCamZoomIn,

+            ChaseCamZoomOut};

+

+        this.inputManager = inputManager;

+        if (!invertYaxis) {

+            inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));

+            inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));

+        } else {

+            inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));

+            inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));

+        }

+        inputManager.addMapping(ChaseCamZoomIn, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));

+        inputManager.addMapping(ChaseCamZoomOut, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));

+        if(!invertXaxis){

+            inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));

+            inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));

+        }else{

+            inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));

+            inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));

+        }

+        inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+        inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));

+

+        inputManager.addListener(this, inputs);

+    }

+

+    /**

+     * Sets custom triggers for toggleing the rotation of the cam

+     * deafult are

+     * new MouseButtonTrigger(MouseInput.BUTTON_LEFT)  left mouse button

+     * new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)  right mouse button

+     * @param triggers

+     */

+    public void setToggleRotationTrigger(Trigger... triggers) {

+        inputManager.deleteMapping(ChaseCamToggleRotate);

+        inputManager.addMapping(ChaseCamToggleRotate, triggers);

+        inputManager.addListener(this, ChaseCamToggleRotate);

+    }

+

+    /**

+     * Sets custom triggers for zomming in the cam

+     * default is

+     * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)  mouse wheel up

+     * @param triggers

+     */

+    public void setZoomInTrigger(Trigger... triggers) {

+        inputManager.deleteMapping(ChaseCamZoomIn);

+        inputManager.addMapping(ChaseCamZoomIn, triggers);

+        inputManager.addListener(this, ChaseCamZoomIn);

+    }

+

+    /**

+     * Sets custom triggers for zomming out the cam

+     * default is

+     * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)  mouse wheel down

+     * @param triggers

+     */

+    public void setZoomOutTrigger(Trigger... triggers) {

+        inputManager.deleteMapping(ChaseCamZoomOut);

+        inputManager.addMapping(ChaseCamZoomOut, triggers);

+        inputManager.addListener(this, ChaseCamZoomOut);

+    }

+

+    private void computePosition() {

+

+        float hDistance = (distance) * FastMath.sin((FastMath.PI / 2) - vRotation);

+        pos.set(hDistance * FastMath.cos(rotation), (distance) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));

+        pos.addLocal(target.getWorldTranslation());

+    }

+

+    //rotate the camera around the target on the horizontal plane

+    private void rotateCamera(float value) {

+        if (!canRotate || !enabled) {

+            return;

+        }

+        rotating = true;

+        targetRotation += value * rotationSpeed;

+

+

+    }

+

+    //move the camera toward or away the target

+    private void zoomCamera(float value) {

+        if (!enabled) {

+            return;

+        }

+

+        zooming = true;

+        targetDistance += value * zoomSpeed;

+        if (targetDistance > maxDistance) {

+            targetDistance = maxDistance;

+        }

+        if (targetDistance < minDistance) {

+            targetDistance = minDistance;

+        }

+        if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {

+            targetVRotation = minVerticalRotation;

+        }

+    }

+

+    //rotate the camera around the target on the vertical plane

+    private void vRotateCamera(float value) {

+        if (!canRotate || !enabled) {

+            return;

+        }

+        vRotating = true;

+        targetVRotation += value * rotationSpeed;

+        if (targetVRotation > maxVerticalRotation) {

+            targetVRotation = maxVerticalRotation;

+        }

+        if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {

+            targetVRotation = minVerticalRotation;

+        }

+    }

+

+    /**

+     * Updates the camera, should only be called internally

+     */

+    protected void updateCamera(float tpf) {

+        if (enabled) {

+            targetLocation.set(target.getWorldTranslation()).addLocal(lookAtOffset);

+            if (smoothMotion) {

+

+                //computation of target direction

+                targetDir.set(targetLocation).subtractLocal(prevPos);

+                float dist = targetDir.length();

+

+                //Low pass filtering on the target postition to avoid shaking when physics are enabled.

+                if (offsetDistance < dist) {

+                    //target moves, start chasing.

+                    chasing = true;

+                    //target moves, start trailing if it has to.

+                    if (trailingEnabled) {

+                        trailing = true;

+                    }

+                    //target moves...

+                    targetMoves = true;

+                } else {

+                    //if target was moving, we compute a slight offset in rotation to avoid a rought stop of the cam

+                    //We do not if the player is rotationg the cam

+                    if (targetMoves && !canRotate) {

+                        if (targetRotation - rotation > trailingRotationInertia) {

+                            targetRotation = rotation + trailingRotationInertia;

+                        } else if (targetRotation - rotation < -trailingRotationInertia) {

+                            targetRotation = rotation - trailingRotationInertia;

+                        }

+                    }

+                    //Target stops

+                    targetMoves = false;

+                }

+

+                //the user is rotating the cam by dragging the mouse

+                if (canRotate) {

+                    //reseting the trailing lerp factor

+                    trailingLerpFactor = 0;

+                    //stop trailing user has the control                  

+                    trailing = false;

+                }

+

+

+                if (trailingEnabled && trailing) {

+                    if (targetMoves) {

+                        //computation if the inverted direction of the target

+                        Vector3f a = targetDir.negate().normalizeLocal();

+                        //the x unit vector

+                        Vector3f b = Vector3f.UNIT_X;

+                        //2d is good enough

+                        a.y = 0;

+                        //computation of the rotation angle between the x axis and the trail

+                        if (targetDir.z > 0) {

+                            targetRotation = FastMath.TWO_PI - FastMath.acos(a.dot(b));

+                        } else {

+                            targetRotation = FastMath.acos(a.dot(b));

+                        }

+                        if (targetRotation - rotation > FastMath.PI || targetRotation - rotation < -FastMath.PI) {

+                            targetRotation -= FastMath.TWO_PI;

+                        }

+

+                        //if there is an important change in the direction while trailing reset of the lerp factor to avoid jumpy movements

+                        if (targetRotation != previousTargetRotation && FastMath.abs(targetRotation - previousTargetRotation) > FastMath.PI / 8) {

+                            trailingLerpFactor = 0;

+                        }

+                        previousTargetRotation = targetRotation;

+                    }

+                    //computing lerp factor

+                    trailingLerpFactor = Math.min(trailingLerpFactor + tpf * tpf * trailingSensitivity, 1);

+                    //computing rotation by linear interpolation

+                    rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation);

+

+                    //if the rotation is near the target rotation we're good, that's over

+                    if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {

+                        trailing = false;

+                        trailingLerpFactor = 0;

+                    }

+                }

+

+                //linear interpolation of the distance while chasing

+                if (chasing) {

+                    distance = temp.set(targetLocation).subtractLocal(cam.getLocation()).length();

+                    distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * chasingSensitivity * 0.05f), 1);

+                    distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);

+                    if (targetDistance + 0.01f >= distance && targetDistance - 0.01f <= distance) {

+                        distanceLerpFactor = 0;

+                        chasing = false;

+                    }

+                }

+

+                //linear interpolation of the distance while zooming

+                if (zooming) {

+                    distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * zoomSensitivity), 1);

+                    distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);

+                    if (targetDistance + 0.1f >= distance && targetDistance - 0.1f <= distance) {

+                        zooming = false;

+                        distanceLerpFactor = 0;

+                    }

+                }

+

+                //linear interpolation of the rotation while rotating horizontally

+                if (rotating) {

+                    rotationLerpFactor = Math.min(rotationLerpFactor + tpf * tpf * rotationSensitivity, 1);

+                    rotation = FastMath.interpolateLinear(rotationLerpFactor, rotation, targetRotation);

+                    if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {

+                        rotating = false;

+                        rotationLerpFactor = 0;

+                    }

+                }

+

+                //linear interpolation of the rotation while rotating vertically

+                if (vRotating) {

+                    vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1);

+                    vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation);

+                    if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) {

+                        vRotating = false;

+                        vRotationLerpFactor = 0;

+                    }

+                }

+                //computing the position

+                computePosition();

+                //setting the position at last

+                cam.setLocation(pos.addLocal(lookAtOffset));

+            } else {

+                //easy no smooth motion

+                vRotation = targetVRotation;

+                rotation = targetRotation;

+                distance = targetDistance;

+                computePosition();

+                cam.setLocation(pos.addLocal(lookAtOffset));

+            }

+            //keeping track on the previous position of the target

+            prevPos.set(targetLocation);

+

+            //the cam looks at the target            

+            cam.lookAt(targetLocation, initialUpVec);

+

+        }

+    }

+

+    /**

+     * Return the enabled/disabled state of the camera

+     * @return true if the camera is enabled

+     */

+    public boolean isEnabled() {

+        return enabled;

+    }

+

+    /**

+     * Enable or disable the camera

+     * @param enabled true to enable

+     */

+    public void setEnabled(boolean enabled) {

+        this.enabled = enabled;

+        if (!enabled) {

+            canRotate = false; // reset this flag in-case it was on before

+        }

+    }

+

+    /**

+     * Returns the max zoom distance of the camera (default is 40)

+     * @return maxDistance

+     */

+    public float getMaxDistance() {

+        return maxDistance;

+    }

+

+    /**

+     * Sets the max zoom distance of the camera (default is 40)

+     * @param maxDistance

+     */

+    public void setMaxDistance(float maxDistance) {

+        this.maxDistance = maxDistance;

+    }

+

+    /**

+     * Returns the min zoom distance of the camera (default is 1)

+     * @return minDistance

+     */

+    public float getMinDistance() {

+        return minDistance;

+    }

+

+    /**

+     * Sets the min zoom distance of the camera (default is 1)

+     * @return minDistance

+     */

+    public void setMinDistance(float minDistance) {

+        this.minDistance = minDistance;

+    }

+

+    /**

+     * clone this camera for a spatial

+     * @param spatial

+     * @return

+     */

+    public Control cloneForSpatial(Spatial spatial) {

+        ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);

+        cc.setMaxDistance(getMaxDistance());

+        cc.setMinDistance(getMinDistance());

+        return cc;

+    }

+

+    /**

+     * Sets the spacial for the camera control, should only be used internally

+     * @param spatial

+     */

+    public void setSpatial(Spatial spatial) {

+        target = spatial;

+        if (spatial == null) {

+            return;

+        }

+        computePosition();

+        prevPos = new Vector3f(target.getWorldTranslation());

+        cam.setLocation(pos);

+    }

+

+    /**

+     * update the camera control, should only be used internally

+     * @param tpf

+     */

+    public void update(float tpf) {

+        updateCamera(tpf);

+    }

+

+    /**

+     * renders the camera control, should only be used internally

+     * @param rm

+     * @param vp

+     */

+    public void render(RenderManager rm, ViewPort vp) {

+        //nothing to render

+    }

+

+    /**

+     * Write the camera

+     * @param ex the exporter

+     * @throws IOException

+     */

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule capsule = ex.getCapsule(this);

+        capsule.write(maxDistance, "maxDistance", 40);

+        capsule.write(minDistance, "minDistance", 1);

+    }

+

+    /**

+     * Read the camera

+     * @param im

+     * @throws IOException

+     */

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        maxDistance = ic.readFloat("maxDistance", 40);

+        minDistance = ic.readFloat("minDistance", 1);

+    }

+

+    /**

+     * returns the maximal vertical rotation angle of the camera around the target

+     * @return

+     */

+    public float getMaxVerticalRotation() {

+        return maxVerticalRotation;

+    }

+

+    /**

+     * sets the maximal vertical rotation angle of the camera around the target default is Pi/2;

+     * @param maxVerticalRotation

+     */

+    public void setMaxVerticalRotation(float maxVerticalRotation) {

+        this.maxVerticalRotation = maxVerticalRotation;

+    }

+

+    /**

+     * returns the minimal vertical rotation angle of the camera around the target

+     * @return

+     */

+    public float getMinVerticalRotation() {

+        return minVerticalRotation;

+    }

+

+    /**

+     * sets the minimal vertical rotation angle of the camera around the target default is 0;

+     * @param minHeight

+     */

+    public void setMinVerticalRotation(float minHeight) {

+        this.minVerticalRotation = minHeight;

+    }

+

+    /**

+     * returns true is smmoth motion is enabled for this chase camera

+     * @return

+     */

+    public boolean isSmoothMotion() {

+        return smoothMotion;

+    }

+

+    /**

+     * Enables smooth motion for this chase camera

+     * @param smoothMotion

+     */

+    public void setSmoothMotion(boolean smoothMotion) {

+        this.smoothMotion = smoothMotion;

+    }

+

+    /**

+     * returns the chasing sensitivity

+     * @return

+     */

+    public float getChasingSensitivity() {

+        return chasingSensitivity;

+    }

+

+    /**

+     * 

+     * Sets the chasing sensitivity, the lower the value the slower the camera will follow the target when it moves

+     * default is 5

+     * Only has an effect if smoothMotion is set to true and trailing is enabled

+     * @param chasingSensitivity

+     */

+    public void setChasingSensitivity(float chasingSensitivity) {

+        this.chasingSensitivity = chasingSensitivity;

+    }

+

+    /**

+     * Returns the rotation sensitivity

+     * @return

+     */

+    public float getRotationSensitivity() {

+        return rotationSensitivity;

+    }

+

+    /**

+     * Sets the rotation sensitivity, the lower the value the slower the camera will rotates around the target when draging with the mouse

+     * default is 5, values over 5 should have no effect.

+     * If you want a significant slow down try values below 1.

+     * Only has an effect if smoothMotion is set to true 

+     * @param rotationSensitivity

+     */

+    public void setRotationSensitivity(float rotationSensitivity) {

+        this.rotationSensitivity = rotationSensitivity;

+    }

+

+    /**

+     * returns true if the trailing is enabled

+     * @return

+     */

+    public boolean isTrailingEnabled() {

+        return trailingEnabled;

+    }

+

+    /**

+     * Enable the camera trailing : The camera smoothly go in the targets trail when it moves.

+     * Only has an effect if smoothMotion is set to true 

+     * @param trailingEnabled

+     */

+    public void setTrailingEnabled(boolean trailingEnabled) {

+        this.trailingEnabled = trailingEnabled;

+    }

+

+    /**

+     * 

+     * returns the trailing rotation inertia

+     * @return

+     */

+    public float getTrailingRotationInertia() {

+        return trailingRotationInertia;

+    }

+

+    /**

+     * Sets the trailing rotation inertia : default is 0.1. This prevent the camera to roughtly stop when the target stops moving

+     * before the camera reached the trail position.

+     * Only has an effect if smoothMotion is set to true and trailing is enabled

+     * @param trailingRotationInertia

+     */

+    public void setTrailingRotationInertia(float trailingRotationInertia) {

+        this.trailingRotationInertia = trailingRotationInertia;

+    }

+

+    /**

+     * returns the trailing sensitivity

+     * @return

+     */

+    public float getTrailingSensitivity() {

+        return trailingSensitivity;

+    }

+

+    /**

+     * Only has an effect if smoothMotion is set to true and trailing is enabled

+     * Sets the trailing sensitivity, the lower the value, the slower the camera will go in the target trail when it moves.

+     * default is 0.5;

+     * @param trailingSensitivity

+     */

+    public void setTrailingSensitivity(float trailingSensitivity) {

+        this.trailingSensitivity = trailingSensitivity;

+    }

+

+    /**

+     * returns the zoom sensitivity

+     * @return

+     */

+    public float getZoomSensitivity() {

+        return zoomSensitivity;

+    }

+

+    /**

+     * Sets the zoom sensitivity, the lower the value, the slower the camera will zoom in and out.

+     * default is 5.

+     * @param zoomSensitivity

+     */

+    public void setZoomSensitivity(float zoomSensitivity) {

+        this.zoomSensitivity = zoomSensitivity;

+    }

+

+    /**

+     * Sets the default distance at start of applicaiton

+     * @param defaultDistance

+     */

+    public void setDefaultDistance(float defaultDistance) {

+        distance = defaultDistance;

+        targetDistance = distance;

+    }

+

+    /**

+     * sets the default horizontal rotation of the camera at start of the application

+     * @param angle

+     */

+    public void setDefaultHorizontalRotation(float angle) {

+        rotation = angle;

+        targetRotation = angle;

+    }

+

+    /**

+     * sets the default vertical rotation of the camera at start of the application

+     * @param angle

+     */

+    public void setDefaultVerticalRotation(float angle) {

+        vRotation = angle;

+        targetVRotation = angle;

+    }

+

+    /**

+     * @return If drag to rotate feature is enabled.

+     *

+     * @see FlyByCamera#setDragToRotate(boolean)

+     */

+    public boolean isDragToRotate() {

+        return dragToRotate;

+    }

+

+    /**

+     * @param dragToRotate When true, the user must hold the mouse button

+     * and drag over the screen to rotate the camera, and the cursor is

+     * visible until dragged. Otherwise, the cursor is invisible at all times

+     * and holding the mouse button is not needed to rotate the camera.

+     * This feature is disabled by default.

+     */

+    public void setDragToRotate(boolean dragToRotate) {

+        this.dragToRotate = dragToRotate;

+        this.canRotate = !dragToRotate;

+        inputManager.setCursorVisible(dragToRotate);

+    }

+

+    /**

+     * return the current distance from the camera to the target

+     * @return

+     */

+    public float getDistanceToTarget() {

+        return distance;

+    }

+

+    /**

+     * returns the current horizontal rotation around the target in radians

+     * @return

+     */

+    public float getHorizontalRotation() {

+        return rotation;

+    }

+

+    /**

+     * returns the current vertical rotation around the target in radians.

+     * @return

+     */

+    public float getVerticalRotation() {

+        return vRotation;

+    }

+

+    /**

+     * returns the offset from the target's position where the camera looks at

+     * @return

+     */

+    public Vector3f getLookAtOffset() {

+        return lookAtOffset;

+    }

+

+    /**

+     * Sets the offset from the target's position where the camera looks at

+     * @param lookAtOffset

+     */

+    public void setLookAtOffset(Vector3f lookAtOffset) {

+        this.lookAtOffset = lookAtOffset;

+    }

+    

+    /**

+     * Sets the up vector of the camera used for the lookAt on the target

+     * @param up 

+     */

+    public void setUpVector(Vector3f up){

+        initialUpVec=up;

+    }

+    

+    /**

+     * Returns the up vector of the camera used for the lookAt on the target

+     * @return 

+     */

+    public Vector3f getUpVector(){

+        return initialUpVec;

+    }

+

+    /**

+     * invert the vertical axis movement of the mouse

+     * @param invertYaxis

+     */

+    public void setInvertVerticalAxis(boolean invertYaxis) {

+        this.invertYaxis = invertYaxis;

+        inputManager.deleteMapping(ChaseCamDown);

+        inputManager.deleteMapping(ChaseCamUp);

+        if (!invertYaxis) {

+            inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));

+            inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));

+        } else {

+            inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));

+            inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));

+        }

+        inputManager.addListener(this, ChaseCamDown, ChaseCamUp);

+    }

+

+    /**

+     * invert the Horizontal axis movement of the mouse

+     * @param invertXaxis

+     */

+    public void setInvertHorizontalAxis(boolean invertXaxis) {

+        this.invertXaxis = invertXaxis;

+        inputManager.deleteMapping(ChaseCamMoveLeft);

+        inputManager.deleteMapping(ChaseCamMoveRight);

+        if(!invertXaxis){

+            inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));

+            inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));

+        }else{

+            inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));

+            inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));

+        }

+        inputManager.addListener(this, ChaseCamMoveLeft, ChaseCamMoveRight);

+    }

+}

diff --git a/engine/src/core/com/jme3/input/FlyByCamera.java b/engine/src/core/com/jme3/input/FlyByCamera.java
new file mode 100644
index 0000000..7b439ef
--- /dev/null
+++ b/engine/src/core/com/jme3/input/FlyByCamera.java
@@ -0,0 +1,364 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.input;

+

+import com.jme3.collision.MotionAllowedListener;

+import com.jme3.input.controls.*;

+import com.jme3.math.FastMath;

+import com.jme3.math.Matrix3f;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+

+/**

+ * A first person view camera controller.

+ * After creation, you must register the camera controller with the

+ * dispatcher using #registerWithDispatcher().

+ *

+ * Controls:

+ *  - Move the mouse to rotate the camera

+ *  - Mouse wheel for zooming in or out

+ *  - WASD keys for moving forward/backward and strafing

+ *  - QZ keys raise or lower the camera

+ */

+public class FlyByCamera implements AnalogListener, ActionListener {

+

+    private static String[] mappings = new String[]{

+            "FLYCAM_Left",

+            "FLYCAM_Right",

+            "FLYCAM_Up",

+            "FLYCAM_Down",

+

+            "FLYCAM_StrafeLeft",

+            "FLYCAM_StrafeRight",

+            "FLYCAM_Forward",

+            "FLYCAM_Backward",

+

+            "FLYCAM_ZoomIn",

+            "FLYCAM_ZoomOut",

+            "FLYCAM_RotateDrag",

+

+            "FLYCAM_Rise",

+            "FLYCAM_Lower"

+        };

+

+    protected Camera cam;

+    protected Vector3f initialUpVec;

+    protected float rotationSpeed = 1f;

+    protected float moveSpeed = 3f;

+    protected MotionAllowedListener motionAllowed = null;

+    protected boolean enabled = true;

+    protected boolean dragToRotate = false;

+    protected boolean canRotate = false;

+    protected InputManager inputManager;

+    

+    /**

+     * Creates a new FlyByCamera to control the given Camera object.

+     * @param cam

+     */

+    public FlyByCamera(Camera cam){

+        this.cam = cam;

+        initialUpVec = cam.getUp().clone();

+    }

+

+    /**

+     * Sets the up vector that should be used for the camera.

+     * @param upVec

+     */

+    public void setUpVector(Vector3f upVec) {

+       initialUpVec.set(upVec);

+    }

+

+    public void setMotionAllowedListener(MotionAllowedListener listener){

+        this.motionAllowed = listener;

+    }

+

+    /**

+     * Sets the move speed. The speed is given in world units per second.

+     * @param moveSpeed

+     */

+    public void setMoveSpeed(float moveSpeed){

+        this.moveSpeed = moveSpeed;

+    }

+

+    /**

+     * Sets the rotation speed.

+     * @param rotationSpeed

+     */

+    public void setRotationSpeed(float rotationSpeed){

+        this.rotationSpeed = rotationSpeed;

+    }

+

+    /**

+     * @param enable If false, the camera will ignore input.

+     */

+    public void setEnabled(boolean enable){

+        if (enabled && !enable){

+            if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){

+                inputManager.setCursorVisible(true);

+            }

+        }

+        enabled = enable;

+    }

+

+    /**

+     * @return If enabled

+     * @see FlyByCamera#setEnabled(boolean)

+     */

+    public boolean isEnabled(){

+        return enabled;

+    }

+

+    /**

+     * @return If drag to rotate feature is enabled.

+     *

+     * @see FlyByCamera#setDragToRotate(boolean) 

+     */

+    public boolean isDragToRotate() {

+        return dragToRotate;

+    }

+

+    /**

+     * Set if drag to rotate mode is enabled.

+     * 

+     * When true, the user must hold the mouse button

+     * and drag over the screen to rotate the camera, and the cursor is

+     * visible until dragged. Otherwise, the cursor is invisible at all times

+     * and holding the mouse button is not needed to rotate the camera.

+     * This feature is disabled by default.

+     * 

+     * @param dragToRotate True if drag to rotate mode is enabled.

+     */

+    public void setDragToRotate(boolean dragToRotate) {

+        this.dragToRotate = dragToRotate;

+        if (inputManager != null) {

+            inputManager.setCursorVisible(dragToRotate);

+        }

+    }

+

+    /**

+     * Registers the FlyByCamera to receive input events from the provided

+     * Dispatcher.

+     * @param inputManager

+     */

+    public void registerWithInput(InputManager inputManager){

+        this.inputManager = inputManager;

+        

+        // both mouse and button - rotation of cam

+        inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true),

+                                               new KeyTrigger(KeyInput.KEY_LEFT));

+

+        inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false),

+                                                new KeyTrigger(KeyInput.KEY_RIGHT));

+

+        inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false),

+                                             new KeyTrigger(KeyInput.KEY_UP));

+

+        inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true),

+                                               new KeyTrigger(KeyInput.KEY_DOWN));

+

+        // mouse only - zoom in/out with wheel, and rotate drag

+        inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));

+        inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));

+        inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+

+        // keyboard only WASD for movement and WZ for rise/lower height

+        inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A));

+        inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D));

+        inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W));

+        inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S));

+        inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q));

+        inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z));

+

+        inputManager.addListener(this, mappings);

+        inputManager.setCursorVisible(dragToRotate || !isEnabled());

+

+        Joystick[] joysticks = inputManager.getJoysticks();

+        if (joysticks != null && joysticks.length > 0){

+            Joystick joystick = joysticks[0];

+            joystick.assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft", JoyInput.AXIS_POV_X);

+            joystick.assignAxis("FLYCAM_Forward", "FLYCAM_Backward", JoyInput.AXIS_POV_Y);

+            joystick.assignAxis("FLYCAM_Right", "FLYCAM_Left", joystick.getXAxisIndex());

+            joystick.assignAxis("FLYCAM_Down", "FLYCAM_Up", joystick.getYAxisIndex());

+        }

+    }

+

+    /**

+     * Registers the FlyByCamera to receive input events from the provided

+     * Dispatcher.

+     * @param inputManager

+     */

+    public void unregisterInput(){

+    

+        if (inputManager == null) {

+            return;

+        }

+    

+        for (String s : mappings) {

+            if (inputManager.hasMapping(s)) {

+                inputManager.deleteMapping( s );

+            }

+        }

+

+        inputManager.removeListener(this);

+        inputManager.setCursorVisible(!dragToRotate);

+

+        Joystick[] joysticks = inputManager.getJoysticks();

+        if (joysticks != null && joysticks.length > 0){

+            Joystick joystick = joysticks[0];

+            

+            // No way to unassing axis

+        }

+    }

+

+    protected void rotateCamera(float value, Vector3f axis){

+        if (dragToRotate){

+            if (canRotate){

+//                value = -value;

+            }else{

+                return;

+            }

+        }

+

+        Matrix3f mat = new Matrix3f();

+        mat.fromAngleNormalAxis(rotationSpeed * value, axis);

+

+        Vector3f up = cam.getUp();

+        Vector3f left = cam.getLeft();

+        Vector3f dir = cam.getDirection();

+

+        mat.mult(up, up);

+        mat.mult(left, left);

+        mat.mult(dir, dir);

+

+        Quaternion q = new Quaternion();

+        q.fromAxes(left, up, dir);

+        q.normalize();

+

+        cam.setAxes(q);

+    }

+

+    protected void zoomCamera(float value){

+        // derive fovY value

+        float h = cam.getFrustumTop();

+        float w = cam.getFrustumRight();

+        float aspect = w / h;

+

+        float near = cam.getFrustumNear();

+

+        float fovY = FastMath.atan(h / near)

+                  / (FastMath.DEG_TO_RAD * .5f);

+        fovY += value * 0.1f;

+

+        h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near;

+        w = h * aspect;

+

+        cam.setFrustumTop(h);

+        cam.setFrustumBottom(-h);

+        cam.setFrustumLeft(-w);

+        cam.setFrustumRight(w);

+    }

+

+    protected void riseCamera(float value){

+        Vector3f vel = new Vector3f(0, value * moveSpeed, 0);

+        Vector3f pos = cam.getLocation().clone();

+

+        if (motionAllowed != null)

+            motionAllowed.checkMotionAllowed(pos, vel);

+        else

+            pos.addLocal(vel);

+

+        cam.setLocation(pos);

+    }

+

+    protected void moveCamera(float value, boolean sideways){

+        Vector3f vel = new Vector3f();

+        Vector3f pos = cam.getLocation().clone();

+

+        if (sideways){

+            cam.getLeft(vel);

+        }else{

+            cam.getDirection(vel);

+        }

+        vel.multLocal(value * moveSpeed);

+

+        if (motionAllowed != null)

+            motionAllowed.checkMotionAllowed(pos, vel);

+        else

+            pos.addLocal(vel);

+

+        cam.setLocation(pos);

+    }

+

+    public void onAnalog(String name, float value, float tpf) {

+        if (!enabled)

+            return;

+

+        if (name.equals("FLYCAM_Left")){

+            rotateCamera(value, initialUpVec);

+        }else if (name.equals("FLYCAM_Right")){

+            rotateCamera(-value, initialUpVec);

+        }else if (name.equals("FLYCAM_Up")){

+            rotateCamera(-value, cam.getLeft());

+        }else if (name.equals("FLYCAM_Down")){

+            rotateCamera(value, cam.getLeft());

+        }else if (name.equals("FLYCAM_Forward")){

+            moveCamera(value, false);

+        }else if (name.equals("FLYCAM_Backward")){

+            moveCamera(-value, false);

+        }else if (name.equals("FLYCAM_StrafeLeft")){

+            moveCamera(value, true);

+        }else if (name.equals("FLYCAM_StrafeRight")){

+            moveCamera(-value, true);

+        }else if (name.equals("FLYCAM_Rise")){

+            riseCamera(value);

+        }else if (name.equals("FLYCAM_Lower")){

+            riseCamera(-value);

+        }else if (name.equals("FLYCAM_ZoomIn")){

+            zoomCamera(value);

+        }else if (name.equals("FLYCAM_ZoomOut")){

+            zoomCamera(-value);

+        }

+    }

+

+    public void onAction(String name, boolean value, float tpf) {

+        if (!enabled)

+            return;

+

+        if (name.equals("FLYCAM_RotateDrag") && dragToRotate){

+            canRotate = value;

+            inputManager.setCursorVisible(!value);

+        }

+    }

+

+}

diff --git a/engine/src/core/com/jme3/input/Input.java b/engine/src/core/com/jme3/input/Input.java
new file mode 100644
index 0000000..1cece68
--- /dev/null
+++ b/engine/src/core/com/jme3/input/Input.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * Abstract interface for an input device.
+ * 
+ * @see MouseInput
+ * @see KeyInput
+ * @see JoyInput
+ */
+public interface Input {
+
+    /**
+     * Initializes the native side to listen into events from the device.
+     */
+    public void initialize();
+
+    /**
+     * Queries the device for input. All events should be sent to the
+     * RawInputListener set with setInputListener.
+     *
+     * @see #setInputListener(com.jme3.input.RawInputListener)
+     */
+    public void update();
+
+    /**
+     * Ceases listening to events from the device.
+     */
+    public void destroy();
+
+    /**
+     * @return True if the device has been initialized and not destroyed.
+     * @see #initialize()
+     * @see #destroy() 
+     */
+    public boolean isInitialized();
+
+    /**
+     * Sets the input listener to receive events from this device. The
+     * appropriate events should be dispatched through the callbacks
+     * in RawInputListener.
+     * @param listener
+     */
+    public void setInputListener(RawInputListener listener);
+
+    /**
+     * @return The current absolute time as nanoseconds. This time is expected
+     * to be relative to the time given in InputEvents time property.
+     */
+    public long getInputTimeNanos();
+}
diff --git a/engine/src/core/com/jme3/input/InputManager.java b/engine/src/core/com/jme3/input/InputManager.java
new file mode 100644
index 0000000..23f2988
--- /dev/null
+++ b/engine/src/core/com/jme3/input/InputManager.java
@@ -0,0 +1,881 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input;
+
+import com.jme3.app.Application;
+import com.jme3.input.controls.*;
+import com.jme3.input.event.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The <code>InputManager</code> is responsible for converting input events
+ * received from the Key, Mouse and Joy Input implementations into an
+ * abstract, input device independent representation that user code can use.
+ * <p>
+ * By default an <code>InputManager</code> is included with every Application instance for use
+ * in user code to query input, unless the Application is created as headless
+ * or with input explicitly disabled.
+ * <p>
+ * The input manager has two concepts, a {@link Trigger} and a mapping.
+ * A trigger represents a specific input trigger, such as a key button, 
+ * or a mouse axis. A mapping represents a link onto one or several triggers, 
+ * when the appropriate trigger is activated (e.g. a key is pressed), the 
+ * mapping will be invoked. Any listeners registered to receive an event
+ * from the mapping will have an event raised.
+ * <p>
+ * There are two types of events that {@link InputListener input listeners}
+ * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action}
+ * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog}
+ * events. 
+ * <p>
+ * <code>onAction</code> events are raised when the specific input
+ * activates or deactivates. For a digital input such as key press, the <code>onAction()</code>
+ * event will be raised with the <code>isPressed</code> argument equal to true,
+ * when the key is released, <code>onAction</code> is called again but this time
+ * with the <code>isPressed</code> argument set to false.
+ * For analog inputs, the <code>onAction</code> method will be called any time
+ * the input is non-zero, however an exception to this is for joystick axis inputs,
+ * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}.
+ * <p>
+ * <code>onAnalog</code> events are raised every frame while the input is activated.
+ * For digital inputs, every frame that the input is active will cause the
+ * <code>onAnalog</code> method to be called, the argument <code>value</code>
+ * argument will equal to the frame's time per frame (TPF) value but only
+ * for digital inputs. For analog inputs however, the <code>value</code> argument
+ * will equal the actual analog value.
+ */
+public class InputManager implements RawInputListener {
+
+    private static final Logger logger = Logger.getLogger(InputManager.class.getName());
+    private final KeyInput keys;
+    private final MouseInput mouse;
+    private final JoyInput joystick;
+    private final TouchInput touch;
+    private float frameTPF;
+    private long lastLastUpdateTime = 0;
+    private long lastUpdateTime = 0;
+    private long frameDelta = 0;
+    private long firstTime = 0;
+    private boolean eventsPermitted = false;
+    private boolean mouseVisible = true;
+    private boolean safeMode = false;
+    private float axisDeadZone = 0.05f;
+    private Vector2f cursorPos = new Vector2f();
+    private Joystick[] joysticks;
+    private final IntMap<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>();
+    private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>();
+    private final IntMap<Long> pressedButtons = new IntMap<Long>();
+    private final IntMap<Float> axisValues = new IntMap<Float>();
+    private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>();
+    private RawInputListener[] rawListenerArray = null;
+    private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();
+
+    private static class Mapping {
+
+        private final String name;
+        private final ArrayList<Integer> triggers = new ArrayList<Integer>();
+        private final ArrayList<InputListener> listeners = new ArrayList<InputListener>();
+
+        public Mapping(String name) {
+            this.name = name;
+        }
+    }
+
+    /**
+     * Initializes the InputManager.
+     * 
+     * <p>This should only be called internally in {@link Application}.
+     *
+     * @param mouse
+     * @param keys
+     * @param joystick
+     * @param touch
+     * @throws IllegalArgumentException If either mouseInput or keyInput are null.
+     */
+    public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) {
+        if (keys == null || mouse == null) {
+            throw new NullPointerException("Mouse or keyboard cannot be null");
+        }
+
+        this.keys = keys;
+        this.mouse = mouse;
+        this.joystick = joystick;
+        this.touch = touch;
+
+        keys.setInputListener(this);
+        mouse.setInputListener(this);
+        if (joystick != null) {
+            joystick.setInputListener(this);
+            joysticks = joystick.loadJoysticks(this);
+        }
+        if (touch != null) {
+            touch.setInputListener(this);
+        }
+
+        firstTime = keys.getInputTimeNanos();
+    }
+
+    private void invokeActions(int hash, boolean pressed) {
+        ArrayList<Mapping> maps = bindings.get(hash);
+        if (maps == null) {
+            return;
+        }
+
+        int size = maps.size();
+        for (int i = size - 1; i >= 0; i--) {
+            Mapping mapping = maps.get(i);
+            ArrayList<InputListener> listeners = mapping.listeners;
+            int listenerSize = listeners.size();
+            for (int j = listenerSize - 1; j >= 0; j--) {
+                InputListener listener = listeners.get(j);
+                if (listener instanceof ActionListener) {
+                    ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF);
+                }
+            }
+        }
+    }
+
+    private float computeAnalogValue(long timeDelta) {
+        if (safeMode || frameDelta == 0) {
+            return 1f;
+        } else {
+            return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1);
+        }
+    }
+
+    private void invokeTimedActions(int hash, long time, boolean pressed) {
+        if (!bindings.containsKey(hash)) {
+            return;
+        }
+
+        if (pressed) {
+            pressedButtons.put(hash, time);
+        } else {
+            Long pressTimeObj = pressedButtons.remove(hash);
+            if (pressTimeObj == null) {
+                return; // under certain circumstances it can be null, ignore
+            }                        // the event then.
+
+            long pressTime = pressTimeObj;
+            long lastUpdate = lastLastUpdateTime;
+            long releaseTime = time;
+            long timeDelta = releaseTime - Math.max(pressTime, lastUpdate);
+
+            if (timeDelta > 0) {
+                invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
+            }
+        }
+    }
+
+    private void invokeUpdateActions() {
+        for (Entry<Long> pressedButton : pressedButtons) {
+            int hash = pressedButton.getKey();
+
+            long pressTime = pressedButton.getValue();
+            long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime);
+
+            if (timeDelta > 0) {
+                invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
+            }
+        }
+
+        for (Entry<Float> axisValue : axisValues) {
+            int hash = axisValue.getKey();
+            float value = axisValue.getValue();
+            invokeAnalogs(hash, value * frameTPF, true);
+        }
+    }
+
+    private void invokeAnalogs(int hash, float value, boolean isAxis) {
+        ArrayList<Mapping> maps = bindings.get(hash);
+        if (maps == null) {
+            return;
+        }
+
+        if (!isAxis) {
+            value *= frameTPF;
+        }
+
+        int size = maps.size();
+        for (int i = size - 1; i >= 0; i--) {
+            Mapping mapping = maps.get(i);
+            ArrayList<InputListener> listeners = mapping.listeners;
+            int listenerSize = listeners.size();
+            for (int j = listenerSize - 1; j >= 0; j--) {
+                InputListener listener = listeners.get(j);
+                if (listener instanceof AnalogListener) {
+                    // NOTE: multiply by TPF for any button bindings
+                    ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
+                }
+            }
+        }
+    }
+
+    private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) {
+        if (value < axisDeadZone) {
+            invokeAnalogs(hash, value, !applyTpf);
+            return;
+        }
+
+        ArrayList<Mapping> maps = bindings.get(hash);
+        if (maps == null) {
+            return;
+        }
+
+        boolean valueChanged = !axisValues.containsKey(hash);
+        if (applyTpf) {
+            value *= frameTPF;
+        }
+
+        int size = maps.size();
+        for (int i = size - 1; i >= 0; i--) {
+            Mapping mapping = maps.get(i);
+            ArrayList<InputListener> listeners = mapping.listeners;
+            int listenerSize = listeners.size();
+            for (int j = listenerSize - 1; j >= 0; j--) {
+                InputListener listener = listeners.get(j);
+
+                if (listener instanceof ActionListener && valueChanged) {
+                    ((ActionListener) listener).onAction(mapping.name, true, frameTPF);
+                }
+
+                if (listener instanceof AnalogListener) {
+                    ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void beginInput() {
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void endInput() {
+    }
+
+    private void onJoyAxisEventQueued(JoyAxisEvent evt) {
+//        for (int i = 0; i < rawListeners.size(); i++){
+//            rawListeners.get(i).onJoyAxisEvent(evt);
+//        }
+
+        int joyId = evt.getJoyIndex();
+        int axis = evt.getAxisIndex();
+        float value = evt.getValue();
+        if (value < axisDeadZone && value > -axisDeadZone) {
+            int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+            int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+
+            Float val1 = axisValues.get(hash1);
+            Float val2 = axisValues.get(hash2);
+
+            if (val1 != null && val1.floatValue() > axisDeadZone) {
+                invokeActions(hash1, false);
+            }
+            if (val2 != null && val2.floatValue() > axisDeadZone) {
+                invokeActions(hash2, false);
+            }
+
+            axisValues.remove(hash1);
+            axisValues.remove(hash2);
+
+        } else if (value < 0) {
+            int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+            int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+            invokeAnalogsAndActions(hash, -value, true);
+            axisValues.put(hash, -value);
+            axisValues.remove(otherHash);
+        } else {
+            int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+            int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+            invokeAnalogsAndActions(hash, value, true);
+            axisValues.put(hash, value);
+            axisValues.remove(otherHash);
+        }
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void onJoyAxisEvent(JoyAxisEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
+        }
+
+        inputQueue.add(evt);
+    }
+
+    private void onJoyButtonEventQueued(JoyButtonEvent evt) {
+//        for (int i = 0; i < rawListeners.size(); i++){
+//            rawListeners.get(i).onJoyButtonEvent(evt);
+//        }
+
+        int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex());
+        invokeActions(hash, evt.isPressed());
+        invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void onJoyButtonEvent(JoyButtonEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
+        }
+
+        inputQueue.add(evt);
+    }
+
+    private void onMouseMotionEventQueued(MouseMotionEvent evt) {
+//        for (int i = 0; i < rawListeners.size(); i++){
+//            rawListeners.get(i).onMouseMotionEvent(evt);
+//        }
+
+        if (evt.getDX() != 0) {
+            float val = Math.abs(evt.getDX()) / 1024f;
+            invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false);
+        }
+        if (evt.getDY() != 0) {
+            float val = Math.abs(evt.getDY()) / 1024f;
+            invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false);
+        }
+        if (evt.getDeltaWheel() != 0) {
+            float val = Math.abs(evt.getDeltaWheel()) / 100f;
+            invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false);
+        }
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void onMouseMotionEvent(MouseMotionEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
+        }
+
+        cursorPos.set(evt.getX(), evt.getY());
+        inputQueue.add(evt);
+    }
+
+    private void onMouseButtonEventQueued(MouseButtonEvent evt) {
+        int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex());
+        invokeActions(hash, evt.isPressed());
+        invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void onMouseButtonEvent(MouseButtonEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
+        }
+        //updating cursor pos on click, so that non android touch events can properly update cursor position.
+        cursorPos.set(evt.getX(), evt.getY());
+        inputQueue.add(evt);
+    }
+
+    private void onKeyEventQueued(KeyInputEvent evt) {
+        if (evt.isRepeating()) {
+            return; // repeat events not used for bindings
+        }
+        
+        int hash = KeyTrigger.keyHash(evt.getKeyCode());
+        invokeActions(hash, evt.isPressed());
+        invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+    }
+
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    public void onKeyEvent(KeyInputEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time.");
+        }
+
+        inputQueue.add(evt);
+    }
+
+    /**
+     * Set the deadzone for joystick axes.
+     * 
+     * <p>{@link ActionListener#onAction(java.lang.String, boolean, float) }
+     * events will only be raised if the joystick axis value is greater than
+     * the <code>deadZone</code>.
+     * 
+     * @param deadZone the deadzone for joystick axes. 
+     */
+    public void setAxisDeadZone(float deadZone) {
+        this.axisDeadZone = deadZone;
+    }
+
+    /**
+     * Returns the deadzone for joystick axes.
+     * 
+     * @return the deadzone for joystick axes.
+     */
+    public float getAxisDeadZone() {
+        return axisDeadZone;
+    }
+    
+    /**
+     * Adds a new listener to receive events on the given mappings.
+     * 
+     * <p>The given InputListener will be registered to receive events
+     * on the specified mapping names. When a mapping raises an event, the
+     * listener will have its appropriate method invoked, either
+     * {@link ActionListener#onAction(java.lang.String, boolean, float) }
+     * or {@link AnalogListener#onAnalog(java.lang.String, float, float) }
+     * depending on which interface the <code>listener</code> implements. 
+     * If the listener implements both interfaces, then it will receive the
+     * appropriate event for each method.
+     * 
+     * @param listener The listener to register to receive input events.
+     * @param mappingNames The mapping names which the listener will receive
+     * events from.
+     * 
+     * @see InputManager#removeListener(com.jme3.input.controls.InputListener) 
+     */
+    public void addListener(InputListener listener, String... mappingNames) {
+        for (String mappingName : mappingNames) {
+            Mapping mapping = mappings.get(mappingName);
+            if (mapping == null) {
+                mapping = new Mapping(mappingName);
+                mappings.put(mappingName, mapping);
+            }
+            if (!mapping.listeners.contains(listener)) {
+                mapping.listeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from receiving events.
+     * 
+     * <p>This will unregister the listener from any mappings that it
+     * was previously registered with via 
+     * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }.
+     * 
+     * @param listener The listener to unregister.
+     * 
+     * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) 
+     */
+    public void removeListener(InputListener listener) {
+        for (Mapping mapping : mappings.values()) {
+            mapping.listeners.remove(listener);
+        }
+    }
+
+    /**
+     * Create a new mapping to the given triggers.
+     * 
+     * <p>
+     * The given mapping will be assigned to the given triggers, when
+     * any of the triggers given raise an event, the listeners
+     * registered to the mappings will receive appropriate events.
+     * 
+     * @param mappingName The mapping name to assign.
+     * @param triggers The triggers to which the mapping is to be registered.
+     * 
+     * @see InputManager#deleteMapping(java.lang.String) 
+     */
+    public void addMapping(String mappingName, Trigger... triggers) {
+        Mapping mapping = mappings.get(mappingName);
+        if (mapping == null) {
+            mapping = new Mapping(mappingName);
+            mappings.put(mappingName, mapping);
+        }
+
+        for (Trigger trigger : triggers) {
+            int hash = trigger.triggerHashCode();
+            ArrayList<Mapping> names = bindings.get(hash);
+            if (names == null) {
+                names = new ArrayList<Mapping>();
+                bindings.put(hash, names);
+            }
+            if (!names.contains(mapping)) {
+                names.add(mapping);
+                mapping.triggers.add(hash);
+            } else {
+                logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName);
+            }
+        }
+    }
+
+    /**
+     * Returns true if this InputManager has a mapping registered
+     * for the given mappingName.
+     *
+     * @param mappingName The mapping name to check.
+     *
+     * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) 
+     * @see InputManager#deleteMapping(java.lang.String) 
+     */ 
+    public boolean hasMapping(String mappingName) {
+        return mappings.containsKey(mappingName);
+    }
+    
+    /**
+     * Deletes a mapping from receiving trigger events.
+     * 
+     * <p>
+     * The given mapping will no longer be assigned to receive trigger
+     * events.
+     * 
+     * @param mappingName The mapping name to unregister.
+     * 
+     * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) 
+     */
+    public void deleteMapping(String mappingName) {
+        Mapping mapping = mappings.remove(mappingName);
+        if (mapping == null) {
+            throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
+        }
+
+        ArrayList<Integer> triggers = mapping.triggers;
+        for (int i = triggers.size() - 1; i >= 0; i--) {
+            int hash = triggers.get(i);
+            ArrayList<Mapping> maps = bindings.get(hash);
+            maps.remove(mapping);
+        }
+    }
+
+    /**
+     * Deletes a specific trigger registered to a mapping.
+     * 
+     * <p>
+     * The given mapping will no longer receive events raised by the 
+     * trigger.
+     * 
+     * @param mappingName The mapping name to cease receiving events from the 
+     * trigger.
+     * @param trigger The trigger to no longer invoke events on the mapping.
+     */
+    public void deleteTrigger(String mappingName, Trigger trigger) {
+        Mapping mapping = mappings.get(mappingName);
+        if (mapping == null) {
+            throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
+        }
+
+        ArrayList<Mapping> maps = bindings.get(trigger.triggerHashCode());
+        maps.remove(mapping);
+
+    }
+
+    /**
+     * Clears all the input mappings from this InputManager. 
+     * Consequently, also clears all of the
+     * InputListeners as well.
+     */
+    public void clearMappings() {
+        mappings.clear();
+        bindings.clear();
+        reset();
+    }
+
+    /**
+     * Do not use.
+     * Called to reset pressed keys or buttons when focus is restored.
+     */
+    public void reset() {
+        pressedButtons.clear();
+        axisValues.clear();
+    }
+
+    /**
+     * Returns whether the mouse cursor is visible or not.
+     * 
+     * <p>By default the cursor is visible.
+     * 
+     * @return whether the mouse cursor is visible or not.
+     * 
+     * @see InputManager#setCursorVisible(boolean) 
+     */
+    public boolean isCursorVisible() {
+        return mouseVisible;
+    }
+
+    /**
+     * Set whether the mouse cursor should be visible or not.
+     * 
+     * @param visible whether the mouse cursor should be visible or not.
+     */
+    public void setCursorVisible(boolean visible) {
+        if (mouseVisible != visible) {
+            mouseVisible = visible;
+            mouse.setCursorVisible(mouseVisible);
+        }
+    }
+
+    /**
+     * Returns the current cursor position. The position is relative to the
+     * bottom-left of the screen and is in pixels.
+     * 
+     * @return the current cursor position
+     */
+    public Vector2f getCursorPosition() {
+        return cursorPos;
+    }
+
+    /**
+     * Returns an array of all joysticks installed on the system.
+     * 
+     * @return an array of all joysticks installed on the system.
+     */
+    public Joystick[] getJoysticks() {
+        return joysticks;
+    }
+
+    /**
+     * Adds a {@link RawInputListener} to receive raw input events.
+     * 
+     * <p>
+     * Any raw input listeners registered to this <code>InputManager</code>
+     * will receive raw input events first, before they get handled
+     * by the <code>InputManager</code> itself. The listeners are 
+     * each processed in the order they were added, e.g. FIFO.
+     * <p>
+     * If a raw input listener has handled the event and does not wish
+     * other listeners down the list to process the event, it may set the
+     * {@link InputEvent#setConsumed() consumed flag} to indicate the 
+     * event was consumed and shouldn't be processed any further.
+     * The listener may do this either at each of the event callbacks 
+     * or at the {@link RawInputListener#endInput() } method.
+     * 
+     * @param listener A listener to receive raw input events.
+     * 
+     * @see RawInputListener
+     */
+    public void addRawInputListener(RawInputListener listener) {
+        rawListeners.add(listener);
+        rawListenerArray = null;
+    }
+
+    /**
+     * Removes a {@link RawInputListener} so that it no longer
+     * receives raw input events.
+     * 
+     * @param listener The listener to cease receiving raw input events.
+     * 
+     * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) 
+     */
+    public void removeRawInputListener(RawInputListener listener) {
+        rawListeners.remove(listener);
+        rawListenerArray = null;
+    }
+
+    /**
+     * Clears all {@link RawInputListener}s.
+     * 
+     * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) 
+     */
+    public void clearRawInputListeners() {
+        rawListeners.clear();
+        rawListenerArray = null;
+    }
+
+    private RawInputListener[] getRawListenerArray() {
+        if (rawListenerArray == null) 
+            rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]);
+        return rawListenerArray;
+    }
+    
+    /**
+     * Enable simulation of mouse events. Used for touchscreen input only.
+     * 
+     * @param value True to enable simulation of mouse events
+     */
+    public void setSimulateMouse(boolean value) {
+        if (touch != null) {
+            touch.setSimulateMouse(value);
+        }
+    }
+    
+    /**
+     * Enable simulation of keyboard events. Used for touchscreen input only.
+     * 
+     * @param value True to enable simulation of keyboard events
+     */
+    public void setSimulateKeyboard(boolean value) {
+        if (touch != null) {
+            touch.setSimulateKeyboard(value);
+        }
+    }
+
+    private void processQueue() {
+        int queueSize = inputQueue.size();
+        RawInputListener[] array = getRawListenerArray();
+ 
+        for (RawInputListener listener : array) {
+            listener.beginInput();
+
+            for (int j = 0; j < queueSize; j++) {
+                InputEvent event = inputQueue.get(j);
+                if (event.isConsumed()) {
+                    continue;
+                }
+
+                if (event instanceof MouseMotionEvent) {
+                    listener.onMouseMotionEvent((MouseMotionEvent) event);
+                } else if (event instanceof KeyInputEvent) {
+                    listener.onKeyEvent((KeyInputEvent) event);
+                } else if (event instanceof MouseButtonEvent) {
+                    listener.onMouseButtonEvent((MouseButtonEvent) event);
+                } else if (event instanceof JoyAxisEvent) {
+                    listener.onJoyAxisEvent((JoyAxisEvent) event);
+                } else if (event instanceof JoyButtonEvent) {
+                    listener.onJoyButtonEvent((JoyButtonEvent) event);
+                } else if (event instanceof TouchEvent) {
+                    listener.onTouchEvent((TouchEvent) event);
+                } else {
+                    assert false;
+                }
+            }
+
+            listener.endInput();
+        }
+
+        for (int i = 0; i < queueSize; i++) {
+            InputEvent event = inputQueue.get(i);
+            if (event.isConsumed()) {
+                continue;
+            }
+
+            if (event instanceof MouseMotionEvent) {
+                onMouseMotionEventQueued((MouseMotionEvent) event);
+            } else if (event instanceof KeyInputEvent) {
+                onKeyEventQueued((KeyInputEvent) event);
+            } else if (event instanceof MouseButtonEvent) {
+                onMouseButtonEventQueued((MouseButtonEvent) event);
+            } else if (event instanceof JoyAxisEvent) {
+                onJoyAxisEventQueued((JoyAxisEvent) event);
+            } else if (event instanceof JoyButtonEvent) {
+                onJoyButtonEventQueued((JoyButtonEvent) event);
+            } else if (event instanceof TouchEvent) {
+                onTouchEventQueued((TouchEvent) event);
+            } else {
+                assert false;
+            }
+            // larynx, 2011.06.10 - flag event as reusable because
+            // the android input uses a non-allocating ringbuffer which
+            // needs to know when the event is not anymore in inputQueue
+            // and therefor can be reused.
+            event.setConsumed();
+        }
+
+        inputQueue.clear();
+    }
+
+    /**
+     * Updates the <code>InputManager</code>. 
+     * This will query current input devices and send
+     * appropriate events to registered listeners.
+     *
+     * @param tpf Time per frame value.
+     */
+    public void update(float tpf) {
+        frameTPF = tpf;
+        
+        // Activate safemode if the TPF value is so small
+        // that rounding errors are inevitable
+        safeMode = tpf < 0.015f;
+        
+        long currentTime = keys.getInputTimeNanos();
+        frameDelta = currentTime - lastUpdateTime;
+
+        eventsPermitted = true;
+
+        keys.update();
+        mouse.update();
+        if (joystick != null) {
+            joystick.update();
+        }
+        if (touch != null) {
+            touch.update();
+        }
+
+        eventsPermitted = false;
+
+        processQueue();
+        invokeUpdateActions();
+
+        lastLastUpdateTime = lastUpdateTime;
+        lastUpdateTime = currentTime;
+    }
+
+    /**
+     * Dispatches touch events to touch listeners
+     * @param evt The touch event to be dispatched to all onTouch listeners
+     */
+    public void onTouchEventQueued(TouchEvent evt) { 
+        ArrayList<Mapping> maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode()));
+        if (maps == null) {
+            return;
+        }
+
+        int size = maps.size();
+        for (int i = size - 1; i >= 0; i--) {
+            Mapping mapping = maps.get(i);
+            ArrayList<InputListener> listeners = mapping.listeners;
+            int listenerSize = listeners.size();
+            for (int j = listenerSize - 1; j >= 0; j--) {
+                InputListener listener = listeners.get(j);
+                if (listener instanceof TouchListener) {
+                    ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF); 
+                }
+            }
+        }               
+    }
+    
+    /**
+     * Callback from RawInputListener. Do not use.
+     */
+    @Override
+    public void onTouchEvent(TouchEvent evt) {
+        if (!eventsPermitted) {
+            throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time.");
+        }
+        inputQueue.add(evt);         
+    }
+}
diff --git a/engine/src/core/com/jme3/input/JoyInput.java b/engine/src/core/com/jme3/input/JoyInput.java
new file mode 100644
index 0000000..77c5539
--- /dev/null
+++ b/engine/src/core/com/jme3/input/JoyInput.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with joysticks or gaming controllers.
+ */
+public interface JoyInput extends Input {
+
+    /**
+     * The X axis for POV (point of view hat).
+     */
+    public static final int AXIS_POV_X = 254;
+    
+    /**
+     * The Y axis for POV (point of view hat).
+     */
+    public static final int AXIS_POV_Y = 255;
+
+    /**
+     * Causes the joystick at <code>joyId</code> index to rumble with
+     * the given amount.
+     * 
+     * @param joyId The joystick index
+     * @param amount Rumble amount. Should be between 0 and 1.
+     */
+    public void setJoyRumble(int joyId, float amount);
+    
+    /**
+     * Loads a list of joysticks from the system.
+     * 
+     * @param inputManager The input manager requesting to load joysticks
+     * @return A list of joysticks that are installed.
+     */
+    public Joystick[] loadJoysticks(InputManager inputManager);
+}
diff --git a/engine/src/core/com/jme3/input/Joystick.java b/engine/src/core/com/jme3/input/Joystick.java
new file mode 100644
index 0000000..658781f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/Joystick.java
@@ -0,0 +1,136 @@
+package com.jme3.input;
+
+import com.jme3.input.controls.JoyAxisTrigger;
+import com.jme3.input.controls.JoyButtonTrigger;
+
+/**
+ * A joystick represents a single joystick that is installed in the system.
+ * 
+ * @author Kirill Vainer
+ */
+public final class Joystick {
+
+    private InputManager inputManager;
+    private JoyInput joyInput;
+    private int joyId;
+    private int buttonCount;
+    private int axisCount;
+    private int axisXIndex, axisYIndex;
+    private String name;
+
+    /**
+     * Creates a new joystick instance. Only used internally.
+     */
+    public Joystick(InputManager inputManager, JoyInput joyInput,
+                    int joyId, String name, int buttonCount, int axisCount,
+                    int xAxis, int yAxis){
+        this.inputManager = inputManager;
+        this.joyInput = joyInput;
+        this.joyId = joyId;
+        this.name = name;
+        this.buttonCount = buttonCount;
+        this.axisCount = axisCount;
+
+        this.axisXIndex = xAxis;
+        this.axisYIndex = yAxis;
+    }
+
+    /**
+     * Rumbles the joystick for the given amount/magnitude.
+     * 
+     * @param amount The amount to rumble. Should be between 0 and 1.
+     */
+    public void rumble(float amount){
+        joyInput.setJoyRumble(joyId, amount);
+    }
+
+    /**
+     * Assign the mapping name to receive events from the given button index
+     * on the joystick.
+     * 
+     * @param mappingName The mapping to receive joystick button events.
+     * @param buttonId The button index.
+     * 
+     * @see Joystick#getButtonCount() 
+     */
+    public void assignButton(String mappingName, int buttonId){
+        if (buttonId < 0 || buttonId >= buttonCount)
+            throw new IllegalArgumentException();
+
+        inputManager.addMapping(mappingName, new JoyButtonTrigger(joyId, buttonId));
+    }
+
+    /**
+     * Assign the mappings to receive events from the given joystick axis.
+     * 
+     * @param positiveMapping The mapping to receive events when the axis is negative
+     * @param negativeMapping The mapping to receive events when the axis is positive
+     * @param axisId The axis index.
+     * 
+     * @see Joystick#getAxisCount() 
+     */
+    public void assignAxis(String positiveMapping, String negativeMapping, int axisId){
+        inputManager.addMapping(positiveMapping, new JoyAxisTrigger(joyId, axisId, false));
+        inputManager.addMapping(negativeMapping, new JoyAxisTrigger(joyId, axisId, true));
+    }
+
+    /**
+     * Gets the index number for the X axis on the joystick.
+     * 
+     * <p>E.g. for most gamepads, the left control stick X axis will be returned.
+     * 
+     * @return The axis index for the X axis for this joystick.
+     * 
+     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) 
+     */
+    public int getXAxisIndex(){
+        return axisXIndex;
+    }
+
+    /**
+     * Gets the index number for the Y axis on the joystick.
+     * 
+     * <p>E.g. for most gamepads, the left control stick Y axis will be returned.
+     * 
+     * @return The axis index for the Y axis for this joystick.
+     * 
+     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) 
+     */
+    public int getYAxisIndex(){
+        return axisYIndex;
+    }
+
+    /**
+     * Returns the number of axes on this joystick.
+     * 
+     * @return the number of axes on this joystick.
+     */
+    public int getAxisCount() {
+        return axisCount;
+    }
+
+    /**
+     * Returns the number of buttons on this joystick.
+     * 
+     * @return the number of buttons on this joystick.
+     */
+    public int getButtonCount() {
+        return buttonCount;
+    }
+
+    /**
+     * Returns the name of this joystick.
+     * 
+     * @return the name of this joystick.
+     */
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString(){
+        return "Joystick[name=" + name + ", id=" + joyId + ", buttons=" + buttonCount
+                                + ", axes=" + axisCount + "]";
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/KeyInput.java b/engine/src/core/com/jme3/input/KeyInput.java
new file mode 100644
index 0000000..a26d23a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/KeyInput.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with the keyboard.
+ */
+public interface KeyInput extends Input {
+
+    /**
+     * escape key.
+     */
+    public static final int KEY_ESCAPE = 0x01;
+    /**
+     * 1 key.
+     */
+    public static final int KEY_1 = 0x02;
+    /**
+     * 2 key.
+     */
+    public static final int KEY_2 = 0x03;
+    /**
+     * 3 key.
+     */
+    public static final int KEY_3 = 0x04;
+    /**
+     * 4 key.
+     */
+    public static final int KEY_4 = 0x05;
+    /**
+     * 5 key.
+     */
+    public static final int KEY_5 = 0x06;
+    /**
+     * 6 key.
+     */
+    public static final int KEY_6 = 0x07;
+    /**
+     * 7 key.
+     */
+    public static final int KEY_7 = 0x08;
+    /**
+     * 8 key.
+     */
+    public static final int KEY_8 = 0x09;
+    /**
+     * 9 key.
+     */
+    public static final int KEY_9 = 0x0A;
+    /**
+     * 0 key.
+     */
+    public static final int KEY_0 = 0x0B;
+    /**
+     * - key.
+     */
+    public static final int KEY_MINUS = 0x0C;
+    /**
+     * = key.
+     */
+    public static final int KEY_EQUALS = 0x0D;
+    /**
+     * back key.
+     */
+    public static final int KEY_BACK = 0x0E;
+    /**
+     * tab key.
+     */
+    public static final int KEY_TAB = 0x0F;
+    /**
+     * q key.
+     */
+    public static final int KEY_Q = 0x10;
+    /**
+     * w key.
+     */
+    public static final int KEY_W = 0x11;
+    /**
+     * e key.
+     */
+    public static final int KEY_E = 0x12;
+    /**
+     * r key.
+     */
+    public static final int KEY_R = 0x13;
+    /**
+     * t key.
+     */
+    public static final int KEY_T = 0x14;
+    /**
+     * y key.
+     */
+    public static final int KEY_Y = 0x15;
+    /**
+     * u key.
+     */
+    public static final int KEY_U = 0x16;
+    /**
+     * i key.
+     */
+    public static final int KEY_I = 0x17;
+    /**
+     * o key.
+     */
+    public static final int KEY_O = 0x18;
+    /**
+     * p key.
+     */
+    public static final int KEY_P = 0x19;
+    /**
+     * [ key.
+     */
+    public static final int KEY_LBRACKET = 0x1A;
+    /**
+     * ] key.
+     */
+    public static final int KEY_RBRACKET = 0x1B;
+    /**
+     * enter (main keyboard) key.
+     */
+    public static final int KEY_RETURN = 0x1C;
+    /**
+     * left control key.
+     */
+    public static final int KEY_LCONTROL = 0x1D;
+    /**
+     * a key.
+     */
+    public static final int KEY_A = 0x1E;
+    /**
+     * s key.
+     */
+    public static final int KEY_S = 0x1F;
+    /**
+     * d key.
+     */
+    public static final int KEY_D = 0x20;
+    /**
+     * f key.
+     */
+    public static final int KEY_F = 0x21;
+    /**
+     * g key.
+     */
+    public static final int KEY_G = 0x22;
+    /**
+     * h key.
+     */
+    public static final int KEY_H = 0x23;
+    /**
+     * j key.
+     */
+    public static final int KEY_J = 0x24;
+    /**
+     * k key.
+     */
+    public static final int KEY_K = 0x25;
+    /**
+     * l key.
+     */
+    public static final int KEY_L = 0x26;
+    /**
+     * ; key.
+     */
+    public static final int KEY_SEMICOLON = 0x27;
+    /**
+     * ' key.
+     */
+    public static final int KEY_APOSTROPHE = 0x28;
+    /**
+     * ` key.
+     */
+    public static final int KEY_GRAVE = 0x29;
+    /**
+     * left shift key.
+     */
+    public static final int KEY_LSHIFT = 0x2A;
+    /**
+     * \ key.
+     */
+    public static final int KEY_BACKSLASH = 0x2B;
+    /**
+     * z key.
+     */
+    public static final int KEY_Z = 0x2C;
+    /**
+     * x key.
+     */
+    public static final int KEY_X = 0x2D;
+    /**
+     * c key.
+     */
+    public static final int KEY_C = 0x2E;
+    /**
+     * v key.
+     */
+    public static final int KEY_V = 0x2F;
+    /**
+     * b key.
+     */
+    public static final int KEY_B = 0x30;
+    /**
+     * n key.
+     */
+    public static final int KEY_N = 0x31;
+    /**
+     * m key.
+     */
+    public static final int KEY_M = 0x32;
+    /**
+     * , key.
+     */
+    public static final int KEY_COMMA = 0x33;
+    /**
+     * . key (main keyboard).
+     */
+    public static final int KEY_PERIOD = 0x34;
+    /**
+     * / key (main keyboard).
+     */
+    public static final int KEY_SLASH = 0x35;
+    /**
+     * right shift key.
+     */
+    public static final int KEY_RSHIFT = 0x36;
+    /**
+     * * key (on keypad).
+     */
+    public static final int KEY_MULTIPLY = 0x37;
+    /**
+     * left alt key.
+     */
+    public static final int KEY_LMENU = 0x38;
+    /**
+     * space key.
+     */
+    public static final int KEY_SPACE = 0x39;
+    /**
+     * caps lock key.
+     */
+    public static final int KEY_CAPITAL = 0x3A;
+    /**
+     * F1 key.
+     */
+    public static final int KEY_F1 = 0x3B;
+    /**
+     * F2 key.
+     */
+    public static final int KEY_F2 = 0x3C;
+    /**
+     * F3 key.
+     */
+    public static final int KEY_F3 = 0x3D;
+    /**
+     * F4 key.
+     */
+    public static final int KEY_F4 = 0x3E;
+    /**
+     * F5 key.
+     */
+    public static final int KEY_F5 = 0x3F;
+    /**
+     * F6 key.
+     */
+    public static final int KEY_F6 = 0x40;
+    /**
+     * F7 key.
+     */
+    public static final int KEY_F7 = 0x41;
+    /**
+     * F8 key.
+     */
+    public static final int KEY_F8 = 0x42;
+    /**
+     * F9 key.
+     */
+    public static final int KEY_F9 = 0x43;
+    /**
+     * F10 key.
+     */
+    public static final int KEY_F10 = 0x44;
+    /**
+     * NumLK key.
+     */
+    public static final int KEY_NUMLOCK = 0x45;
+    /**
+     * Scroll lock key.
+     */
+    public static final int KEY_SCROLL = 0x46;
+    /**
+     * 7 key (num pad).
+     */
+    public static final int KEY_NUMPAD7 = 0x47;
+    /**
+     * 8 key (num pad).
+     */
+    public static final int KEY_NUMPAD8 = 0x48;
+    /**
+     * 9 key (num pad).
+     */
+    public static final int KEY_NUMPAD9 = 0x49;
+    /**
+     * - key (num pad).
+     */
+    public static final int KEY_SUBTRACT = 0x4A;
+    /**
+     * 4 key (num pad).
+     */
+    public static final int KEY_NUMPAD4 = 0x4B;
+    /**
+     * 5 key (num pad).
+     */
+    public static final int KEY_NUMPAD5 = 0x4C;
+    /**
+     * 6 key (num pad).
+     */
+    public static final int KEY_NUMPAD6 = 0x4D;
+    /**
+     * + key (num pad).
+     */
+    public static final int KEY_ADD = 0x4E;
+    /**
+     * 1 key (num pad).
+     */
+    public static final int KEY_NUMPAD1 = 0x4F;
+    /**
+     * 2 key (num pad).
+     */
+    public static final int KEY_NUMPAD2 = 0x50;
+    /**
+     * 3 key (num pad).
+     */
+    public static final int KEY_NUMPAD3 = 0x51;
+    /**
+     * 0 key (num pad).
+     */
+    public static final int KEY_NUMPAD0 = 0x52;
+    /**
+     * . key (num pad).
+     */
+    public static final int KEY_DECIMAL = 0x53;
+    /**
+     * F11 key.
+     */
+    public static final int KEY_F11 = 0x57;
+    /**
+     * F12 key.
+     */
+    public static final int KEY_F12 = 0x58;
+    /**
+     * F13 key.
+     */
+    public static final int KEY_F13 = 0x64;
+    /**
+     * F14 key.
+     */
+    public static final int KEY_F14 = 0x65;
+    /**
+     * F15 key.
+     */
+    public static final int KEY_F15 = 0x66;
+    /**
+     * kana key (Japanese).
+     */
+    public static final int KEY_KANA = 0x70;
+    /**
+     * convert key (Japanese).
+     */
+    public static final int KEY_CONVERT = 0x79;
+    /**
+     * noconvert key (Japanese).
+     */
+    public static final int KEY_NOCONVERT = 0x7B;
+    /**
+     * yen key (Japanese).
+     */
+    public static final int KEY_YEN = 0x7D;
+    /**
+     * = on num pad (NEC PC98).
+     */
+    public static final int KEY_NUMPADEQUALS = 0x8D;
+    /**
+     * circum flex key (Japanese).
+     */
+    public static final int KEY_CIRCUMFLEX = 0x90;
+    /**
+     * &#064; key (NEC PC98).
+     */
+    public static final int KEY_AT = 0x91;
+    /**
+     * : key (NEC PC98)
+     */
+    public static final int KEY_COLON = 0x92;
+    /**
+     * _ key (NEC PC98).
+     */
+    public static final int KEY_UNDERLINE = 0x93;
+    /**
+     * kanji key (Japanese).
+     */
+    public static final int KEY_KANJI = 0x94;
+    /**
+     * stop key (NEC PC98).
+     */
+    public static final int KEY_STOP = 0x95;
+    /**
+     * ax key (Japanese).
+     */
+    public static final int KEY_AX = 0x96;
+    /**
+     * (J3100).
+     */
+    public static final int KEY_UNLABELED = 0x97;
+    /**
+     * Enter key (num pad).
+     */
+    public static final int KEY_NUMPADENTER = 0x9C;
+    /**
+     * right control key.
+     */
+    public static final int KEY_RCONTROL = 0x9D;
+    /**
+     * , key on num pad (NEC PC98).
+     */
+    public static final int KEY_NUMPADCOMMA = 0xB3;
+    /**
+     * / key (num pad).
+     */
+    public static final int KEY_DIVIDE = 0xB5;
+    /**
+     * SysRq key.
+     */
+    public static final int KEY_SYSRQ = 0xB7;
+    /**
+     * right alt key.
+     */
+    public static final int KEY_RMENU = 0xB8;
+    /**
+     * pause key.
+     */
+    public static final int KEY_PAUSE = 0xC5;
+    /**
+     * home key.
+     */
+    public static final int KEY_HOME = 0xC7;
+    /**
+     * up arrow key.
+     */
+    public static final int KEY_UP = 0xC8;
+    /**
+     * PgUp key.
+     */
+    public static final int KEY_PRIOR = 0xC9;
+    /**
+     * PgUp key.
+     */
+    public static final int KEY_PGUP = KEY_PRIOR;
+
+    /**
+     * left arrow key.
+     */
+    public static final int KEY_LEFT = 0xCB;
+    /**
+     * right arrow key.
+     */
+    public static final int KEY_RIGHT = 0xCD;
+    /**
+     * end key.
+     */
+    public static final int KEY_END = 0xCF;
+    /**
+     * down arrow key.
+     */
+    public static final int KEY_DOWN = 0xD0;
+    /**
+     * PgDn key.
+     */
+    public static final int KEY_NEXT = 0xD1;
+    /**
+     * PgDn key.
+     */
+    public static final int KEY_PGDN = KEY_NEXT;
+
+    /**
+     * insert key.
+     */
+    public static final int KEY_INSERT = 0xD2;
+    /**
+     * delete key.
+     */
+    public static final int KEY_DELETE = 0xD3;
+    
+    /**
+     * Left "Windows" key on PC keyboards, left "Option" key on Mac keyboards.
+     */
+    public static final int KEY_LMETA  = 0xDB;
+    
+    /**
+     * Right "Windows" key on PC keyboards, right "Option" key on Mac keyboards.
+     */
+    public static final int KEY_RMETA = 0xDC;
+    
+    public static final int KEY_APPS = 0xDD;
+    /**
+     * power key.
+     */
+    public static final int KEY_POWER = 0xDE;
+    /**
+     * sleep key.
+     */
+    public static final int KEY_SLEEP = 0xDF;
+
+}
diff --git a/engine/src/core/com/jme3/input/KeyNames.java b/engine/src/core/com/jme3/input/KeyNames.java
new file mode 100644
index 0000000..89490c0
--- /dev/null
+++ b/engine/src/core/com/jme3/input/KeyNames.java
@@ -0,0 +1,153 @@
+package com.jme3.input;
+
+import static com.jme3.input.KeyInput.*;
+
+public class KeyNames {
+    
+    private static final String[] KEY_NAMES = new String[0xFF];
+    
+    static {
+        KEY_NAMES[KEY_0] = "0";
+        KEY_NAMES[KEY_1] = "1";
+        KEY_NAMES[KEY_2] = "2";
+        KEY_NAMES[KEY_3] = "3";
+        KEY_NAMES[KEY_4] = "4";
+        KEY_NAMES[KEY_5] = "5";
+        KEY_NAMES[KEY_6] = "6";
+        KEY_NAMES[KEY_7] = "7";
+        KEY_NAMES[KEY_8] = "8";
+        KEY_NAMES[KEY_9] = "9";
+        
+        KEY_NAMES[KEY_Q] = "Q";
+        KEY_NAMES[KEY_W] = "W";
+        KEY_NAMES[KEY_E] = "E";
+        KEY_NAMES[KEY_R] = "R";
+        KEY_NAMES[KEY_T] = "T";
+        KEY_NAMES[KEY_Y] = "Y";
+        KEY_NAMES[KEY_U] = "U";
+        KEY_NAMES[KEY_I] = "I";
+        KEY_NAMES[KEY_O] = "O";
+        KEY_NAMES[KEY_P] = "P";
+        KEY_NAMES[KEY_A] = "A";
+        KEY_NAMES[KEY_S] = "S";
+        KEY_NAMES[KEY_D] = "D";
+        KEY_NAMES[KEY_F] = "F";
+        KEY_NAMES[KEY_G] = "G";
+        KEY_NAMES[KEY_H] = "H";
+        KEY_NAMES[KEY_J] = "J";
+        KEY_NAMES[KEY_K] = "K";
+        KEY_NAMES[KEY_L] = "L";
+        KEY_NAMES[KEY_Z] = "Z";
+        KEY_NAMES[KEY_X] = "X";
+        KEY_NAMES[KEY_C] = "C";
+        KEY_NAMES[KEY_V] = "V";
+        KEY_NAMES[KEY_B] = "B";
+        KEY_NAMES[KEY_N] = "N";
+        KEY_NAMES[KEY_M] = "M";
+        
+        KEY_NAMES[KEY_F1] = "F1";
+        KEY_NAMES[KEY_F2] = "F2";
+        KEY_NAMES[KEY_F3] = "F3";
+        KEY_NAMES[KEY_F4] = "F4";
+        KEY_NAMES[KEY_F5] = "F5";
+        KEY_NAMES[KEY_F6] = "F6";
+        KEY_NAMES[KEY_F7] = "F7";
+        KEY_NAMES[KEY_F8] = "F8";
+        KEY_NAMES[KEY_F9] = "F9";
+        KEY_NAMES[KEY_F10] = "F10";
+        KEY_NAMES[KEY_F11] = "F11";
+        KEY_NAMES[KEY_F12] = "F12";
+        KEY_NAMES[KEY_F13] = "F13";
+        KEY_NAMES[KEY_F14] = "F14";
+        KEY_NAMES[KEY_F15] = "F15";
+        
+        KEY_NAMES[KEY_NUMPAD0] = "Numpad 0";
+        KEY_NAMES[KEY_NUMPAD1] = "Numpad 1";
+        KEY_NAMES[KEY_NUMPAD2] = "Numpad 2";
+        KEY_NAMES[KEY_NUMPAD3] = "Numpad 3";
+        KEY_NAMES[KEY_NUMPAD4] = "Numpad 4";
+        KEY_NAMES[KEY_NUMPAD5] = "Numpad 5";
+        KEY_NAMES[KEY_NUMPAD6] = "Numpad 6";
+        KEY_NAMES[KEY_NUMPAD7] = "Numpad 7";
+        KEY_NAMES[KEY_NUMPAD8] = "Numpad 8";
+        KEY_NAMES[KEY_NUMPAD9] = "Numpad 9";
+        
+        KEY_NAMES[KEY_NUMPADEQUALS] = "Numpad =";
+        KEY_NAMES[KEY_NUMPADENTER] = "Numpad Enter";
+        KEY_NAMES[KEY_NUMPADCOMMA] = "Numpad .";
+        KEY_NAMES[KEY_DIVIDE] = "Numpad /";
+        
+        
+        KEY_NAMES[KEY_LMENU] = "Left Alt";
+        KEY_NAMES[KEY_RMENU] = "Right Alt";
+        
+        KEY_NAMES[KEY_LCONTROL] = "Left Ctrl";
+        KEY_NAMES[KEY_RCONTROL] = "Right Ctrl";
+        
+        KEY_NAMES[KEY_LSHIFT] = "Left Shift";
+        KEY_NAMES[KEY_RSHIFT] = "Right Shift";
+        
+        KEY_NAMES[KEY_LMETA] = "Left Option";
+        KEY_NAMES[KEY_RMETA] = "Right Option";
+        
+        KEY_NAMES[KEY_MINUS] = "-";
+        KEY_NAMES[KEY_EQUALS] = "=";
+        KEY_NAMES[KEY_LBRACKET] = "[";
+        KEY_NAMES[KEY_RBRACKET] = "]";
+        KEY_NAMES[KEY_SEMICOLON] = ";";
+        KEY_NAMES[KEY_APOSTROPHE] = "'";
+        KEY_NAMES[KEY_GRAVE] = "`";
+        KEY_NAMES[KEY_BACKSLASH] = "\\";
+        KEY_NAMES[KEY_COMMA] = ",";
+        KEY_NAMES[KEY_PERIOD] = ".";
+        KEY_NAMES[KEY_SLASH] = "/";
+        KEY_NAMES[KEY_MULTIPLY] = "*";
+        KEY_NAMES[KEY_ADD] = "+";
+        KEY_NAMES[KEY_COLON] = ":";
+        KEY_NAMES[KEY_UNDERLINE] = "_";
+        KEY_NAMES[KEY_AT] = "@";
+        
+        KEY_NAMES[KEY_APPS] = "Apps";
+        KEY_NAMES[KEY_POWER] = "Power";
+        KEY_NAMES[KEY_SLEEP] = "Sleep";
+        
+        KEY_NAMES[KEY_STOP] = "Stop";
+        KEY_NAMES[KEY_ESCAPE] = "Esc";
+        KEY_NAMES[KEY_RETURN] = "Enter";
+        KEY_NAMES[KEY_SPACE] = "Space";
+        KEY_NAMES[KEY_BACK] = "Backspace";
+        KEY_NAMES[KEY_TAB] = "Tab";
+        
+        KEY_NAMES[KEY_SYSRQ] = "SysEq";
+        KEY_NAMES[KEY_PAUSE] = "Pause";
+        
+        KEY_NAMES[KEY_HOME] = "Home";
+        KEY_NAMES[KEY_PGUP] = "Page Up";
+        KEY_NAMES[KEY_PGDN] = "Page Down";
+        KEY_NAMES[KEY_END] = "End";
+        KEY_NAMES[KEY_INSERT] = "Insert";
+        KEY_NAMES[KEY_DELETE] = "Delete";
+        
+        KEY_NAMES[KEY_UP] = "Up";
+        KEY_NAMES[KEY_LEFT] = "Left";
+        KEY_NAMES[KEY_RIGHT] = "Right";
+        KEY_NAMES[KEY_DOWN] = "Down";
+        
+        KEY_NAMES[KEY_NUMLOCK] = "Num Lock";
+        KEY_NAMES[KEY_CAPITAL] = "Caps Lock";
+        KEY_NAMES[KEY_SCROLL] = "Scroll Lock";
+        
+        KEY_NAMES[KEY_KANA] = "Kana";
+        KEY_NAMES[KEY_CONVERT] = "Convert";
+        KEY_NAMES[KEY_NOCONVERT] = "No Convert";
+        KEY_NAMES[KEY_YEN] = "Yen";
+        KEY_NAMES[KEY_CIRCUMFLEX] = "Circumflex";
+        KEY_NAMES[KEY_KANJI] = "Kanji";
+        KEY_NAMES[KEY_AX] = "Ax";
+        KEY_NAMES[KEY_UNLABELED] = "Unlabeled";
+    }
+    
+    public String getName(int keyId){
+        return KEY_NAMES[keyId];
+    }
+}
diff --git a/engine/src/core/com/jme3/input/MouseInput.java b/engine/src/core/com/jme3/input/MouseInput.java
new file mode 100644
index 0000000..f4a1f36
--- /dev/null
+++ b/engine/src/core/com/jme3/input/MouseInput.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with the mouse.
+ */
+public interface MouseInput extends Input {
+
+    /**
+     * Mouse X axis.
+     */
+    public static final int AXIS_X = 0;
+    
+    /**
+     * Mouse Y axis.
+     */
+    public static final int AXIS_Y = 1;
+    
+    /**
+     * Mouse wheel axis.
+     */
+    public static final int AXIS_WHEEL = 2;
+
+    /**
+     * Left mouse button.
+     */
+    public static final int BUTTON_LEFT   = 0;
+    
+    /**
+     * Right mouse button.
+     */
+    public static final int BUTTON_RIGHT  = 1;
+    
+    /**
+     * Middle mouse button.
+     */
+    public static final int BUTTON_MIDDLE = 2;
+
+    /**
+     * Set whether the mouse cursor should be visible or not.
+     * 
+     * @param visible Whether the mouse cursor should be visible or not.
+     */
+    public void setCursorVisible(boolean visible);
+
+    /**
+     * Returns the number of buttons the mouse has. Typically 3 for most mice.
+     * 
+     * @return the number of buttons the mouse has.
+     */
+    public int getButtonCount();
+}
diff --git a/engine/src/core/com/jme3/input/RawInputListener.java b/engine/src/core/com/jme3/input/RawInputListener.java
new file mode 100644
index 0000000..dfdd69e
--- /dev/null
+++ b/engine/src/core/com/jme3/input/RawInputListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+import com.jme3.input.event.*;
+
+/**
+ * An interface used for receiving raw input from devices.
+ */
+public interface RawInputListener {
+
+    /**
+     * Called before a batch of input will be sent to this 
+     * <code>RawInputListener</code>. 
+     */
+    public void beginInput();
+    
+    /**
+     * Called after a batch of input was sent to this
+     * <code>RawInputListener</code>. 
+     * 
+     * The listener should set the {@link InputEvent#setConsumed() consumed flag}
+     * on any events that have been consumed either at this call or previous calls.
+     */
+    public void endInput();
+
+    /**
+     * Invoked on joystick axis events.
+     * 
+     * @param evt 
+     */
+    public void onJoyAxisEvent(JoyAxisEvent evt);
+    
+    /**
+     * Invoked on joystick button presses.
+     * 
+     * @param evt 
+     */
+    public void onJoyButtonEvent(JoyButtonEvent evt);
+    
+    /**
+     * Invoked on mouse movement/motion events.
+     * 
+     * @param evt 
+     */
+    public void onMouseMotionEvent(MouseMotionEvent evt);
+    
+    /**
+     * Invoked on mouse button events.
+     * 
+     * @param evt 
+     */
+    public void onMouseButtonEvent(MouseButtonEvent evt);
+    
+    /**
+     * Invoked on keyboard key press or release events.
+     * 
+     * @param evt 
+     */
+    public void onKeyEvent(KeyInputEvent evt);
+    
+    
+    /**
+     * Invoked on touchscreen touch events.
+     * 
+     * @param evt 
+     */
+    public void onTouchEvent(TouchEvent evt);
+}
diff --git a/engine/src/core/com/jme3/input/TouchInput.java b/engine/src/core/com/jme3/input/TouchInput.java
new file mode 100644
index 0000000..2f45b44
--- /dev/null
+++ b/engine/src/core/com/jme3/input/TouchInput.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with smartphone touch devices
+ */
+public interface TouchInput extends Input {
+
+    /**
+     * No filter, get all events
+     */
+    public static final int ALL = 0x00;
+    /**
+     * Home key
+     */
+    public static final int KEYCODE_HOME = 0x03;
+    /**
+     * Escape key.
+     */
+    public static final int KEYCODE_BACK = 0x04;
+    /**
+     * Context Menu key.
+     */
+    public static final int KEYCODE_MENU = 0x52;
+    /**
+     * Search key.
+     */
+    public static final int KEYCODE_SEARCH = 0x54;
+    /**
+     * Volume up key.
+     */
+    public static final int KEYCODE_VOLUME_UP = 0x18;        
+    /**
+     * Volume down key.
+     */
+    public static final int KEYCODE_VOLUME_DOWN = 0x19;    
+
+    
+    /**
+     * Set if mouse events should be generated
+     * 
+     * @param simulate if mouse events should be generated
+     */
+    public void setSimulateMouse(boolean simulate);
+
+    /**
+     * Set if keyboard events should be generated
+     * 
+     * @param simulate if keyboard events should be generated
+     */
+    public void setSimulateKeyboard(boolean simulate);
+    
+    /**
+     * Set if historic android events should be transmitted, can be used to get better performance and less mem
+     * @see <a href="http://developer.android.com/reference/android/view/MotionEvent.html#getHistoricalX%28int,%20int%29">
+     * http://developer.android.com/reference/android/view/MotionEvent.html#getHistoricalX%28int,%20int%29</a>
+     * @param dontSendHistory turn of historic events if true, false else and default
+     */
+    public void setOmitHistoricEvents(boolean dontSendHistory);
+    
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/controls/ActionListener.java b/engine/src/core/com/jme3/input/controls/ActionListener.java
new file mode 100644
index 0000000..8deed3c
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/ActionListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * <code>ActionListener</code> is used to receive input events in "digital" style.
+ * <p>
+ * Generally all button inputs, such as keyboard, mouse button, and joystick button,
+ * will be represented exactly. Analog inputs will be converted into digital.
+ * <p>
+ * When an action listener is registered to a natively digital input, such as a button,
+ * the event will be invoked when the button is pressed, with <code>value</code>
+ * set to <code>true</code>, and will be invoked again when the button is released,
+ * with <code>value</code> set to <code>false</code>.
+ *
+ * @author Kirill Vainer
+ */
+public interface ActionListener extends InputListener {
+    
+    /**
+     * Called when an input to which this listener is registered to is invoked.
+     * 
+     * @param name The name of the mapping that was invoked
+     * @param isPressed True if the action is "pressed", false otherwise
+     * @param tpf The time per frame value.
+     */
+    public void onAction(String name, boolean isPressed, float tpf);
+}
diff --git a/engine/src/core/com/jme3/input/controls/AnalogListener.java b/engine/src/core/com/jme3/input/controls/AnalogListener.java
new file mode 100644
index 0000000..b28796a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/AnalogListener.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * <code>AnalogListener</code> is used to receive events of inputs
+ * in analog format. 
+ *
+ * @author Kirill Vainer
+ */
+public interface AnalogListener extends InputListener {
+    /**
+     * Called to notify the implementation that an analog event has occurred.
+     *
+     * The results of KeyTrigger and MouseButtonTrigger events will have tpf
+     *  == value.
+     *
+     * @param name The name of the mapping that was invoked
+     * @param value Value of the axis, from 0 to 1.
+     * @param tpf The time per frame value.
+     */
+    public void onAnalog(String name, float value, float tpf);
+}
diff --git a/engine/src/core/com/jme3/input/controls/InputListener.java b/engine/src/core/com/jme3/input/controls/InputListener.java
new file mode 100644
index 0000000..ce140a9
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/InputListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * A generic interface for input listeners, the {@link AnalogListener} and
+ * {@link ActionListener} interfaces extend this interface.
+ *
+ * @author Kirill Vainer
+ */
+public interface InputListener {
+}
diff --git a/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java b/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java
new file mode 100644
index 0000000..539d83e
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.Joystick;
+
+public class JoyAxisTrigger implements Trigger {
+
+    private final int joyId, axisId;
+    private final boolean negative;
+
+    /**
+     * Use {@link Joystick#assignAxis(java.lang.String, java.lang.String, int) }
+     * instead.
+     */
+    public JoyAxisTrigger(int joyId, int axisId, boolean negative) {
+        this.joyId = joyId;
+        this.axisId = axisId;
+        this.negative = negative;
+    }
+
+    public static int joyAxisHash(int joyId, int joyAxis, boolean negative){
+        assert joyAxis >= 0 && joyAxis <= 255;
+        return (2048 * joyId) | (negative ? 1280 : 1024) | (joyAxis & 0xff);
+    }
+
+    public int getAxisId() {
+        return axisId;
+    }
+
+    public int getJoyId() {
+        return joyId;
+    }
+
+    public boolean isNegative() {
+        return negative;
+    }
+
+    public String getName() {
+        return "JoyAxis[joyId="+joyId+", axisId="+axisId+", neg="+negative+"]";
+    }
+
+    public int triggerHashCode() {
+        return joyAxisHash(joyId, axisId, negative);
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java b/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java
new file mode 100644
index 0000000..5e2cfd6
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.Joystick;
+
+public class JoyButtonTrigger implements Trigger {
+
+    private final int joyId, buttonId;
+
+    /**
+     * Use {@link Joystick#assignButton(java.lang.String, int) } instead.
+     * 
+     * @param joyId
+     * @param axisId 
+     */
+    public JoyButtonTrigger(int joyId, int axisId) {
+        this.joyId = joyId;
+        this.buttonId = axisId;
+    }
+
+    public static int joyButtonHash(int joyId, int joyButton){
+        assert joyButton >= 0 && joyButton <= 255;
+        return (2048 * joyId) | 1536 | (joyButton & 0xff);
+    }
+
+    public int getAxisId() {
+        return buttonId;
+    }
+
+    public int getJoyId() {
+        return joyId;
+    }
+
+    public String getName() {
+        return "JoyButton[joyId="+joyId+", axisId="+buttonId+"]";
+    }
+
+    public int triggerHashCode() {
+        return joyButtonHash(joyId, buttonId);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/KeyTrigger.java b/engine/src/core/com/jme3/input/controls/KeyTrigger.java
new file mode 100644
index 0000000..993b4e7
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/KeyTrigger.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * A <code>KeyTrigger</code> is used as a mapping to keyboard keys.
+ *
+ * @author Kirill Vainer
+ */
+public class KeyTrigger implements Trigger {
+
+    private final int keyCode;
+
+    /**
+     * Create a new <code>KeyTrigger</code> for the given keycode.
+     * 
+     * @param keyCode the code for the key, see constants in {@link KeyInput}.
+     */
+    public KeyTrigger(int keyCode){
+        this.keyCode = keyCode;
+    }
+
+    public String getName() {
+        return "KeyCode " + keyCode;
+    }
+
+    public int getKeyCode(){
+        return keyCode;
+    }
+
+    public static int keyHash(int keyCode){
+        assert keyCode >= 0 && keyCode <= 255;
+        return keyCode & 0xff;
+    }
+
+    public int triggerHashCode() {
+        return keyHash(keyCode);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java b/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java
new file mode 100644
index 0000000..49de745
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * A <code>MouseAxisTrigger</code> is used as a mapping to mouse axis,
+ * a mouse axis is movement along the X axis (left/right), Y axis (up/down)
+ * and the mouse wheel (scroll up/down).
+ *
+ * @author Kirill Vainer
+ */
+public class MouseAxisTrigger implements Trigger {
+
+    private int mouseAxis;
+    private boolean negative;
+
+    /**
+     * Create a new <code>MouseAxisTrigger</code>.
+     * <p>
+     * @param mouseAxis Mouse axis. See AXIS_*** constants in {@link MouseInput}
+     * @param negative True if listen to negative axis events, false if
+     * listen to positive axis events.
+     */
+    public MouseAxisTrigger(int mouseAxis, boolean negative){
+        if (mouseAxis < 0 || mouseAxis > 2)
+            throw new IllegalArgumentException("Mouse Axis must be between 0 and 2");
+
+        this.mouseAxis = mouseAxis;
+        this.negative = negative;
+    }
+
+    public int getMouseAxis(){
+        return mouseAxis;
+    }
+
+    public boolean isNegative() {
+        return negative;
+    }
+
+    public String getName() {
+        String sign = negative ? "Negative" : "Positive";
+        switch (mouseAxis){
+            case MouseInput.AXIS_X: return "Mouse X Axis " + sign;
+            case MouseInput.AXIS_Y: return "Mouse Y Axis " + sign;
+            case MouseInput.AXIS_WHEEL: return "Mouse Wheel " + sign;
+            default: throw new AssertionError();
+        }
+    }
+
+    public static int mouseAxisHash(int mouseAxis, boolean negative){
+        assert mouseAxis >= 0 && mouseAxis <= 255;
+        return (negative ? 768 : 512) | (mouseAxis & 0xff);
+    }
+
+    public int triggerHashCode() {
+        return mouseAxisHash(mouseAxis, negative);
+    }
+}
diff --git a/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java b/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java
new file mode 100644
index 0000000..ff013d6
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * A <code>MouseButtonTrigger</code> is used as a mapping to receive events
+ * from mouse buttons. It is generally expected for a mouse to have at least
+ * a left and right mouse button, but some mice may have a lot more buttons
+ * than that.
+ *
+ * @author Kirill Vainer
+ */
+public class MouseButtonTrigger implements Trigger {
+
+    private final int mouseButton;
+
+    /**
+     * Create a new <code>MouseButtonTrigger</code> to receive mouse button events.
+     * 
+     * @param mouseButton Mouse button index. See BUTTON_*** constants in
+     * {@link MouseInput}.
+     */
+    public MouseButtonTrigger(int mouseButton) {
+        if  (mouseButton < 0)
+            throw new IllegalArgumentException("Mouse Button cannot be negative");
+
+        this.mouseButton = mouseButton;
+    }
+
+    public int getMouseButton() {
+        return mouseButton;
+    }
+
+    public String getName() {
+        return "Mouse Button " + mouseButton;
+    }
+
+    public static int mouseButtonHash(int mouseButton){
+        assert mouseButton >= 0 && mouseButton <= 255;
+        return 256 | (mouseButton & 0xff);
+    }
+
+    public int triggerHashCode() {
+        return mouseButtonHash(mouseButton);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/TouchListener.java b/engine/src/core/com/jme3/input/controls/TouchListener.java
new file mode 100644
index 0000000..21c100f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/TouchListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.event.TouchEvent;
+
+/**
+ * <code>TouchListener</code> is used to receive events of inputs from smartphone touch devices 
+ *
+ * @author larynx
+ */
+public interface TouchListener extends InputListener {
+    /**
+     * @param name the name of the event
+     * @param event the touch event
+     * @param tpf how much time has passed since the last frame
+     */
+    public void onTouch(String name, TouchEvent event, float tpf);
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/controls/TouchTrigger.java b/engine/src/core/com/jme3/input/controls/TouchTrigger.java
new file mode 100644
index 0000000..25124ab
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/TouchTrigger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * Class to trigger TouchEvents, keycode can be TouchInput.ALL(=0) or TouchInput.KEYCODE_*
+ * @author larynx
+ *
+ */
+public class TouchTrigger implements Trigger {
+    
+    private final int keyCode;
+    
+    /**
+     * Constructor
+     * @param keyCode can be zero to get all events or TouchInput.KEYCODE_*
+     */
+    public TouchTrigger(int keyCode) {
+        super();
+        this.keyCode = keyCode;
+    }
+    
+    @Override
+    public String getName() {
+        if (keyCode != 0)
+            return "TouchInput";
+        else
+            return "TouchInput KeyCode " + keyCode;
+    }
+    
+    public static int touchHash(int keyCode){
+        assert keyCode >= 0 && keyCode <= 255;
+        return 0xfedcba98 + keyCode;
+    }
+
+    public int triggerHashCode() {
+        return touchHash(keyCode);
+    }
+    
+    public int getKeyCode(){
+        return keyCode;
+    }
+}
diff --git a/engine/src/core/com/jme3/input/controls/Trigger.java b/engine/src/core/com/jme3/input/controls/Trigger.java
new file mode 100644
index 0000000..9f059e8
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/Trigger.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * A trigger represents a physical input, such as a keyboard key, a mouse
+ * button, or joystick axis.
+ */
+public interface Trigger {
+
+    /**
+     * @return A user friendly name for the trigger.
+     */
+    public String getName();
+    
+    /**
+     * Returns the hash code for the trigger.
+     * 
+     * @return the hash code for the trigger.
+     */
+    public int triggerHashCode();
+}
diff --git a/engine/src/core/com/jme3/input/controls/package.html b/engine/src/core/com/jme3/input/controls/package.html
new file mode 100644
index 0000000..2303ea9
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.controls</code> package allows user code to listen
+to input events regardless of the type of input used. 
+<p>
+Users will receive input in one of two forms, either 
+{@link com.jme3.input.controls.AnalogListener analog input} or
+{@link com.jme3.input.controls.Action digital/action input}.
+
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/dummy/DummyInput.java b/engine/src/core/com/jme3/input/dummy/DummyInput.java
new file mode 100644
index 0000000..3ecb509
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyInput.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.Input;
+import com.jme3.input.RawInputListener;
+
+/**
+ * DummyInput as an implementation of <code>Input</code> that raises no
+ * input events.
+ * 
+ * @author Kirill Vainer.
+ */
+public class DummyInput implements Input {
+
+    protected boolean inited = false;
+
+    public void initialize() {
+        if (inited)
+            throw new IllegalStateException("Input already initialized.");
+
+        inited = true;
+    }
+
+    public void update() {
+        if (!inited)
+            throw new IllegalStateException("Input not initialized.");
+    }
+
+    public void destroy() {
+        if (!inited)
+            throw new IllegalStateException("Input not initialized.");
+
+        inited = false;
+    }
+
+    public boolean isInitialized() {
+        return inited;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+    }
+
+    public long getInputTimeNanos() {
+        return System.currentTimeMillis() * 1000000;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java b/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java
new file mode 100644
index 0000000..4d4efcb
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * DummyKeyInput as an implementation of <code>KeyInput</code> that raises no
+ * input events.
+ * 
+ * @author Kirill Vainer.
+ */
+public class DummyKeyInput extends DummyInput implements KeyInput {
+
+    public int getKeyCount() {
+        if (!inited)
+            throw new IllegalStateException("Input not initialized.");
+
+        return 0;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java b/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java
new file mode 100644
index 0000000..b862061
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * DummyMouseInput as an implementation of <code>MouseInput</code> that raises no
+ * input events.
+ * 
+ * @author Kirill Vainer.
+ */
+public class DummyMouseInput extends DummyInput implements MouseInput {
+
+    public void setCursorVisible(boolean visible) {
+        if (!inited)
+            throw new IllegalStateException("Input not initialized.");
+    }
+
+    public int getButtonCount() {
+        return 0;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/package.html b/engine/src/core/com/jme3/input/dummy/package.html
new file mode 100644
index 0000000..13f5c55
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.dummy</code> package provides "dummy" or "null"
+implementations of the input interfaces.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/event/InputEvent.java b/engine/src/core/com/jme3/input/event/InputEvent.java
new file mode 100644
index 0000000..bb3867d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/InputEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.Input;
+
+/**
+ * An abstract input event.
+ */
+public abstract class InputEvent {
+
+    protected long time;
+
+    
+    protected boolean consumed = false;
+
+    /**
+     * The time when the event occurred. This is relative to
+     * {@link Input#getInputTimeNanos() }.
+     * 
+     * @return time when the event occured
+     */
+    public long getTime(){
+        return time;
+    }
+
+    /**
+     * Set the time when the event occurred.
+     * 
+     * @param time time when the event occurred.
+     */
+    public void setTime(long time){
+        this.time = time;
+    }
+
+    /**
+     * Returns true if the input event has been consumed, meaning it is no longer valid
+     * and should not be forwarded to input listeners.
+     * 
+     * @return true if the input event has been consumed
+     */
+    public boolean isConsumed() {
+        return consumed;
+    }
+
+    /**
+     * Call to mark this input event as consumed, meaning it is no longer valid
+     * and should not be forwarded to input listeners.
+     */
+    public void setConsumed() {
+        this.consumed = true;
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/input/event/JoyAxisEvent.java b/engine/src/core/com/jme3/input/event/JoyAxisEvent.java
new file mode 100644
index 0000000..2896b0b
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/JoyAxisEvent.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.Joystick;
+
+/**
+ * Joystick axis event.
+ * 
+ * @author Kirill Vainer
+ */
+public class JoyAxisEvent extends InputEvent {
+
+    private int joyIdx;
+    private int axisIdx;
+    private float value;
+
+    public JoyAxisEvent(int joyIdx, int axisIdx, float value) {
+        this.joyIdx = joyIdx;
+        this.axisIdx = axisIdx;
+        this.value = value;
+    }
+
+    /**
+     * Returns the joystick axis index.
+     * 
+     * @return joystick axis index.
+     * 
+     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) 
+     */
+    public int getAxisIndex() {
+        return axisIdx;
+    }
+
+    /**
+     * The joystick index.
+     * 
+     * @return joystick index.
+     * 
+     * @see InputManager#getJoysticks() 
+     */
+    public int getJoyIndex() {
+        return joyIdx;
+    }
+
+    /**
+     * The value of the axis.
+     * 
+     * @return value of the axis.
+     */
+    public float getValue() {
+        return value;
+    }
+}
diff --git a/engine/src/core/com/jme3/input/event/JoyButtonEvent.java b/engine/src/core/com/jme3/input/event/JoyButtonEvent.java
new file mode 100644
index 0000000..906847f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/JoyButtonEvent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.Joystick;
+
+/**
+ * Joystick button event.
+ * 
+ * @author Kirill Vainer
+ */
+public class JoyButtonEvent extends InputEvent {
+
+    private int joyIdx;
+    private int btnIdx;
+    private boolean pressed;
+
+    public JoyButtonEvent(int joyIdx, int btnIdx, boolean pressed) {
+        this.joyIdx = joyIdx;
+        this.btnIdx = btnIdx;
+        this.pressed = pressed;
+    }
+
+    /**
+     * The button index.
+     * 
+     * @return button index.
+     * 
+     * @see Joystick#assignButton(java.lang.String, int) 
+     */
+    public int getButtonIndex() {
+        return btnIdx;
+    }
+
+    /**
+     * The joystick index.
+     * 
+     * @return joystick index.
+     * 
+     * @see InputManager#getJoysticks() 
+     */
+    public int getJoyIndex() {
+        return joyIdx;
+    }
+
+    /**
+     * Returns true if the event was a button press,
+     * returns false if the event was a button release.
+     * 
+     * @return true if the event was a button press,
+     * false if the event was a button release.
+     */
+    public boolean isPressed() {
+        return pressed;
+    }
+
+
+
+}
diff --git a/engine/src/core/com/jme3/input/event/KeyInputEvent.java b/engine/src/core/com/jme3/input/event/KeyInputEvent.java
new file mode 100644
index 0000000..403635c
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/KeyInputEvent.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * Keyboard key event.
+ * 
+ * @author Kirill Vainer
+ */
+public class KeyInputEvent extends InputEvent {
+
+    private int keyCode;
+    private char keyChar;
+    private boolean pressed;
+    private boolean repeating;
+
+    public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) {
+        this.keyCode = keyCode;
+        this.keyChar = keyChar;
+        this.pressed = pressed;
+        this.repeating = repeating;
+    }
+
+    /**
+     * Returns the key character. Returns 0 if the key has no character.
+     * 
+     * @return the key character. 0 if the key has no character.
+     */
+    public char getKeyChar() {
+        return keyChar;
+    }
+
+    /**
+     * The key code.
+     * <p>
+     * See KEY_*** constants in {@link KeyInput}.
+     * 
+     * @return key code.
+     */
+    public int getKeyCode() {
+        return keyCode;
+    }
+
+    /**
+     * Returns true if this event is key press, false is it was a key release.
+     * 
+     * @return true if this event is key press, false is it was a key release.
+     */
+    public boolean isPressed() {
+        return pressed;
+    }
+
+    /**
+     * Returns true if this event is a repeat event. Not used anymore.
+     * 
+     * @return true if this event is a repeat event
+     */
+    public boolean isRepeating() {
+        return repeating;
+    }
+
+    /**
+     * Returns true if this event is a key release, false if it was a key press.
+     * 
+     * @return true if this event is a key release, false if it was a key press.
+     */
+    public boolean isReleased() {
+        return !pressed;
+    }
+
+    @Override
+    public String toString(){
+        String str = "Key(CODE="+keyCode;
+        if (keyChar != '\0')
+            str = str + ", CHAR=" + keyChar;
+            
+        if (repeating){
+            return str + ", REPEATING)";
+        }else if (pressed){
+            return str + ", PRESSED)";
+        }else{
+            return str + ", RELEASED)";
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/input/event/MouseButtonEvent.java b/engine/src/core/com/jme3/input/event/MouseButtonEvent.java
new file mode 100644
index 0000000..66af54d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/MouseButtonEvent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * Mouse button press/release event.
+ * 
+ * @author Kirill Vainer
+ */
+public class MouseButtonEvent extends InputEvent {
+
+    private int x;
+    private int y;
+    private int btnIndex;
+    private boolean pressed;
+
+    public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) {
+        this.btnIndex = btnIndex;
+        this.pressed = pressed;
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * Returns the mouse button index.
+     * <p>
+     * See constants in {@link MouseInput}.
+     * 
+     * @return the mouse button index.
+     */
+    public int getButtonIndex() {
+        return btnIndex;
+    }
+
+    /**
+     * Returns true if the mouse button was pressed, false if it was released.
+     * 
+     * @return true if the mouse button was pressed, false if it was released.
+     */
+    public boolean isPressed() {
+        return pressed;
+    }
+
+    /**
+     * Returns true if the mouse button was released, false if it was pressed.
+     * 
+     * @return true if the mouse button was released, false if it was pressed.
+     */
+    public boolean isReleased() {
+        return !pressed;
+    }
+    
+    /**
+     * The X coordinate of the mouse when the event was generated.
+     * @return X coordinate of the mouse when the event was generated.
+     */
+    public int getX() {
+        return x;
+    }
+    
+    /**
+     * The Y coordinate of the mouse when the event was generated.
+     * @return Y coordinate of the mouse when the event was generated.
+     */
+    public int getY() {
+        return y;
+    }
+    
+    @Override
+    public String toString(){
+        String str = "MouseButton(BTN="+btnIndex;
+        if (pressed){
+            return str + ", PRESSED)";
+        }else{
+            return str + ", RELEASED)";
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/event/MouseMotionEvent.java b/engine/src/core/com/jme3/input/event/MouseMotionEvent.java
new file mode 100644
index 0000000..7439ecc
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/MouseMotionEvent.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+/**
+ * Mouse movement event.
+ * <p>
+ * Movement events are only generated if the mouse is on-screen.
+ * 
+ * @author Kirill Vainer
+ */
+public class MouseMotionEvent extends InputEvent {
+
+    private int x, y, dx, dy, wheel, deltaWheel;
+
+    public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) {
+        this.x = x;
+        this.y = y;
+        this.dx = dx;
+        this.dy = dy;
+        this.wheel = wheel;
+        this.deltaWheel = deltaWheel;
+    }
+
+    /**
+     * The change in wheel rotation.
+     * 
+     * @return change in wheel rotation.
+     */
+    public int getDeltaWheel() {
+        return deltaWheel;
+    }
+
+    /**
+     * The change in X coordinate
+     * @return change in X coordinate
+     */
+    public int getDX() {
+        return dx;
+    }
+
+    /**
+     * The change in Y coordinate
+     * 
+     * @return change in Y coordinate
+     */
+    public int getDY() {
+        return dy;
+    }
+
+    /**
+     * Current mouse wheel value
+     * @return Current mouse wheel value
+     */
+    public int getWheel() {
+        return wheel;
+    }
+
+    /**
+     * Current X coordinate
+     * @return Current X coordinate
+     */
+    public int getX() {
+        return x;
+    }
+
+    /**
+     * Current Y coordinate
+     * @return Current Y coordinate
+     */
+    public int getY() {
+        return y;
+    }
+
+    @Override
+    public String toString(){
+        return "MouseMotion(X="+x+", Y="+y+", DX="+dx+", DY="+dy+")";
+    }
+
+}
diff --git a/engine/src/core/com/jme3/input/event/TouchEvent.java b/engine/src/core/com/jme3/input/event/TouchEvent.java
new file mode 100644
index 0000000..8834c85
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/TouchEvent.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input.event;
+
+/**
+ * <code>TouchEvent</code> represents a single event from multi-touch input devices
+ * @author larynx
+ */
+public class TouchEvent extends InputEvent {
+
+    public enum Type {
+
+        /**
+         * Touch down event, fields: posX, posY, pressure
+         */
+        DOWN,
+        /**
+         * Move/Drag event, fields: posX, posY, deltaX, deltaY, pressure
+         */
+        MOVE,
+        /**
+         * Touch up event, fields: posX, posY, pressure
+         */
+        UP,
+        /**
+         * Virtual keyboard or hardware key event down, fields: keyCode, characters
+         */
+        KEY_DOWN,
+        /**
+         * Virtual keyboard or hardware key event up, fields: keyCode, characters
+         */
+        KEY_UP,
+        // Single finger gestures
+        FLING,
+        TAP,
+        DOUBLETAP,
+        LONGPRESSED,
+        // Two finger scale events
+        /**
+         * Two finger scale event start, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan  
+         */
+        SCALE_START,
+        /**
+         * Two finger scale event, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan
+         */
+        SCALE_MOVE,
+        /**
+         * Two finger scale event end, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan
+         */
+        SCALE_END,
+        /**
+         *  Scroll event 
+         */
+        SCROLL,
+        /**
+         * The user has performed a down MotionEvent and not performed a move or up yet. This event is commonly used to provide visual feedback to the user to let them know that their action has been recognized i.e. highlight an element.
+         */
+        SHOWPRESS,
+        // Others
+        OUTSIDE,
+        IDLE
+    }
+    private Type type = Type.IDLE;
+    private int pointerId;
+    private float posX;
+    private float posY;
+    private float deltaX;
+    private float deltaY;
+    private float pressure;
+    
+    // Used only with KEY* events
+    private int keyCode;
+    private String characters;
+    // Used only with SCALE* events
+    private float scaleFactor;
+    private float scaleSpan;
+
+    public TouchEvent() {
+        set(Type.IDLE, 0f, 0f, 0f, 0f);
+    }
+
+    public TouchEvent(Type type, float x, float y, float deltax, float deltay) {
+        set(type, x, y, deltax, deltay);
+    }
+
+    public void set(Type type) {
+        set(type, 0f, 0f, 0f, 0f);
+    }
+
+    public void set(Type type, float x, float y, float deltax, float deltay) {
+        this.type = type;
+        this.posX = x;
+        this.posY = y;
+        this.deltaX = deltax;
+        this.deltaY = deltay;
+        pointerId = 0;
+        pressure = 0;
+        keyCode = 0;
+        scaleFactor = 0;
+        scaleSpan = 0;
+        characters = "";
+        consumed = false;
+    }
+
+    /**
+     * Returns the type of touch event.
+     * 
+     * @return the type of touch event.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    public float getX() {
+        return posX;
+    }
+
+    public float getY() {
+        return posY;
+    }
+
+    public float getDeltaX() {
+        return deltaX;
+    }
+
+    public float getDeltaY() {
+        return deltaY;
+    }
+    
+    public float getPressure() 
+    {
+        return pressure;
+    }
+    
+    public void setPressure(float pressure) 
+    {
+        this.pressure = pressure;
+    }
+    
+    public int getPointerId() 
+    {
+        return pointerId;
+    }
+
+    public void setPointerId(int pointerId) {
+        this.pointerId = pointerId;
+    }
+
+    public int getKeyCode() {
+        return keyCode;
+    }
+
+    public void setKeyCode(int keyCode) {
+        this.keyCode = keyCode;
+    }
+
+    public String getCharacters() {
+        return characters;
+    }
+
+    public void setCharacters(String characters) {
+        this.characters = characters;
+    }
+
+    public float getScaleFactor() {
+        return scaleFactor;
+    }
+
+    public void setScaleFactor(float scaleFactor) {
+        this.scaleFactor = scaleFactor;
+    }
+
+    public float getScaleSpan() {
+        return scaleSpan;
+    }
+
+    public void setScaleSpan(float scaleSpan) {
+        this.scaleSpan = scaleSpan;
+    }
+}
diff --git a/engine/src/core/com/jme3/input/event/package.html b/engine/src/core/com/jme3/input/event/package.html
new file mode 100644
index 0000000..eaa96fb
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/package.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.event</code> package contains low-level input events.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/package.html b/engine/src/core/com/jme3/input/package.html
new file mode 100644
index 0000000..998a22a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/package.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input</code> package is used for all input handling in
+jMonkeyEngine. User code should use the {@link com.jme3.input.InputManager} to register
+for and receive input events. The <code>InputManager</code> can be
+retrieved for an application by using {@link com.jme3.app.Application#getInputManager()}.
+
+<h3>Usage</h3>
+
+<p>
+Using ActionListener:<br>
+<code>
+// Retrieve an input manager for the application "app"<br>
+InputManager inputManager = app.getInputManager();<br>
+<br>
+// Adds a new mapping "PrintHello" that will be invoked when the Return/Enter key is pressed<br>
+inputManager.addMapping("PrintHello", new KeyTrigger(KeyInput.KEY_RETURN));<br>
+// Adds a new ActionListener to get an event when enter is pressed.<br>
+inputManager.addListener(new ActionListener() {<br>
+    public void onAction(String name, boolean isPressed, float tpf) {<br>
+        // Only invoke the event when the mapping is "PrintHello" <br>
+        // and isPressed is true, meaning it was a key press and not release.<br>
+        if (name.equals("PrintHello") && isPressed){<br>
+            System.out.println("Hello!");<br>
+        }<br>
+    }<br>
+}, "PrintHello");<br>
+</code>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/light/AmbientLight.java b/engine/src/core/com/jme3/light/AmbientLight.java
new file mode 100644
index 0000000..736cc12
--- /dev/null
+++ b/engine/src/core/com/jme3/light/AmbientLight.java
@@ -0,0 +1,26 @@
+package com.jme3.light;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * An ambient light adds a constant color to the scene.
+ * <p>
+ * Ambient lights are unaffected by the surface normal, and are constant
+ * regardless of the model's location. The material's ambient color is
+ * multiplied by the ambient light color to get the final ambient color of
+ * an object.
+ * 
+ * @author Kirill Vainer
+ */
+public class AmbientLight extends Light {
+
+    @Override
+    public void computeLastDistance(Spatial owner) {
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Ambient;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/light/DirectionalLight.java b/engine/src/core/com/jme3/light/DirectionalLight.java
new file mode 100644
index 0000000..e2bcdee
--- /dev/null
+++ b/engine/src/core/com/jme3/light/DirectionalLight.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * <code>DirectionalLight</code> is a light coming from a certain direction in world space. 
+ * E.g sun or moon light.
+ * <p>
+ * Directional lights have no specific position in the scene, they always 
+ * come from their direction regardless of where an object is placed.
+ */
+public class DirectionalLight extends Light {
+
+    protected Vector3f direction = new Vector3f(0f, -1f, 0f);
+
+    @Override
+    public void computeLastDistance(Spatial owner) {
+        lastDistance = 0; // directional lights are always closest to their owner
+    }
+
+    /**
+     * Returns the direction vector of the light.
+     * 
+     * @return The direction vector of the light.
+     * 
+     * @see DirectionalLight#setDirection(com.jme3.math.Vector3f) 
+     */
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    /**
+     * Sets the direction of the light.
+     * <p>
+     * Represents the vector direction the light is coming from.
+     * (1, 0, 0) would represent a directional light coming from the X axis.
+     * 
+     * @param dir the direction of the light.
+     */
+    public void setDirection(Vector3f dir){
+        direction.set(dir);
+        if (!direction.isUnitVector()) {
+            direction.normalizeLocal();
+        }
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Directional;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(direction, "direction", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        direction = (Vector3f) ic.readSavable("direction", null);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/light/Light.java b/engine/src/core/com/jme3/light/Light.java
new file mode 100644
index 0000000..965eaf9
--- /dev/null
+++ b/engine/src/core/com/jme3/light/Light.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.export.*;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Abstract class for representing a light source.
+ * <p>
+ * All light source types have a color.
+ */
+public abstract class Light implements Savable, Cloneable {
+
+    /**
+     * Describes the light type.
+     */
+    public enum Type {
+
+        /**
+         * Directional light
+         * 
+         * @see DirectionalLight
+         */
+        Directional(0),
+        
+        /**
+         * Point light
+         * 
+         * @see PointLight
+         */
+        Point(1),
+        
+        /**
+         * Spot light.
+         * <p>
+         * Not supported by jMonkeyEngine
+         */
+        Spot(2),
+        
+        /**
+         * Ambient light
+         * 
+         * @see AmbientLight
+         */
+        Ambient(3);
+
+        private int typeId;
+
+        Type(int type){
+            this.typeId = type;
+        }
+
+        /**
+         * Returns an index for the light type
+         * @return an index for the light type
+         */
+        public int getId(){
+            return typeId;
+        }
+    }
+
+    protected ColorRGBA color = new ColorRGBA(1f,1f,1f,1f);
+    
+    /**
+     * Used in LightList for caching the distance 
+     * to the owner spatial. Should be reset after the sorting.
+     */
+    protected transient float lastDistance = -1;
+
+    /**
+     * If light is disabled, it will not have any 
+     */
+    protected boolean enabled = true;
+
+    /** 
+     * The light name. 
+     */
+    protected String name;
+
+    /**
+     * Returns the color of the light.
+     * 
+     * @return The color of the light.
+     */
+    public ColorRGBA getColor() {
+        return color;
+    }
+
+    /**
+     * This method sets the light name.
+     * 
+     * @param name the light name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Return the light name.
+     * 
+     * @return the light name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /*
+    public void setLastDistance(float lastDistance){
+        this.lastDistance = lastDistance;
+    }
+
+    public float getLastDistance(){
+        return lastDistance;
+    }
+    */
+
+    /**
+     * Sets the light color.
+     * 
+     * @param color the light color.
+     */
+    public void setColor(ColorRGBA color){
+        this.color.set(color);
+    }
+
+    
+    /*
+     * Returns true if the light is enabled
+     * 
+     * @return true if the light is enabled
+     * 
+     * @see Light#setEnabled(boolean)
+     */
+    /*
+    public boolean isEnabled() {
+        return enabled;
+    }
+    */
+    
+    @Override
+    public Light clone(){
+        try {
+            return (Light) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(color, "color", null);
+        oc.write(enabled, "enabled", true);
+        oc.write(name, "name", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        color = (ColorRGBA) ic.readSavable("color", null);
+        enabled = ic.readBoolean("enabled", true);
+        name = ic.readString("name", null);
+    }
+
+    /**
+     * Used internally to compute the last distance value.
+     */
+    protected abstract void computeLastDistance(Spatial owner);
+    
+    /**
+     * Returns the light type
+     * 
+     * @return the light type
+     * 
+     * @see Type
+     */
+    public abstract Type getType();
+
+}
diff --git a/engine/src/core/com/jme3/light/LightList.java b/engine/src/core/com/jme3/light/LightList.java
new file mode 100644
index 0000000..00b6b4d
--- /dev/null
+++ b/engine/src/core/com/jme3/light/LightList.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.export.*;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SortUtil;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * <code>LightList</code> is used internally by {@link Spatial}s to manage
+ * lights that are attached to them.
+ * 
+ * @author Kirill Vainer
+ */
+public final class LightList implements Iterable<Light>, Savable, Cloneable {
+
+    private Light[] list, tlist;
+    private float[] distToOwner;
+    private int listSize;
+    private Spatial owner;
+
+    private static final int DEFAULT_SIZE = 1;
+
+    private static final Comparator<Light> c = new Comparator<Light>() {
+        /**
+         * This assumes lastDistance have been computed in a previous step.
+         */
+        public int compare(Light l1, Light l2) {
+            if (l1.lastDistance < l2.lastDistance)
+                return -1;
+            else if (l1.lastDistance > l2.lastDistance)
+                return 1;
+            else
+                return 0;
+        }
+    };
+
+    /**
+     * Default constructor for serialization. Do not use
+     */
+    public LightList(){
+    }
+
+    /**
+     * Creates a <code>LightList</code> for the given {@link Spatial}.
+     * 
+     * @param owner The spatial owner
+     */
+    public LightList(Spatial owner) {
+        listSize = 0;
+        list = new Light[DEFAULT_SIZE];
+        distToOwner = new float[DEFAULT_SIZE];
+        Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
+        this.owner = owner;
+    }
+
+    /**
+     * Set the owner of the LightList. Only used for cloning.
+     * @param owner 
+     */
+    public void setOwner(Spatial owner){
+        this.owner = owner;
+    }
+
+    private void doubleSize(){
+        Light[] temp = new Light[list.length * 2];
+        float[] temp2 = new float[list.length * 2];
+        System.arraycopy(list, 0, temp, 0, list.length);
+        System.arraycopy(distToOwner, 0, temp2, 0, list.length);
+        list = temp;
+        distToOwner = temp2;
+    }
+
+    /**
+     * Adds a light to the list. List size is doubled if there is no room.
+     *
+     * @param l
+     *            The light to add.
+     */
+    public void add(Light l) {
+        if (listSize == list.length) {
+            doubleSize();
+        }
+        list[listSize] = l;
+        distToOwner[listSize++] = Float.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * Remove the light at the given index.
+     * 
+     * @param index
+     */
+    public void remove(int index){
+        if (index >= listSize || index < 0)
+            throw new IndexOutOfBoundsException();
+
+        listSize --;
+        if (index == listSize){
+            list[listSize] = null;
+            return;
+        }
+
+        for (int i = index; i < listSize; i++){
+            list[i] = list[i+1];
+        }
+        list[listSize] = null;
+    }
+
+    /**
+     * Removes the given light from the LightList.
+     * 
+     * @param l the light to remove
+     */
+    public void remove(Light l){
+        for (int i = 0; i < listSize; i++){
+            if (list[i] == l){
+                remove(i);
+                return;
+            }
+        }
+    }
+
+    /**
+     * @return The size of the list.
+     */
+    public int size(){
+        return listSize;
+    }
+
+    /**
+     * @return the light at the given index.
+     * @throws IndexOutOfBoundsException If the given index is outside bounds.
+     */
+    public Light get(int num){
+        if (num >= listSize || num < 0)
+            throw new IndexOutOfBoundsException();
+
+        return list[num];
+    }
+
+    /**
+     * Resets list size to 0.
+     */
+    public void clear() {
+        if (listSize == 0)
+            return;
+
+        for (int i = 0; i < listSize; i++)
+            list[i] = null;
+
+        if (tlist != null)
+            Arrays.fill(tlist, null);
+
+        listSize = 0;
+    }
+
+    /**
+     * Sorts the elements in the list acording to their Comparator.
+     * There are two reasons why lights should be resorted. 
+     * First, if the lights have moved, that means their distance to 
+     * the spatial changed. 
+     * Second, if the spatial itself moved, it means the distance from it to 
+     * the individual lights might have changed.
+     * 
+     *
+     * @param transformChanged Whether the spatial's transform has changed
+     */
+    public void sort(boolean transformChanged) {
+        if (listSize > 1) {
+            // resize or populate our temporary array as necessary
+            if (tlist == null || tlist.length != list.length) {
+                tlist = list.clone();
+            } else {
+                System.arraycopy(list, 0, tlist, 0, list.length);
+            }
+
+            if (transformChanged){
+                // check distance of each light
+                for (int i = 0; i < listSize; i++){
+                    list[i].computeLastDistance(owner);
+                }
+            }
+
+            // now merge sort tlist into list
+            SortUtil.msort(tlist, list, 0, listSize - 1, c);
+        }
+    }
+
+    /**
+     * Updates a "world-space" light list, using the spatial's local-space
+     * light list and its parent's world-space light list.
+     *
+     * @param local
+     * @param parent
+     */
+    public void update(LightList local, LightList parent){
+        // clear the list as it will be reconstructed
+        // using the arguments
+        clear();
+
+        while (list.length <= local.listSize){
+            doubleSize();
+        }
+
+        // add the lights from the local list
+        System.arraycopy(local.list, 0, list, 0, local.listSize);
+        for (int i = 0; i < local.listSize; i++){
+//            list[i] = local.list[i];
+            distToOwner[i] = Float.NEGATIVE_INFINITY;
+        }
+
+        // if the spatial has a parent node, add the lights
+        // from the parent list as well
+        if (parent != null){
+            int sz = local.listSize + parent.listSize;
+            while (list.length <= sz)
+                doubleSize();
+
+            for (int i = 0; i < parent.listSize; i++){
+                int p = i + local.listSize;
+                list[p] = parent.list[i];
+                distToOwner[p] = Float.NEGATIVE_INFINITY;
+            }
+            
+            listSize = local.listSize + parent.listSize;
+        }else{
+            listSize = local.listSize;
+        }
+    }
+
+    /**
+     * Returns an iterator that can be used to iterate over this LightList.
+     * 
+     * @return an iterator that can be used to iterate over this LightList.
+     */
+    public Iterator<Light> iterator() {
+        return new Iterator<Light>(){
+
+            int index = 0;
+
+            public boolean hasNext() {
+                return index < size();
+            }
+
+            public Light next() {
+                if (!hasNext())
+                    throw new NoSuchElementException();
+                
+                return list[index++];
+            }
+            
+            public void remove() {
+                LightList.this.remove(--index);
+            }
+        };
+    }
+
+    @Override
+    public LightList clone(){
+        try{
+            LightList clone = (LightList) super.clone();
+            
+            clone.owner = null;
+            clone.list = list.clone();
+            clone.distToOwner = distToOwner.clone();
+            clone.tlist = null; // list used for sorting only
+
+            return clone;
+        }catch (CloneNotSupportedException ex){
+            throw new AssertionError();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+//        oc.write(owner, "owner", null);
+
+        ArrayList<Light> lights = new ArrayList<Light>();
+        for (int i = 0; i < listSize; i++){
+            lights.add(list[i]);
+        }
+        oc.writeSavableArrayList(lights, "lights", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+//        owner = (Spatial) ic.readSavable("owner", null);
+
+        List<Light> lights = ic.readSavableArrayList("lights", null);
+        listSize = lights.size();
+        
+        // NOTE: make sure the array has a length of at least 1
+        int arraySize = Math.max(DEFAULT_SIZE, listSize);
+        list = new Light[arraySize];
+        distToOwner = new float[arraySize];
+
+        for (int i = 0; i < listSize; i++){
+            list[i] = lights.get(i);
+        }
+        
+        Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/light/PointLight.java b/engine/src/core/com/jme3/light/PointLight.java
new file mode 100644
index 0000000..e1deeac
--- /dev/null
+++ b/engine/src/core/com/jme3/light/PointLight.java
@@ -0,0 +1,157 @@
+/*

+ * Copyright (c) 2009-2010 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.light;

+

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Spatial;

+import java.io.IOException;

+

+/**

+ * Represents a point light.

+ * A point light emits light from a given position into all directions in space.

+ * E.g a lamp or a bright effect. Point light positions are in world space.

+ * <p>

+ * In addition to a position, point lights also have a radius which 

+ * can be used to attenuate the influence of the light depending on the 

+ * distance between the light and the effected object.

+ * 

+ */

+public class PointLight extends Light {

+

+    protected Vector3f position = new Vector3f();

+    protected float radius = 0;

+    protected float invRadius = 0;

+

+    @Override

+    public void computeLastDistance(Spatial owner) {

+        if (owner.getWorldBound() != null) {

+            BoundingVolume bv = owner.getWorldBound();

+            lastDistance = bv.distanceSquaredTo(position);

+        } else {

+            lastDistance = owner.getWorldTranslation().distanceSquared(position);

+        }

+    }

+

+    /**

+     * Returns the world space position of the light.

+     * 

+     * @return the world space position of the light.

+     * 

+     * @see PointLight#setPosition(com.jme3.math.Vector3f) 

+     */

+    public Vector3f getPosition() {

+        return position;

+    }

+

+    /**

+     * Set the world space position of the light.

+     * 

+     * @param position the world space position of the light.

+     */

+    public void setPosition(Vector3f position) {

+        this.position.set(position);

+    }

+

+    /**

+     * Returns the radius of the light influence. A radius of 0 means

+     * the light has no attenuation.

+     * 

+     * @return the radius of the light

+     */

+    public float getRadius() {

+        return radius;

+    }

+

+    /**

+     * Set the radius of the light influence.

+     * <p>

+     * Setting a non-zero radius indicates the light should use attenuation.

+     * If a pixel's distance to this light's position

+     * is greater than the light's radius, then the pixel will not be

+     * effected by this light, if the distance is less than the radius, then

+     * the magnitude of the influence is equal to distance / radius.

+     * 

+     * @param radius the radius of the light influence.

+     * 

+     * @throws IllegalArgumentException If radius is negative

+     */

+    public void setRadius(float radius) {

+        if (radius < 0) {

+            throw new IllegalArgumentException("Light radius cannot be negative");

+        }

+        this.radius = radius;

+        if(radius!=0){

+            this.invRadius = 1 / radius;

+        }else{

+            this.invRadius = 0;

+        }

+    }

+

+    /**

+     * for internal use only

+     * @return the inverse of the radius

+     */

+    public float getInvRadius() {

+        return invRadius;

+    }

+

+    @Override

+    public Light.Type getType() {

+        return Light.Type.Point;

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(position, "position", null);

+        oc.write(radius, "radius", 0f);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule ic = im.getCapsule(this);

+        position = (Vector3f) ic.readSavable("position", null);

+        radius = ic.readFloat("radius", 0f);

+        if(radius!=0){

+            this.invRadius = 1 / radius;

+        }else{

+            this.invRadius = 0;

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/light/SpotLight.java b/engine/src/core/com/jme3/light/SpotLight.java
new file mode 100644
index 0000000..4c653a5
--- /dev/null
+++ b/engine/src/core/com/jme3/light/SpotLight.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Represents a spot light.
+ * A spot light emmit a cone of light from a position and in a direction.
+ * It can be used to fake torch lights or car's lights.
+ * <p>
+ * In addition to a position and a direction, spot lights also have a range which 
+ * can be used to attenuate the influence of the light depending on the 
+ * distance between the light and the effected object.
+ * Also the angle of the cone can be tweaked by changing the spot inner angle and the spot outer angle.
+ * the spot inner angle determin the cone of light where light has full influence.
+ * the spot outer angle determin the cone global cone of light of the spot light.
+ * the light intensity slowly decrease between the inner cone and the outer cone.
+ *  @author Nehon
+ */
+public class SpotLight extends Light implements Savable {
+
+    protected Vector3f position = new Vector3f();
+    protected Vector3f direction = new Vector3f(0,-1,0);
+    protected float spotInnerAngle = FastMath.QUARTER_PI / 8;
+    protected float spotOuterAngle = FastMath.QUARTER_PI / 6;
+    protected float spotRange = 100;
+    protected float invSpotRange = 1 / 100;
+    protected float packedAngleCos=0;
+
+    public SpotLight() {
+        super();
+        computePackedCos();
+    }
+
+    private void computePackedCos() {
+        float innerCos=FastMath.cos(spotInnerAngle);
+        float outerCos=FastMath.cos(spotOuterAngle);
+        packedAngleCos=(int)(innerCos*1000);
+        packedAngleCos+=outerCos;
+    }
+
+    @Override
+    protected void computeLastDistance(Spatial owner) {
+        if (owner.getWorldBound() != null) {
+            BoundingVolume bv = owner.getWorldBound();
+            lastDistance = bv.distanceSquaredTo(position);
+        } else {
+            lastDistance = owner.getWorldTranslation().distanceSquared(position);
+        }
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Spot;
+    }
+
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    public void setDirection(Vector3f direction) {
+        this.direction.set(direction);
+    }
+
+    public Vector3f getPosition() {
+        return position;
+    }
+
+    public void setPosition(Vector3f position) {
+        this.position.set(position);
+    }
+
+    public float getSpotRange() {
+        return spotRange;
+    }
+
+    /**
+     * Set the range of the light influence.
+     * <p>
+     * Setting a non-zero range indicates the light should use attenuation.
+     * If a pixel's distance to this light's position
+     * is greater than the light's range, then the pixel will not be
+     * effected by this light, if the distance is less than the range, then
+     * the magnitude of the influence is equal to distance / range.
+     * 
+     * @param spotRange the range of the light influence.
+     * 
+     * @throws IllegalArgumentException If spotRange is negative
+     */
+    public void setSpotRange(float spotRange) {
+        if (spotRange < 0) {
+            throw new IllegalArgumentException("SpotLight range cannot be negative");
+        }
+        this.spotRange = spotRange;
+        if (spotRange != 0) {
+            this.invSpotRange = 1 / spotRange;
+        } else {
+            this.invSpotRange = 0;
+        }
+    }
+
+    /**
+     * for internal use only
+     * @return the inverse of the spot range
+     */
+    public float getInvSpotRange() {
+        return invSpotRange;
+    }
+
+    /**
+     * returns the spot inner angle
+     * @return the spot inner angle
+     */
+    public float getSpotInnerAngle() {        
+        return spotInnerAngle;
+    }
+
+    /**
+     * Sets the inner angle of the cone of influence.
+     * This angle is the angle between the spot direction axis and the inner border of the cone of influence.
+     * @param spotInnerAngle 
+     */
+    public void setSpotInnerAngle(float spotInnerAngle) {
+        this.spotInnerAngle = spotInnerAngle;
+        computePackedCos();
+    }
+
+    /**
+     * returns the spot outer angle
+     * @return the spot outer angle
+     */
+    public float getSpotOuterAngle() {
+        return spotOuterAngle;
+    }
+
+    /**
+     * Sets the outer angle of the cone of influence.
+     * This angle is the angle between the spot direction axis and the outer border of the cone of influence.
+     * this should be greater than the inner angle or the result will be unexpected.
+     * @param spotOuterAngle 
+     */
+    public void setSpotOuterAngle(float spotOuterAngle) {
+        this.spotOuterAngle = spotOuterAngle;
+        computePackedCos();
+    }
+
+    /**
+     * for internal use only
+     * @return the cosines of the inner and outter angle packed in a float
+     */
+    public float getPackedAngleCos() {
+        return packedAngleCos;
+    }
+    
+    
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(direction, "direction", new Vector3f());
+        oc.write(position, "position", new Vector3f());
+        oc.write(spotInnerAngle, "spotInnerAngle", FastMath.QUARTER_PI / 8);
+        oc.write(spotOuterAngle, "spotOuterAngle", FastMath.QUARTER_PI / 6);
+        oc.write(spotRange, "spotRange", 100);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        spotInnerAngle = ic.readFloat("spotInnerAngle", FastMath.QUARTER_PI / 8);
+        spotOuterAngle = ic.readFloat("spotOuterAngle", FastMath.QUARTER_PI / 6);
+        direction = (Vector3f) ic.readSavable("direction", new Vector3f());
+        position = (Vector3f) ic.readSavable("position", new Vector3f());
+        spotRange = ic.readFloat("spotRange", 100);
+        if (spotRange != 0) {
+            this.invSpotRange = 1 / spotRange;
+        } else {
+            this.invSpotRange = 0;
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/light/package.html b/engine/src/core/com/jme3/light/package.html
new file mode 100644
index 0000000..ac65816
--- /dev/null
+++ b/engine/src/core/com/jme3/light/package.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.light</code> package contains various lights that can be placed
+in a scene.
+<p>
+There are 3 types of lights currently supported in jMonkeyEngine:
+<ul>
+    <li>Point Light - Point lights emit from a certain location and have a certain influence radius (optional)</li>
+    <li>Directional Light - Directional lights will always appear to emit from a certain direction
+    regardless of an object's position</li>
+    <li>Ambient Light - Ambient lights have no location or direction; 
+        they simply emit a constant color that is applied to the entire scene</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/material/FixedFuncBinding.java b/engine/src/core/com/jme3/material/FixedFuncBinding.java
new file mode 100644
index 0000000..e316ad8
--- /dev/null
+++ b/engine/src/core/com/jme3/material/FixedFuncBinding.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+/**
+ * Fixed function binding is used to specify a binding for a {@link MatParam}
+ * in case that shaders are not supported on the system.
+ * 
+ * @author Kirill Vainer
+ */
+public enum FixedFuncBinding {
+    /**
+     * Specifies the material ambient color.
+     * Same as GL_AMBIENT for OpenGL.
+     */
+    MaterialAmbient,
+    
+    /**
+     * Specifies the material diffuse color.
+     * Same as GL_DIFFUSE for OpenGL.
+     */
+    MaterialDiffuse,
+    
+    /**
+     * Specifies the material specular color.
+     * Same as GL_SPECULAR for OpenGL
+     */
+    MaterialSpecular,
+    
+    /**
+     * Specifies the color of the object.
+     * <p>
+     * Used only for non-lit materials.
+     */
+    Color,
+    
+    /**
+     * Specifies the material shininess value.
+     * 
+     * Same as GL_SHININESS for OpenGL.
+     */
+    MaterialShininess,
+    
+    /**
+     * Use vertex color as an additional diffuse color, if lighting is enabled.
+     * If lighting is disabled, vertex color is modulated with
+     * {@link #Color material color}.
+     */
+    UseVertexColor
+}
diff --git a/engine/src/core/com/jme3/material/MatParam.java b/engine/src/core/com/jme3/material/MatParam.java
new file mode 100644
index 0000000..b0ef117
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MatParam.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.renderer.GL1Renderer;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.IOException;
+
+/**
+ * Describes a material parameter. This is used for both defining a name and type
+ * as well as a material parameter value.
+ *
+ * @author Kirill Vainer
+ */
+public class MatParam implements Savable, Cloneable {
+
+    protected VarType type;
+    protected String name;
+    protected String prefixedName;
+    protected Object value;
+    protected FixedFuncBinding ffBinding;
+
+    /**
+     * Create a new material parameter. For internal use only.
+     */
+    public MatParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) {
+        this.type = type;
+        this.name = name;
+        this.prefixedName = "m_" + name;
+        this.value = value;
+        this.ffBinding = ffBinding;
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public MatParam() {
+    }
+
+    /**
+     * Returns the fixed function binding.
+     *
+     * @return the fixed function binding.
+     */
+    public FixedFuncBinding getFixedFuncBinding() {
+        return ffBinding;
+    }
+
+    /**
+     * Returns the material parameter type.
+     *
+     * @return the material parameter type.
+     */
+    public VarType getVarType() {
+        return type;
+    }
+
+    /**
+     * Returns the name of the material parameter.
+     * @return the name of the material parameter.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the name with "m_" prefixed to it.
+     *
+     * @return the name with "m_" prefixed to it
+     */
+    public String getPrefixedName() {
+        return prefixedName;
+    }
+
+    /**
+     * Used internally
+     * @param name
+     */
+    void setName(String name) {
+        this.name = name;
+        this.prefixedName = "m_" + name;
+    }
+
+    /**
+     * Returns the value of this material parameter.
+     * <p>
+     * Material parameters that are used for material definitions
+     * will not have a value, unless there's a default value declared
+     * in the definition.
+     *
+     * @return the value of this material parameter.
+     */
+    public Object getValue() {
+        return value;
+    }
+
+    /**
+     * Sets the value of this material parameter.
+     * <p>
+     * It is assumed the value is of the same {@link MatParam#getVarType() type}
+     * as this material parameter.
+     *
+     * @param value the value of this material parameter.
+     */
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    void apply(Renderer r, Technique technique) {
+        TechniqueDef techDef = technique.getDef();
+        if (techDef.isUsingShaders()) {
+            technique.updateUniformParam(getPrefixedName(), getVarType(), getValue(), true);
+        }
+        if (ffBinding != null && r instanceof GL1Renderer) {
+            ((GL1Renderer) r).setFixedFuncBinding(ffBinding, getValue());
+        }
+    }
+
+    /**
+     * Returns the material parameter value as it would appear in a J3M
+     * file. E.g.<br/>
+     * <code>
+     * MaterialParameters {<br/>
+     *     ABC : 1 2 3 4<br/>
+     * }<br/>
+     * </code>
+     * Assuming "ABC" is a Vector4 parameter, then the value
+     * "1 2 3 4" would be returned by this method.
+     * <br/><br/>
+     * @return material parameter value as it would appear in a J3M file.
+     */
+    public String getValueAsString() {
+        switch (type) {
+            case Boolean:
+            case Float:
+            case Int:
+                return value.toString();
+            case Vector2:
+                Vector2f v2 = (Vector2f) value;
+                return v2.getX() + " " + v2.getY();
+/* 
+This may get used at a later point of time
+When arrays can be inserted in J3M files
+
+            case Vector2Array:
+                Vector2f[] v2Arr = (Vector2f[]) value;
+                String v2str = "";
+                for (int i = 0; i < v2Arr.length ; i++) {
+                    v2str += v2Arr[i].getX() + " " + v2Arr[i].getY() + "\n";
+                }
+                return v2str;
+*/
+            case Vector3:
+                Vector3f v3 = (Vector3f) value;
+                return v3.getX() + " " + v3.getY() + " " + v3.getZ();
+/*
+            case Vector3Array:
+                Vector3f[] v3Arr = (Vector3f[]) value;
+                String v3str = "";
+                for (int i = 0; i < v3Arr.length ; i++) {
+                    v3str += v3Arr[i].getX() + " "
+                            + v3Arr[i].getY() + " "
+                            + v3Arr[i].getZ() + "\n";
+                }
+                return v3str;
+            case Vector4Array:
+                // can be either ColorRGBA, Vector4f or Quaternion
+                if (value instanceof Vector4f) {
+                    Vector4f[] v4arr = (Vector4f[]) value;
+                    String v4str = "";
+                    for (int i = 0; i < v4arr.length ; i++) {
+                        v4str += v4arr[i].getX() + " "
+                                + v4arr[i].getY() + " "
+                                + v4arr[i].getZ() + " "
+                                + v4arr[i].getW() + "\n";
+                    }
+                    return v4str;
+                } else if (value instanceof ColorRGBA) {
+                    ColorRGBA[] colorArr = (ColorRGBA[]) value;
+                    String colStr = "";
+                    for (int i = 0; i < colorArr.length ; i++) {
+                        colStr += colorArr[i].getRed() + " "
+                                + colorArr[i].getGreen() + " "
+                                + colorArr[i].getBlue() + " "
+                                + colorArr[i].getAlpha() + "\n";
+                    }
+                    return colStr;
+                } else if (value instanceof Quaternion) {
+                    Quaternion[] quatArr = (Quaternion[]) value;
+                    String quatStr = "";
+                    for (int i = 0; i < quatArr.length ; i++) {
+                        quatStr += quatArr[i].getX() + " "
+                                + quatArr[i].getY() + " "
+                                + quatArr[i].getZ() + " "
+                                + quatArr[i].getW() + "\n";
+                    }
+                    return quatStr;
+                } else {
+                    throw new UnsupportedOperationException("Unexpected Vector4Array type: " + value);
+                }
+*/
+            case Vector4:
+                // can be either ColorRGBA, Vector4f or Quaternion
+                if (value instanceof Vector4f) {
+                    Vector4f v4 = (Vector4f) value;
+                    return v4.getX() + " " + v4.getY() + " "
+                            + v4.getZ() + " " + v4.getW();
+                } else if (value instanceof ColorRGBA) {
+                    ColorRGBA color = (ColorRGBA) value;
+                    return color.getRed() + " " + color.getGreen() + " "
+                            + color.getBlue() + " " + color.getAlpha();
+                } else if (value instanceof Quaternion) {
+                    Quaternion quat = (Quaternion) value;
+                    return quat.getX() + " " + quat.getY() + " "
+                            + quat.getZ() + " " + quat.getW();
+                } else {
+                    throw new UnsupportedOperationException("Unexpected Vector4 type: " + value);
+                }
+            case Texture2D:
+            case Texture3D:
+            case TextureArray:
+            case TextureBuffer:
+            case TextureCubeMap:
+                Texture texVal = (Texture) value;
+                TextureKey texKey = (TextureKey) texVal.getKey();
+                if (texKey == null){
+                    throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+                }
+
+                String ret = "";
+                if (texKey.isFlipY()) {
+                    ret += "Flip ";
+                }
+                if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
+                    ret += "Repeat ";
+                }
+
+                return ret + texKey.getName();
+            default:
+                return null; // parameter type not supported in J3M
+        }
+    }
+
+    @Override
+    public MatParam clone() {
+        try {
+            MatParam param = (MatParam) super.clone();
+            return param;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(type, "varType", null);
+        oc.write(name, "name", null);
+        oc.write(ffBinding, "ff_binding", null);
+        if (value instanceof Savable) {
+            Savable s = (Savable) value;
+            oc.write(s, "value_savable", null);
+        } else if (value instanceof Float) {
+            Float f = (Float) value;
+            oc.write(f.floatValue(), "value_float", 0f);
+        } else if (value instanceof Integer) {
+            Integer i = (Integer) value;
+            oc.write(i.intValue(), "value_int", 0);
+        } else if (value instanceof Boolean) {
+            Boolean b = (Boolean) value;
+            oc.write(b.booleanValue(), "value_bool", false);
+        }
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        type = ic.readEnum("varType", VarType.class, null);
+        name = ic.readString("name", null);
+        ffBinding = ic.readEnum("ff_binding", FixedFuncBinding.class, null);
+        switch (getVarType()) {
+            case Boolean:
+                value = ic.readBoolean("value_bool", false);
+                break;
+            case Float:
+                value = ic.readFloat("value_float", 0f);
+                break;
+            case Int:
+                value = ic.readInt("value_int", 0);
+                break;
+            default:
+                value = ic.readSavable("value_savable", null);
+                break;
+        }
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof MatParam)) {
+            return false;
+        }
+
+        MatParam otherParam = (MatParam) other;
+        return otherParam.type == type
+                && otherParam.name.equals(name);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 17 * hash + (this.type != null ? this.type.hashCode() : 0);
+        hash = 17 * hash + (this.name != null ? this.name.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public String toString() {
+        return type.name() + " " + name + " : " + getValueAsString();
+    }
+}
diff --git a/engine/src/core/com/jme3/material/MatParamTexture.java b/engine/src/core/com/jme3/material/MatParamTexture.java
new file mode 100644
index 0000000..fc8b469
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MatParamTexture.java
@@ -0,0 +1,67 @@
+package com.jme3.material;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+
+public class MatParamTexture extends MatParam {
+
+    private Texture texture;
+    private int unit;
+
+    public MatParamTexture(VarType type, String name, Texture texture, int unit) {
+        super(type, name, texture, null);
+        this.texture = texture;
+        this.unit = unit;
+    }
+
+    public MatParamTexture() {
+    }
+
+    public Texture getTextureValue() {
+        return texture;
+    }
+
+    public void setTextureValue(Texture value) {
+        this.value = value;
+        this.texture = value;
+    }
+
+    public void setUnit(int unit) {
+        this.unit = unit;
+    }
+
+    public int getUnit() {
+        return unit;
+    }
+
+    @Override
+    public void apply(Renderer r, Technique technique) {
+        TechniqueDef techDef = technique.getDef();
+        r.setTexture(getUnit(), getTextureValue());
+        if (techDef.isUsingShaders()) {
+            technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit(), true);
+        }
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(unit, "texture_unit", -1);
+        oc.write(texture, "texture", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        unit = ic.readInt("texture_unit", -1);
+        texture = (Texture) ic.readSavable("texture", null);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/material/Material.java b/engine/src/core/com/jme3/material/Material.java
new file mode 100644
index 0000000..8d43853
--- /dev/null
+++ b/engine/src/core/com/jme3/material/Material.java
@@ -0,0 +1,1152 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.light.*;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.material.TechniqueDef.LightMode;
+import com.jme3.material.TechniqueDef.ShadowMode;
+import com.jme3.math.*;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.util.ListMap;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Material</code> describes the rendering style for a given
+ * {@link Geometry}.
+ * <p>A material is essentially a list of {@link MatParam parameters},
+ * those parameters map to uniforms which are defined in a shader.
+ * Setting the parameters can modify the behavior of a
+ * shader.
+ * <p/>
+ * @author Kirill Vainer
+ */
+public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
+
+    // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
+    public static final int SAVABLE_VERSION = 2;
+    
+    private static final Logger logger = Logger.getLogger(Material.class.getName());
+    private static final RenderState additiveLight = new RenderState();
+    private static final RenderState depthOnly = new RenderState();
+    private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
+
+    static {
+        depthOnly.setDepthTest(true);
+        depthOnly.setDepthWrite(true);
+        depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
+        depthOnly.setColorWrite(false);
+
+        additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+        additiveLight.setDepthWrite(false);
+    }
+    private AssetKey key;
+    private String name;
+    private MaterialDef def;
+    private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
+    private Technique technique;
+    private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
+    private int nextTexUnit = 0;
+    private RenderState additionalState = null;
+    private RenderState mergedRenderState = new RenderState();
+    private boolean transparent = false;
+    private boolean receivesShadows = false;
+    private int sortingId = -1;
+    private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+
+    public Material(MaterialDef def) {
+        if (def == null) {
+            throw new NullPointerException("Material definition cannot be null");
+        }
+        this.def = def;
+
+        // Load default values from definition (if any)
+        for (MatParam param : def.getMaterialParams()){
+            if (param.getValue() != null){
+                setParam(param.getName(), param.getVarType(), param.getValue());
+            }
+        }
+    }
+
+    public Material(AssetManager contentMan, String defName) {
+        this((MaterialDef) contentMan.loadAsset(new AssetKey(defName)));
+    }
+
+    /**
+     * Do not use this constructor. Serialization purposes only.
+     */
+    public Material() {
+    }
+
+    /**
+     * Returns the asset key name of the asset from which this material was loaded.
+     *
+     * <p>This value will be <code>null</code> unless this material was loaded
+     * from a .j3m file.
+     *
+     * @return Asset key name of the j3m file
+     */
+    public String getAssetName() {
+        return key != null ? key.getName() : null;
+    }
+    
+    /**
+     * @return the name of the material (not the same as the asset name), the returned value can be null
+     */
+    public String getName() {
+		return name;
+	}
+    
+    /**
+     * This method sets the name of the material.
+     * The name is not the same as the asset name.
+     * It can be null and there is no guarantee of its uniqness.
+     * @param name the name of the material
+     */
+    public void setName(String name) {
+		this.name = name;
+	}
+
+    public void setKey(AssetKey key) {
+        this.key = key;
+    }
+
+    public AssetKey getKey() {
+        return key;
+    }
+
+    /**
+     * Returns the sorting ID or sorting index for this material.
+     *
+     * <p>The sorting ID is used internally by the system to sort rendering
+     * of geometries. It sorted to reduce shader switches, if the shaders
+     * are equal, then it is sorted by textures.
+     *
+     * @return The sorting ID used for sorting geometries for rendering.
+     */
+    public int getSortId() {
+        Technique t = getActiveTechnique();
+        if (sortingId == -1 && t != null && t.getShader() != null) {
+            int texId = -1;
+            for (int i = 0; i < paramValues.size(); i++) {
+                MatParam param = paramValues.getValue(i);
+                if (param instanceof MatParamTexture) {
+                    MatParamTexture tex = (MatParamTexture) param;
+                    if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
+                        if (texId == -1) {
+                            texId = 0;
+                        }
+                        texId += tex.getTextureValue().getImage().getId() % 0xff;
+                    }
+                }
+            }
+            sortingId = texId + t.getShader().getId() * 1000;
+        }
+        return sortingId;
+    }
+
+    /**
+     * Uses the sorting ID for each material to compare them.
+     *
+     * @param m The other material to compare to.
+     *
+     * @return zero if the materials are equal, returns a negative value
+     * if <code>this</code> has a lower sorting ID than <code>m</code>,
+     * otherwise returns a positive value.
+     */
+    public int compareTo(Material m) {
+        return m.getSortId() - getSortId();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if(obj instanceof Material){
+            return ((Material)obj).compareTo(this) == 0;
+        }
+        return super.equals(obj);
+    }
+    
+    /**
+     * Clones this material. The result is returned.
+     */
+    @Override
+    public Material clone() {
+        try {
+            Material mat = (Material) super.clone();
+
+            if (additionalState != null) {
+                mat.additionalState = additionalState.clone();
+            }
+            mat.technique = null;
+            mat.techniques = new HashMap<String, Technique>();
+
+            mat.paramValues = new ListMap<String, MatParam>();
+            for (int i = 0; i < paramValues.size(); i++) {
+                Map.Entry<String, MatParam> entry = paramValues.getEntry(i);
+                mat.paramValues.put(entry.getKey(), entry.getValue().clone());
+            }
+
+            return mat;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Returns the currently active technique.
+     * <p>
+     * The technique is selected automatically by the {@link RenderManager}
+     * based on system capabilities. Users may select their own
+     * technique by using
+     * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
+     *
+     * @return the currently active technique.
+     *
+     * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
+     */
+    public Technique getActiveTechnique() {
+        return technique;
+    }
+
+    /**
+     * Check if the transparent value marker is set on this material.
+     * @return True if the transparent value marker is set on this material.
+     * @see #setTransparent(boolean)
+     */
+    public boolean isTransparent() {
+        return transparent;
+    }
+
+    /**
+     * Set the transparent value marker.
+     *
+     * <p>This value is merely a marker, by itself it does nothing.
+     * Generally model loaders will use this marker to indicate further
+     * up that the material is transparent and therefore any geometries
+     * using it should be put into the {@link Bucket#Transparent transparent
+     * bucket}.
+     *
+     * @param transparent the transparent value marker.
+     */
+    public void setTransparent(boolean transparent) {
+        this.transparent = transparent;
+    }
+
+    /**
+     * Check if the material should receive shadows or not.
+     *
+     * @return True if the material should receive shadows.
+     *
+     * @see Material#setReceivesShadows(boolean)
+     */
+    public boolean isReceivesShadows() {
+        return receivesShadows;
+    }
+
+    /**
+     * Set if the material should receive shadows or not.
+     *
+     * <p>This value is merely a marker, by itself it does nothing.
+     * Generally model loaders will use this marker to indicate
+     * the material should receive shadows and therefore any
+     * geometries using it should have the {@link ShadowMode#Receive} set
+     * on them.
+     *
+     * @param receivesShadows if the material should receive shadows or not.
+     */
+    public void setReceivesShadows(boolean receivesShadows) {
+        this.receivesShadows = receivesShadows;
+    }
+
+    /**
+     * Acquire the additional {@link RenderState render state} to apply
+     * for this material.
+     *
+     * <p>The first call to this method will create an additional render
+     * state which can be modified by the user to apply any render
+     * states in addition to the ones used by the renderer. Only render
+     * states which are modified in the additional render state will be applied.
+     *
+     * @return The additional render state.
+     */
+    public RenderState getAdditionalRenderState() {
+        if (additionalState == null) {
+            additionalState = RenderState.ADDITIONAL.clone();
+        }
+        return additionalState;
+    }
+
+    /**
+     * Get the material definition (j3md file info) that <code>this</code>
+     * material is implementing.
+     *
+     * @return the material definition this material implements.
+     */
+    public MaterialDef getMaterialDef() {
+        return def;
+    }
+
+    /**
+     * Returns the parameter set on this material with the given name,
+     * returns <code>null</code> if the parameter is not set.
+     *
+     * @param name The parameter name to look up.
+     * @return The MatParam if set, or null if not set.
+     */
+    public MatParam getParam(String name) {
+        MatParam param = paramValues.get(name);
+        return param;
+    }
+
+    /**
+     * Returns the texture parameter set on this material with the given name,
+     * returns <code>null</code> if the parameter is not set.
+     *
+     * @param name The parameter name to look up.
+     * @return The MatParamTexture if set, or null if not set.
+     */
+    public MatParamTexture getTextureParam(String name) {
+        MatParam param = paramValues.get(name);
+        if (param instanceof MatParamTexture) {
+            return (MatParamTexture) param;
+        }
+        return null;
+    }
+
+    /**
+     * Returns a collection of all parameters set on this material.
+     *
+     * @return a collection of all parameters set on this material.
+     *
+     * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
+     */
+    public Collection<MatParam> getParams() {
+        return paramValues.values();
+    }
+
+    private String checkSetParam(VarType type, String name) {
+        MatParam paramDef = def.getMaterialParam(name);
+        String newName = name;
+
+        if (paramDef == null && name.startsWith("m_")) {
+            newName = name.substring(2);
+            paramDef = def.getMaterialParam(newName);
+            if (paramDef == null) {
+                throw new IllegalArgumentException("Material parameter is not defined: " + name);
+            } else {
+                logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName});
+            }
+        } else if (paramDef == null) {
+            throw new IllegalArgumentException("Material parameter is not defined: " + name);
+        }
+
+        if (type != null && paramDef.getVarType() != type) {
+            logger.log(Level.WARNING, "Material parameter being set: {0} with "
+                    + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
+        }
+
+        return newName;
+    }
+
+    /**
+     * Pass a parameter to the material shader.
+     *
+     * @param name the name of the parameter defined in the material definition (j3md)
+     * @param type the type of the parameter {@link VarType}
+     * @param value the value of the parameter
+     */
+    public void setParam(String name, VarType type, Object value) {
+        name = checkSetParam(type, name);
+
+        MatParam val = getParam(name);
+        if (technique != null) {
+            technique.notifySetParam(name, type, value);
+        }
+        if (val == null) {
+            MatParam paramDef = def.getMaterialParam(name);
+            paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding()));
+        } else {
+            val.setValue(value);
+        }
+    }
+
+    /**
+     * Clear a parameter from this material. The parameter must exist
+     * @param name the name of the parameter to clear
+     */
+    public void clearParam(String name) {
+        //On removal, we don't check if the param exists in the paramDef, and just go on with the process.
+        // name = checkSetParam(null, name);
+
+        MatParam matParam = getParam(name);
+        if (matParam != null) {
+            paramValues.remove(name);
+            if (technique != null) {
+                technique.notifyClearParam(name);
+            }
+            if (matParam instanceof MatParamTexture) {
+                int texUnit = ((MatParamTexture) matParam).getUnit();
+                nextTexUnit--;
+                for (MatParam param : paramValues.values()) {
+                    if (param instanceof MatParamTexture) {
+                        MatParamTexture texParam = (MatParamTexture) param;
+                        if (texParam.getUnit() > texUnit) {
+                            texParam.setUnit(texParam.getUnit() - 1);
+                        }
+                    }
+                }
+            }
+        }
+//        else {
+//            throw new IllegalArgumentException("The given parameter is not set.");
+//        }
+    }
+
+    private void clearTextureParam(String name) {
+        name = checkSetParam(null, name);
+
+        MatParamTexture val = getTextureParam(name);
+        if (val == null) {
+            throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null.");
+        }
+
+        int texUnit = val.getUnit();
+        paramValues.remove(name);
+        nextTexUnit--;
+        for (MatParam param : paramValues.values()) {
+            if (param instanceof MatParamTexture) {
+                MatParamTexture texParam = (MatParamTexture) param;
+                if (texParam.getUnit() > texUnit) {
+                    texParam.setUnit(texParam.getUnit() - 1);
+                }
+            }
+        }
+
+        sortingId = -1;
+    }
+
+    /**
+     * Set a texture parameter.
+     *
+     * @param name The name of the parameter
+     * @param type The variable type {@link VarType}
+     * @param value The texture value of the parameter.
+     *
+     * @throws IllegalArgumentException is value is null
+     */
+    public void setTextureParam(String name, VarType type, Texture value) {
+        if (value == null) {
+            throw new IllegalArgumentException();
+        }
+
+        name = checkSetParam(type, name);
+        MatParamTexture val = getTextureParam(name);
+        if (val == null) {
+            paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++));
+        } else {
+            val.setTextureValue(value);
+        }
+
+        if (technique != null) {
+            technique.notifySetParam(name, type, nextTexUnit - 1);
+        }
+
+        // need to recompute sort ID
+        sortingId = -1;
+    }
+
+    /**
+     * Pass a texture to the material shader.
+     *
+     * @param name the name of the texture defined in the material definition
+     * (j3md) (for example Texture for Lighting.j3md)
+     * @param value the Texture object previously loaded by the asset manager
+     */
+    public void setTexture(String name, Texture value) {
+        if (value == null) {
+            // clear it
+            clearTextureParam(name);
+            return;
+        }
+
+        VarType paramType = null;
+        switch (value.getType()) {
+            case TwoDimensional:
+                paramType = VarType.Texture2D;
+                break;
+            case TwoDimensionalArray:
+                paramType = VarType.TextureArray;
+                break;
+            case ThreeDimensional:
+                paramType = VarType.Texture3D;
+                break;
+            case CubeMap:
+                paramType = VarType.TextureCubeMap;
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
+        }
+
+        setTextureParam(name, paramType, value);
+    }
+
+    /**
+     * Pass a Matrix4f to the material shader.
+     *
+     * @param name the name of the matrix defined in the material definition (j3md)
+     * @param value the Matrix4f object
+     */
+    public void setMatrix4(String name, Matrix4f value) {
+        setParam(name, VarType.Matrix4, value);
+    }
+
+    /**
+     * Pass a boolean to the material shader.
+     *
+     * @param name the name of the boolean defined in the material definition (j3md)
+     * @param value the boolean value
+     */
+    public void setBoolean(String name, boolean value) {
+        setParam(name, VarType.Boolean, value);
+    }
+
+    /**
+     * Pass a float to the material shader.
+     *
+     * @param name the name of the float defined in the material definition (j3md)
+     * @param value the float value
+     */
+    public void setFloat(String name, float value) {
+        setParam(name, VarType.Float, value);
+    }
+
+    /**
+     * Pass an int to the material shader.
+     *
+     * @param name the name of the int defined in the material definition (j3md)
+     * @param value the int value
+     */
+    public void setInt(String name, int value) {
+        setParam(name, VarType.Int, value);
+    }
+
+    /**
+     * Pass a Color to the material shader.
+     *
+     * @param name the name of the color defined in the material definition (j3md)
+     * @param value the ColorRGBA value
+     */
+    public void setColor(String name, ColorRGBA value) {
+        setParam(name, VarType.Vector4, value);
+    }
+
+    /**
+     * Pass a Vector2f to the material shader.
+     *
+     * @param name the name of the Vector2f defined in the material definition (j3md)
+     * @param value the Vector2f value
+     */
+    public void setVector2(String name, Vector2f value) {
+        setParam(name, VarType.Vector2, value);
+    }
+
+    /**
+     * Pass a Vector3f to the material shader.
+     *
+     * @param name the name of the Vector3f defined in the material definition (j3md)
+     * @param value the Vector3f value
+     */
+    public void setVector3(String name, Vector3f value) {
+        setParam(name, VarType.Vector3, value);
+    }
+
+    /**
+     * Pass a Vector4f to the material shader.
+     *
+     * @param name the name of the Vector4f defined in the material definition (j3md)
+     * @param value the Vector4f value
+     */
+    public void setVector4(String name, Vector4f value) {
+        setParam(name, VarType.Vector4, value);
+    }
+
+    private ColorRGBA getAmbientColor(LightList lightList) {
+        ambientLightColor.set(0, 0, 0, 1);
+        for (int j = 0; j < lightList.size(); j++) {
+            Light l = lightList.get(j);
+            if (l instanceof AmbientLight) {
+                ambientLightColor.addLocal(l.getColor());
+            }
+        }
+        ambientLightColor.a = 1.0f;
+        return ambientLightColor;
+    }
+
+    /**
+     * Uploads the lights in the light list as two uniform arrays.<br/><br/>
+     *      * <p>
+     * <code>uniform vec4 g_LightColor[numLights];</code><br/>
+     * // g_LightColor.rgb is the diffuse/specular color of the light.<br/>
+     * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/>
+     * // 2 = Spot. <br/>
+     * <br/>
+     * <code>uniform vec4 g_LightPosition[numLights];</code><br/>
+     * // g_LightPosition.xyz is the position of the light (for point lights)<br/>
+     * // or the direction of the light (for directional lights).<br/>
+     * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/>
+     * </p>
+     */
+    protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) {
+        if (numLights == 0) { // this shader does not do lighting, ignore.
+            return;
+        }
+
+        LightList lightList = g.getWorldLightList();
+        Uniform lightColor = shader.getUniform("g_LightColor");
+        Uniform lightPos = shader.getUniform("g_LightPosition");
+        Uniform lightDir = shader.getUniform("g_LightDirection");
+        lightColor.setVector4Length(numLights);
+        lightPos.setVector4Length(numLights);
+        lightDir.setVector4Length(numLights);
+
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+
+        int lightIndex = 0;
+
+        for (int i = 0; i < numLights; i++) {
+            if (lightList.size() <= i) {
+                lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+                lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+            } else {
+                Light l = lightList.get(i);
+                ColorRGBA color = l.getColor();
+                lightColor.setVector4InArray(color.getRed(),
+                        color.getGreen(),
+                        color.getBlue(),
+                        l.getType().getId(),
+                        i);
+
+                switch (l.getType()) {
+                    case Directional:
+                        DirectionalLight dl = (DirectionalLight) l;
+                        Vector3f dir = dl.getDirection();
+                        lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex);
+                        break;
+                    case Point:
+                        PointLight pl = (PointLight) l;
+                        Vector3f pos = pl.getPosition();
+                        float invRadius = pl.getInvRadius();
+                        lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex);
+                        break;
+                    case Spot:
+                        SpotLight sl = (SpotLight) l;
+                        Vector3f pos2 = sl.getPosition();
+                        Vector3f dir2 = sl.getDirection();
+                        float invRange = sl.getInvSpotRange();
+                        float spotAngleCos = sl.getPackedAngleCos();
+
+                        lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex);
+                        lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex);
+                        break;
+                    case Ambient:
+                        // skip this light. Does not increase lightIndex
+                        continue;
+                    default:
+                        throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+                }
+            }
+
+            lightIndex++;
+        }
+
+        while (lightIndex < numLights) {
+            lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+            lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+
+            lightIndex++;
+        }
+    }
+
+    protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) {
+
+        Renderer r = rm.getRenderer();
+        LightList lightList = g.getWorldLightList();
+        Uniform lightDir = shader.getUniform("g_LightDirection");
+        Uniform lightColor = shader.getUniform("g_LightColor");
+        Uniform lightPos = shader.getUniform("g_LightPosition");
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        boolean isFirstLight = true;
+        boolean isSecondLight = false;
+
+        for (int i = 0; i < lightList.size(); i++) {
+            Light l = lightList.get(i);
+            if (l instanceof AmbientLight) {
+                continue;
+            }
+
+            if (isFirstLight) {
+                // set ambient color for first light only
+                ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+                isFirstLight = false;
+                isSecondLight = true;
+            } else if (isSecondLight) {
+                ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+                // apply additive blending for 2nd and future lights
+                r.applyRenderState(additiveLight);
+                isSecondLight = false;
+            }
+
+            TempVars vars = TempVars.get();
+            Quaternion tmpLightDirection = vars.quat1;
+            Quaternion tmpLightPosition = vars.quat2;
+            ColorRGBA tmpLightColor = vars.color;
+            Vector4f tmpVec = vars.vect4f;
+
+            ColorRGBA color = l.getColor();
+            tmpLightColor.set(color);
+            tmpLightColor.a = l.getType().getId();
+            lightColor.setValue(VarType.Vector4, tmpLightColor);
+
+            switch (l.getType()) {
+                case Directional:
+                    DirectionalLight dl = (DirectionalLight) l;
+                    Vector3f dir = dl.getDirection();
+
+                    tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Point:
+                    PointLight pl = (PointLight) l;
+                    Vector3f pos = pl.getPosition();
+                    float invRadius = pl.getInvRadius();
+
+                    tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Spot:
+                    SpotLight sl = (SpotLight) l;
+                    Vector3f pos2 = sl.getPosition();
+                    Vector3f dir2 = sl.getDirection();
+                    float invRange = sl.getInvSpotRange();
+                    float spotAngleCos = sl.getPackedAngleCos();
+
+                    tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+
+                    //We transform the spot directoin in view space here to save 5 varying later in the lighting shader
+                    //one vec4 less and a vec4 that becomes a vec3
+                    //the downside is that spotAngleCos decoding happen now in the frag shader.
+                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0);
+                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
+
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+            }
+            vars.release();
+            r.setShader(shader);
+            r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+        }
+
+        if (isFirstLight && lightList.size() > 0) {
+            // There are only ambient lights in the scene. Render
+            // a dummy "normal light" so we can see the ambient
+            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+            lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
+            lightPos.setValue(VarType.Vector4, nullDirLight);
+            r.setShader(shader);
+            r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+        }
+    }
+
+    /**
+     * Select the technique to use for rendering this material.
+     * <p>
+     * If <code>name</code> is "Default", then one of the
+     * {@link MaterialDef#getDefaultTechniques() default techniques}
+     * on the material will be selected. Otherwise, the named technique
+     * will be found in the material definition.
+     * <p>
+     * Any candidate technique for selection (either default or named)
+     * must be verified to be compatible with the system, for that, the
+     * <code>renderManager</code> is queried for capabilities.
+     *
+     * @param name The name of the technique to select, pass "Default" to
+     * select one of the default techniques.
+     * @param renderManager The {@link RenderManager render manager}
+     * to query for capabilities.
+     *
+     * @throws IllegalArgumentException If "Default" is passed and no default
+     * techniques are available on the material definition, or if a name
+     * is passed but there's no technique by that name.
+     * @throws UnsupportedOperationException If no candidate technique supports
+     * the system capabilities.
+     */
+    public void selectTechnique(String name, RenderManager renderManager) {
+        // check if already created
+        Technique tech = techniques.get(name);
+        if (tech == null) {
+            // When choosing technique, we choose one that
+            // supports all the caps.
+            EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
+
+            if (name.equals("Default")) {
+                List<TechniqueDef> techDefs = def.getDefaultTechniques();
+                if (techDefs == null || techDefs.isEmpty()) {
+                    throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
+                }
+
+                TechniqueDef lastTech = null;
+                for (TechniqueDef techDef : techDefs) {
+                    if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
+                        // use the first one that supports all the caps
+                        tech = new Technique(this, techDef);
+                        techniques.put(name, tech);
+                        break;
+                    }
+                    lastTech = techDef;
+                }
+                if (tech == null) {
+                    throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
+                            + " is supported by the video hardware. The caps "
+                            + lastTech.getRequiredCaps() + " are required.");
+                }
+
+            } else {
+                // create "special" technique instance
+                TechniqueDef techDef = def.getTechniqueDef(name);
+                if (techDef == null) {
+                    throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
+                }
+
+                if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
+                    throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
+                            + "requires caps " + techDef.getRequiredCaps() + " which are not "
+                            + "supported by the video renderer");
+                }
+
+                tech = new Technique(this, techDef);
+                techniques.put(name, tech);
+            }
+        } else if (technique == tech) {
+            // attempting to switch to an already
+            // active technique.
+            return;
+        }
+
+        technique = tech;
+        tech.makeCurrent(def.getAssetManager());
+
+        // shader was changed
+        sortingId = -1;
+    }
+
+    private void autoSelectTechnique(RenderManager rm) {
+        if (technique == null) {
+            // NOTE: Not really needed anymore since we have technique
+            // selection by caps. Rename all "FixedFunc" techniques to "Default"
+            // and remove this hack.
+            if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) {
+                selectTechnique("FixedFunc", rm);
+            } else {
+                selectTechnique("Default", rm);
+            }
+        } else if (technique.isNeedReload()) {
+            technique.makeCurrent(def.getAssetManager());
+        }
+    }
+
+    /**
+     * Preloads this material for the given render manager.
+     * <p>
+     * Preloading the material can ensure that when the material is first
+     * used for rendering, there won't be any delay since the material has
+     * been already been setup for rendering.
+     *
+     * @param rm The render manager to preload for
+     */
+    public void preload(RenderManager rm) {
+        autoSelectTechnique(rm);
+
+        Renderer r = rm.getRenderer();
+        TechniqueDef techDef = technique.getDef();
+
+        Collection<MatParam> params = paramValues.values();
+        for (MatParam param : params) {
+            if (param instanceof MatParamTexture) {
+                MatParamTexture texParam = (MatParamTexture) param;
+                r.setTexture(0, texParam.getTextureValue());
+            } else {
+                if (!techDef.isUsingShaders()) {
+                    continue;
+                }
+
+                technique.updateUniformParam(param.getName(),
+                        param.getVarType(),
+                        param.getValue(), true);
+            }
+        }
+
+        Shader shader = technique.getShader();
+        if (techDef.isUsingShaders()) {
+            r.setShader(shader);
+        }
+    }
+
+    private void clearUniformsSetByCurrent(Shader shader) {
+        ListMap<String, Uniform> uniforms = shader.getUniformMap();
+        int size = uniforms.size();
+        for (int i = 0; i < size; i++) {
+            Uniform u = uniforms.getValue(i);
+            u.clearSetByCurrentMaterial();
+        }
+    }
+
+    private void resetUniformsNotSetByCurrent(Shader shader) {
+        ListMap<String, Uniform> uniforms = shader.getUniformMap();
+        int size = uniforms.size();
+        for (int i = 0; i < size; i++) {
+            Uniform u = uniforms.getValue(i);
+            if (!u.isSetByCurrentMaterial()) {
+                u.clearValue();
+            }
+        }
+    }
+
+    /**
+     * Called by {@link RenderManager} to render the geometry by
+     * using this material.
+     *
+     * @param geom The geometry to render
+     * @param rm The render manager requesting the rendering
+     */
+    public void render(Geometry geom, RenderManager rm) {
+        autoSelectTechnique(rm);
+
+        Renderer r = rm.getRenderer();
+
+        TechniqueDef techDef = technique.getDef();
+
+        if (techDef.getLightMode() == LightMode.MultiPass
+                && geom.getWorldLightList().size() == 0) {
+            return;
+        }
+
+        if (rm.getForcedRenderState() != null) {
+            r.applyRenderState(rm.getForcedRenderState());
+        } else {
+            if (techDef.getRenderState() != null) {
+                r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
+            } else {
+                r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
+            }
+        }
+
+
+        // update camera and world matrices
+        // NOTE: setWorldTransform should have been called already
+        if (techDef.isUsingShaders()) {
+            // reset unchanged uniform flag
+            clearUniformsSetByCurrent(technique.getShader());
+            rm.updateUniformBindings(technique.getWorldBindUniforms());
+        }
+
+        // setup textures and uniforms
+        for (int i = 0; i < paramValues.size(); i++) {
+            MatParam param = paramValues.getValue(i);
+            param.apply(r, technique);
+        }
+
+        Shader shader = technique.getShader();
+
+        // send lighting information, if needed
+        switch (techDef.getLightMode()) {
+            case Disable:
+                r.setLighting(null);
+                break;
+            case SinglePass:
+                updateLightListUniforms(shader, geom, 4);
+                break;
+            case FixedPipeline:
+                r.setLighting(geom.getWorldLightList());
+                break;
+            case MultiPass:
+                // NOTE: Special case!
+                resetUniformsNotSetByCurrent(shader);
+                renderMultipassLighting(shader, geom, rm);
+                // very important, notice the return statement!
+                return;
+        }
+
+        // upload and bind shader
+        if (techDef.isUsingShaders()) {
+            // any unset uniforms will be set to 0
+            resetUniformsNotSetByCurrent(shader);
+            r.setShader(shader);
+        }
+
+        r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(def.getAssetName(), "material_def", null);
+        oc.write(additionalState, "render_state", null);
+        oc.write(transparent, "is_transparent", false);
+        oc.writeStringSavableMap(paramValues, "parameters", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+
+        additionalState = (RenderState) ic.readSavable("render_state", null);
+        transparent = ic.readBoolean("is_transparent", false);
+
+        // Load the material def
+        String defName = ic.readString("material_def", null);
+        HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null);
+
+        boolean enableVcolor = false;
+        boolean separateTexCoord = false;
+        boolean applyDefaultValues = false;
+        boolean guessRenderStateApply = false;
+
+        int ver = ic.getSavableVersion(Material.class);
+        if (ver < 1){
+            applyDefaultValues = true;
+        }
+        if (ver < 2){
+            guessRenderStateApply = true;
+        }
+        if (im.getFormatVersion() == 0) {
+            // Enable compatibility with old models
+            if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
+                // Using VertexColor, switch to Unshaded and set VertexColor=true
+                enableVcolor = true;
+                defName = "Common/MatDefs/Misc/Unshaded.j3md";
+            } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
+                    || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
+                // Using SimpleTextured/SolidColor, just switch to Unshaded
+                defName = "Common/MatDefs/Misc/Unshaded.j3md";
+            } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
+                // Using WireColor, set wireframe renderstate = true and use Unshaded
+                getAdditionalRenderState().setWireframe(true);
+                defName = "Common/MatDefs/Misc/Unshaded.j3md";
+            } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
+                // Uses unshaded, ensure that the proper param is set
+                MatParam value = params.get("SeperateTexCoord");
+                if (value != null && ((Boolean) value.getValue()) == true) {
+                    params.remove("SeperateTexCoord");
+                    separateTexCoord = true;
+                }
+            }
+            assert applyDefaultValues && guessRenderStateApply;
+        }
+
+        def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName));
+        paramValues = new ListMap<String, MatParam>();
+
+        // load the textures and update nextTexUnit
+        for (Map.Entry<String, MatParam> entry : params.entrySet()) {
+            MatParam param = entry.getValue();
+            if (param instanceof MatParamTexture) {
+                MatParamTexture texVal = (MatParamTexture) param;
+
+                if (nextTexUnit < texVal.getUnit() + 1) {
+                    nextTexUnit = texVal.getUnit() + 1;
+                }
+
+                // the texture failed to load for this param
+                // do not add to param values
+                if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
+                    continue;
+                }
+            }
+            param.setName(checkSetParam(param.getVarType(), param.getName()));
+            paramValues.put(param.getName(), param);
+        }
+        
+        if (applyDefaultValues){
+            // compatability with old versions where default vars were
+            // not available
+            for (MatParam param : def.getMaterialParams()){
+                if (param.getValue() != null && paramValues.get(param.getName()) == null){
+                    setParam(param.getName(), param.getVarType(), param.getValue());
+                }
+            }
+        }
+        if (guessRenderStateApply && additionalState != null){
+            // Try to guess values of "apply" render state based on defaults
+            // if value != default then set apply to true
+            additionalState.applyPolyOffset = additionalState.offsetEnabled;
+            additionalState.applyAlphaFallOff = additionalState.alphaTest;
+            additionalState.applyAlphaTest = additionalState.alphaTest;
+            additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
+            additionalState.applyColorWrite = !additionalState.colorWrite; 
+            additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
+            additionalState.applyDepthTest = !additionalState.depthTest;
+            additionalState.applyDepthWrite = !additionalState.depthWrite;
+            additionalState.applyPointSprite = additionalState.pointSprite;
+            additionalState.applyStencilTest = additionalState.stencilTest;
+            additionalState.applyWireFrame = additionalState.wireframe;
+        }
+        if (enableVcolor) {
+            setBoolean("VertexColor", true);
+        }
+        if (separateTexCoord) {
+            setBoolean("SeparateTexCoord", true);
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/material/MaterialDef.java b/engine/src/core/com/jme3/material/MaterialDef.java
new file mode 100644
index 0000000..e7ec3fc
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MaterialDef.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.shader.VarType;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Describes a J3MD (Material definition).
+ * 
+ * @author Kirill Vainer
+ */
+public class MaterialDef {
+
+    private static final Logger logger = Logger.getLogger(MaterialDef.class.getName());
+
+    private String name;
+    private String assetName;
+    private AssetManager assetManager;
+
+    private List<TechniqueDef> defaultTechs;
+    private Map<String, TechniqueDef> techniques;
+    private Map<String, MatParam> matParams;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public MaterialDef(){
+    }
+    
+    /**
+     * Creates a new material definition with the given name.
+     * 
+     * @param assetManager The asset manager to use to load shaders
+     * @param name The debug name of the material definition
+     */
+    public MaterialDef(AssetManager assetManager, String name){
+        this.assetManager = assetManager;
+        this.name = name;
+        techniques = new HashMap<String, TechniqueDef>();
+        matParams = new HashMap<String, MatParam>();
+        defaultTechs = new ArrayList<TechniqueDef>();
+        logger.log(Level.INFO, "Loaded material definition: {0}", name);
+    }
+
+    /**
+     * Returns the asset key name of the asset from which this material 
+     * definition was loaded.
+     * 
+     * @return Asset key name of the j3md file 
+     */
+    public String getAssetName() {
+        return assetName;
+    }
+
+    /**
+     * Set the asset key name. 
+     * 
+     * @param assetName the asset key name
+     */
+    public void setAssetName(String assetName) {
+        this.assetName = assetName;
+    }
+
+    /**
+     * Returns the AssetManager passed in the constructor.
+     * 
+     * @return the AssetManager passed in the constructor.
+     */
+    public AssetManager getAssetManager(){
+        return assetManager;
+    }
+
+    /**
+     * The debug name of the material definition.
+     * 
+     * @return debug name of the material definition.
+     */
+    public String getName(){
+        return name;
+    }
+
+    /**
+     * Adds a new material parameter.
+     * 
+     * @param type Type of the parameter
+     * @param name Name of the parameter
+     * @param value Default value of the parameter
+     * @param ffBinding Fixed function binding for the parameter
+     */
+    public void addMaterialParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) {
+        matParams.put(name, new MatParam(type, name, value, ffBinding));
+    }
+    
+    /**
+     * Returns the material parameter with the given name.
+     * 
+     * @param name The name of the parameter to retrieve
+     * 
+     * @return The material parameter, or null if it does not exist.
+     */
+    public MatParam getMaterialParam(String name){
+        return matParams.get(name);
+    }
+    
+    /**
+     * Returns a collection of all material parameters declared in this
+     * material definition.
+     * <p>
+     * Modifying the material parameters or the collection will lead
+     * to undefined results.
+     * 
+     * @return All material parameters declared in this definition.
+     */
+    public Collection<MatParam> getMaterialParams(){
+        return matParams.values();
+    }
+
+    /**
+     * Adds a new technique definition to this material definition.
+     * <p>
+     * If the technique name is "Default", it will be added
+     * to the list of {@link MaterialDef#getDefaultTechniques() default techniques}.
+     * 
+     * @param technique The technique definition to add.
+     */
+    public void addTechniqueDef(TechniqueDef technique){
+        if (technique.getName().equals("Default")){
+            defaultTechs.add(technique);
+        }else{
+            techniques.put(technique.getName(), technique);
+        }
+    }
+
+    /**
+     * Returns a list of all default techniques.
+     * 
+     * @return a list of all default techniques.
+     */
+    public List<TechniqueDef> getDefaultTechniques(){
+        return defaultTechs;
+    }
+
+    /**
+     * Returns a technique definition with the given name.
+     * This does not include default techniques which can be
+     * retrieved via {@link MaterialDef#getDefaultTechniques() }.
+     * 
+     * @param name The name of the technique definition to find
+     * 
+     * @return The technique definition, or null if cannot be found.
+     */
+    public TechniqueDef getTechniqueDef(String name) {
+        return techniques.get(name);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/material/MaterialList.java b/engine/src/core/com/jme3/material/MaterialList.java
new file mode 100644
index 0000000..9f2a512
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MaterialList.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import java.util.HashMap;
+
+/**
+ * A map from material name to a material. Used by loaders to locate
+ * materials for meshes inside a model.
+ * 
+ * @author Kirill Vainer
+ */
+public class MaterialList extends HashMap<String, Material> {
+}
diff --git a/engine/src/core/com/jme3/material/RenderState.java b/engine/src/core/com/jme3/material/RenderState.java
new file mode 100644
index 0000000..37897fd
--- /dev/null
+++ b/engine/src/core/com/jme3/material/RenderState.java
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.export.*;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import java.io.IOException;
+
+/**
+ * <code>RenderState</code> specifies material rendering properties that cannot
+ * be controlled by a shader on a {@link Material}. The properties
+ * allow manipulation of rendering features such as depth testing, alpha blending,
+ * face culling, stencil operations, and much more.
+ *
+ * @author Kirill Vainer
+ */
+public class RenderState implements Cloneable, Savable {
+
+    /**
+     * The <code>DEFAULT</code> render state is the one used by default
+     * on all materials unless changed otherwise by the user.
+     *
+     * <p>
+     * It has the following properties:
+     * <ul>
+     * <li>Back Face Culling</li>
+     * <li>Depth Testing Enabled</li>
+     * <li>Depth Writing Enabled</li>
+     * </ul>
+     */
+    public static final RenderState DEFAULT = new RenderState();
+
+    /**
+     * The <code>NULL</code> render state is identical to the {@link RenderState#DEFAULT}
+     * render state except that depth testing and face culling are disabled.
+     */
+    public static final RenderState NULL = new RenderState();
+
+    /**
+     * The <code>ADDITIONAL</code> render state is identical to the
+     * {@link RenderState#DEFAULT} render state except that all apply
+     * values are set to false. This allows the <code>ADDITIONAL</code> render
+     * state to be combined with other state but only influencing values
+     * that were changed from the original.
+     */
+    public static final RenderState ADDITIONAL = new RenderState();
+
+    /**
+     * <code>TestFunction</code> specifies the testing function for stencil test
+     * function and alpha test function.
+     *
+     * <p>The functions work similarly as described except that for stencil
+     * test function, the reference value given in the stencil command is
+     * the input value while the reference is the value already in the stencil
+     * buffer.
+     */
+    public enum TestFunction {
+
+        /**
+         * The test always fails
+         */
+        Never,
+        /**
+         * The test succeeds if the input value is equal to the reference value.
+         */
+        Equal,
+        /**
+         * The test succeeds if the input value is less than the reference value.
+         */
+        Less,
+        /**
+         * The test succeeds if the input value is less than or equal to
+         * the reference value.
+         */
+        LessOrEqual,
+        /**
+         * The test succeeds if the input value is greater than the reference value.
+         */
+        Greater,
+        /**
+         * The test succeeds if the input value is greater than or equal to
+         * the reference value.
+         */
+        GreaterOrEqual,
+        /**
+         * The test succeeds if the input value does not equal the
+         * reference value.
+         */
+        NotEqual,
+        /**
+         * The test always passes
+         */
+        Always,}
+
+    /**
+     * <code>BlendMode</code> specifies the blending operation to use.
+     *
+     * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode)
+     */
+    public enum BlendMode {
+
+        /**
+         * No blending mode is used.
+         */
+        Off,
+        /**
+         * Additive blending. For use with glows and particle emitters.
+         * <p>
+         * Result = Source Color + Destination Color -> (GL_ONE, GL_ONE)
+         */
+        Additive,
+        /**
+         * Premultiplied alpha blending, for use with premult alpha textures.
+         * <p>
+         * Result = Source Color + (Dest Color * (1 - Source Alpha) ) -> (GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
+         */
+        PremultAlpha,
+        /**
+         * Additive blending that is multiplied with source alpha.
+         * For use with glows and particle emitters.
+         * <p>
+         * Result = (Source Alpha * Source Color) + Dest Color -> (GL_SRC_ALPHA, GL_ONE)
+         */
+        AlphaAdditive,
+        /**
+         * Color blending, blends in color from dest color
+         * using source color.
+         * <p>
+         * Result = Source Color + (1 - Source Color) * Dest Color -> (GL_ONE, GL_ONE_MINUS_SRC_COLOR)
+         */
+        Color,
+        /**
+         * Alpha blending, interpolates to source color from dest color
+         * using source alpha.
+         * <p>
+         * Result = Source Alpha * Source Color +
+         *          (1 - Source Alpha) * Dest Color -> (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+         */
+        Alpha,
+        /**
+         * Multiplies the source and dest colors.
+         * <p>
+         * Result = Source Color * Dest Color -> (GL_DST_COLOR, GL_ZERO)
+         */
+        Modulate,
+        /**
+         * Multiplies the source and dest colors then doubles the result.
+         * <p>
+         * Result = 2 * Source Color * Dest Color -> (GL_DST_COLOR, GL_SRC_COLOR)
+         */
+        ModulateX2
+    }
+
+    /**
+     * <code>FaceCullMode</code> specifies the criteria for faces to be culled.
+     *
+     * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+     */
+    public enum FaceCullMode {
+
+        /**
+         * Face culling is disabled.
+         */
+        Off,
+        /**
+         * Cull front faces
+         */
+        Front,
+        /**
+         * Cull back faces
+         */
+        Back,
+        /**
+         * Cull both front and back faces.
+         */
+        FrontAndBack
+    }
+
+    /**
+     * <code>StencilOperation</code> specifies the stencil operation to use
+     * in a certain scenario as specified in {@link RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilFunction,
+     * com.jme3.material.RenderState.StencilFunction)}
+     */
+    public enum StencilOperation {
+
+        /**
+         * Keep the current value.
+         */
+        Keep,
+        /**
+         * Set the value to 0
+         */
+        Zero,
+        /**
+         * Replace the value in the stencil buffer with the reference value.
+         */
+        Replace,
+
+        /**
+         * Increment the value in the stencil buffer, clamp once reaching
+         * the maximum value.
+         */
+        Increment,
+
+        /**
+         * Increment the value in the stencil buffer and wrap to 0 when
+         * reaching the maximum value.
+         */
+        IncrementWrap,
+        /**
+         * Decrement the value in the stencil buffer and clamp once reaching 0.
+         */
+        Decrement,
+        /**
+         * Decrement the value in the stencil buffer and wrap to the maximum
+         * value when reaching 0.
+         */
+        DecrementWrap,
+
+        /**
+         * Does a bitwise invert of the value in the stencil buffer.
+         */
+        Invert
+    }
+
+    static {
+        NULL.cullMode = FaceCullMode.Off;
+        NULL.depthTest = false;
+    }
+
+    static {
+        ADDITIONAL.applyPointSprite = false;
+        ADDITIONAL.applyWireFrame = false;
+        ADDITIONAL.applyCullMode = false;
+        ADDITIONAL.applyDepthWrite = false;
+        ADDITIONAL.applyDepthTest = false;
+        ADDITIONAL.applyColorWrite = false;
+        ADDITIONAL.applyBlendMode = false;
+        ADDITIONAL.applyAlphaTest = false;
+        ADDITIONAL.applyAlphaFallOff = false;
+        ADDITIONAL.applyPolyOffset = false;
+    }
+
+    boolean pointSprite = false;
+    boolean applyPointSprite = true;
+
+    boolean wireframe = false;
+    boolean applyWireFrame = true;
+
+    FaceCullMode cullMode = FaceCullMode.Back;
+    boolean applyCullMode = true;
+
+    boolean depthWrite = true;
+    boolean applyDepthWrite = true;
+
+    boolean depthTest = true;
+    boolean applyDepthTest = true;
+
+    boolean colorWrite = true;
+    boolean applyColorWrite = true;
+
+    BlendMode blendMode = BlendMode.Off;
+    boolean applyBlendMode = true;
+
+    boolean alphaTest = false;
+    boolean applyAlphaTest = true;
+
+    float alphaFallOff = 0;
+    boolean applyAlphaFallOff = true;
+
+    float offsetFactor = 0;
+    float offsetUnits = 0;
+    boolean offsetEnabled = false;
+    boolean applyPolyOffset = true;
+
+    boolean stencilTest = false;
+    boolean applyStencilTest = false;
+    StencilOperation frontStencilStencilFailOperation = StencilOperation.Keep;
+    StencilOperation frontStencilDepthFailOperation = StencilOperation.Keep;
+    StencilOperation frontStencilDepthPassOperation = StencilOperation.Keep;
+    StencilOperation backStencilStencilFailOperation = StencilOperation.Keep;
+    StencilOperation backStencilDepthFailOperation = StencilOperation.Keep;
+    StencilOperation backStencilDepthPassOperation = StencilOperation.Keep;
+    TestFunction frontStencilFunction = TestFunction.Always;
+    TestFunction backStencilFunction = TestFunction.Always;
+    
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(pointSprite, "pointSprite", false);
+        oc.write(wireframe, "wireframe", false);
+        oc.write(cullMode, "cullMode", FaceCullMode.Back);
+        oc.write(depthWrite, "depthWrite", true);
+        oc.write(depthTest, "depthTest", true);
+        oc.write(colorWrite, "colorWrite", true);
+        oc.write(blendMode, "blendMode", BlendMode.Off);
+        oc.write(alphaTest, "alphaTest", false);
+        oc.write(alphaFallOff, "alphaFallOff", 0);
+        oc.write(offsetEnabled, "offsetEnabled", false);
+        oc.write(offsetFactor, "offsetFactor", 0);
+        oc.write(offsetUnits, "offsetUnits", 0);
+        oc.write(stencilTest, "stencilTest", false);
+        oc.write(frontStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep);
+        oc.write(frontStencilDepthFailOperation, "frontStencilDepthFailOperation", StencilOperation.Keep);
+        oc.write(frontStencilDepthPassOperation, "frontStencilDepthPassOperation", StencilOperation.Keep);
+        oc.write(backStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep);
+        oc.write(backStencilDepthFailOperation, "backStencilDepthFailOperation", StencilOperation.Keep);
+        oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
+        oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
+        oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
+        
+        // Only "additional render state" has them set to false by default
+        oc.write(applyPointSprite,  "applyPointSprite",  true);
+        oc.write(applyWireFrame,    "applyWireFrame",    true);
+        oc.write(applyCullMode,     "applyCullMode",     true);
+        oc.write(applyDepthWrite,   "applyDepthWrite",   true);
+        oc.write(applyDepthTest,    "applyDepthTest",    true);
+        oc.write(applyColorWrite,   "applyColorWrite",   true);
+        oc.write(applyBlendMode,    "applyBlendMode",    true);
+        oc.write(applyAlphaTest,    "applyAlphaTest",    true);
+        oc.write(applyAlphaFallOff, "applyAlphaFallOff", true);
+        oc.write(applyPolyOffset,   "applyPolyOffset",   true);
+        
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        pointSprite = ic.readBoolean("pointSprite", false);
+        wireframe = ic.readBoolean("wireframe", false);
+        cullMode = ic.readEnum("cullMode", FaceCullMode.class, FaceCullMode.Back);
+        depthWrite = ic.readBoolean("depthWrite", true);
+        depthTest = ic.readBoolean("depthTest", true);
+        colorWrite = ic.readBoolean("colorWrite", true);
+        blendMode = ic.readEnum("blendMode", BlendMode.class, BlendMode.Off);
+        alphaTest = ic.readBoolean("alphaTest", false);
+        alphaFallOff = ic.readFloat("alphaFallOff", 0);
+        offsetEnabled = ic.readBoolean("offsetEnabled", false);
+        offsetFactor = ic.readFloat("offsetFactor", 0);
+        offsetUnits = ic.readFloat("offsetUnits", 0);
+        stencilTest = ic.readBoolean("stencilTest", false);
+        frontStencilStencilFailOperation = ic.readEnum("frontStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep);
+        frontStencilDepthFailOperation = ic.readEnum("frontStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep);
+        frontStencilDepthPassOperation = ic.readEnum("frontStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep);
+        backStencilStencilFailOperation = ic.readEnum("backStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep);
+        backStencilDepthFailOperation = ic.readEnum("backStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep);
+        backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep);
+        frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always);
+        backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
+        
+        applyPointSprite =  ic.readBoolean("applyPointSprite",  true);
+        applyWireFrame =    ic.readBoolean("applyWireFrame",    true);
+        applyCullMode =     ic.readBoolean("applyCullMode",     true);
+        applyDepthWrite =   ic.readBoolean("applyDepthWrite",   true);
+        applyDepthTest =    ic.readBoolean("applyDepthTest",    true);
+        applyColorWrite =   ic.readBoolean("applyColorWrite",   true);
+        applyBlendMode =    ic.readBoolean("applyBlendMode",    true);
+        applyAlphaTest =    ic.readBoolean("applyAlphaTest",    true);
+        applyAlphaFallOff = ic.readBoolean("applyAlphaFallOff", true);
+        applyPolyOffset =   ic.readBoolean("applyPolyOffset",   true);
+    }
+
+    /**
+     * Create a clone of this <code>RenderState</code>
+     *
+     * @return Clone of this render state.
+     */
+    @Override
+    public RenderState clone() {
+        try {
+            return (RenderState) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Enables point sprite mode.
+     *
+     * <p>When point sprite is enabled, any meshes
+     * with the type of {@link Mode#Points} will be rendered as 2D quads
+     * with texturing enabled. Fragment shaders can write to the
+     * <code>gl_PointCoord</code> variable to manipulate the texture coordinate
+     * for each pixel. The size of the 2D quad can be controlled by writing
+     * to the <code>gl_PointSize</code> variable in the vertex shader.
+     *
+     * @param pointSprite Enables Point Sprite mode.
+     */
+    public void setPointSprite(boolean pointSprite) {
+        applyPointSprite = true;
+        this.pointSprite = pointSprite;
+    }
+
+    /**
+     * Sets the alpha fall off value for alpha testing.
+     *
+     * <p>If the pixel's alpha value is greater than the
+     * <code>alphaFallOff</code> then the pixel will be rendered, otherwise
+     * the pixel will be discarded.
+     *
+     * @param alphaFallOff The alpha of all rendered pixels must be higher
+     * than this value to be rendered. This value should be between 0 and 1.
+     *
+     * @see RenderState#setAlphaTest(boolean)
+     */
+    public void setAlphaFallOff(float alphaFallOff) {
+        applyAlphaFallOff = true;
+        this.alphaFallOff = alphaFallOff;
+    }
+
+    /**
+     * Enable alpha testing.
+     *
+     * <p>When alpha testing is enabled, all input pixels' alpha are compared
+     * to the {@link RenderState#setAlphaFallOff(float) constant alpha falloff}.
+     * If the input alpha is greater than the falloff, the pixel will be rendered,
+     * otherwise it will be discarded.
+     *
+     * @param alphaTest Set to true to enable alpha testing.
+     *
+     * @see RenderState#setAlphaFallOff(float)
+     */
+    public void setAlphaTest(boolean alphaTest) {
+        applyAlphaTest = true;
+        this.alphaTest = alphaTest;
+    }
+
+    /**
+     * Enable writing color.
+     *
+     * <p>When color write is enabled, the result of a fragment shader, the
+     * <code>gl_FragColor</code>, will be rendered into the color buffer
+     * (including alpha).
+     *
+     * @param colorWrite Set to true to enable color writing.
+     */
+    public void setColorWrite(boolean colorWrite) {
+        applyColorWrite = true;
+        this.colorWrite = colorWrite;
+    }
+
+    /**
+     * Set the face culling mode.
+     *
+     * <p>See the {@link FaceCullMode} enum on what each value does.
+     * Face culling will project the triangle's points onto the screen
+     * and determine if the triangle is in counter-clockwise order or
+     * clockwise order. If a triangle is in counter-clockwise order, then
+     * it is considered a front-facing triangle, otherwise, it is considered
+     * a back-facing triangle.
+     *
+     * @param cullMode the face culling mode.
+     */
+    public void setFaceCullMode(FaceCullMode cullMode) {
+        applyCullMode = true;
+        this.cullMode = cullMode;
+    }
+
+    /**
+     * Set the blending mode.
+     *
+     * <p>When blending is enabled, (<code>blendMode</code> is not {@link BlendMode#Off})
+     * the input pixel will be blended with the pixel
+     * already in the color buffer. The blending operation is determined
+     * by the {@link BlendMode}. For example, the {@link BlendMode#Additive}
+     * will add the input pixel's color to the color already in the color buffer:
+     * <br/>
+     * <code>Result = Source Color + Destination Color</code>
+     *
+     * @param blendMode The blend mode to use. Set to {@link BlendMode#Off}
+     * to disable blending.
+     */
+    public void setBlendMode(BlendMode blendMode) {
+        applyBlendMode = true;
+        this.blendMode = blendMode;
+    }
+
+    /**
+     * Enable depth testing.
+     *
+     * <p>When depth testing is enabled, a pixel must pass the depth test
+     * before it is written to the color buffer.
+     * The input pixel's depth value must be less than or equal than
+     * the value already in the depth buffer to pass the depth test.
+     *
+     * @param depthTest Enable or disable depth testing.
+     */
+    public void setDepthTest(boolean depthTest) {
+        applyDepthTest = true;
+        this.depthTest = depthTest;
+    }
+
+    /**
+     * Enable depth writing.
+     *
+     * <p>After passing the {@link RenderState#setDepthTest(boolean) depth test},
+     * a pixel's depth value will be written into the depth buffer if
+     * depth writing is enabled.
+     *
+     * @param depthWrite True to enable writing to the depth buffer.
+     */
+    public void setDepthWrite(boolean depthWrite) {
+        applyDepthWrite = true;
+        this.depthWrite = depthWrite;
+    }
+
+    /**
+     * Enables wireframe rendering mode.
+     *
+     * <p>When in wireframe mode, {@link Mesh meshes} rendered in triangle mode
+     * will not be solid, but instead, only the edges of the triangles
+     * will be rendered.
+     *
+     * @param wireframe True to enable wireframe mode.
+     */
+    public void setWireframe(boolean wireframe) {
+        applyWireFrame = true;
+        this.wireframe = wireframe;
+    }
+
+    /**
+     * Offsets the on-screen z-order of the material's polygons, to combat visual artefacts like
+     * stitching, bleeding and z-fighting for overlapping polygons.
+     * Factor and units are summed to produce the depth offset.
+     * This offset is applied in screen space,
+     * typically with positive Z pointing into the screen.
+     * Typical values are (1.0f, 1.0f) or (-1.0f, -1.0f)
+     *
+     * @see <a href="http://www.opengl.org/resources/faq/technical/polygonoffset.htm" rel="nofollow">http://www.opengl.org/resources/faq/technical/polygonoffset.htm</a>
+     * @param factor scales the maximum Z slope, with respect to X or Y of the polygon
+     * @param units scales the minimum resolvable depth buffer value
+     **/
+    public void setPolyOffset(float factor, float units) {
+        applyPolyOffset = true;
+        offsetEnabled = true;
+        offsetFactor = factor;
+        offsetUnits = units;
+    }
+
+    /**
+     * Enable stencil testing.
+     *
+     * <p>Stencil testing can be used to filter pixels according to the stencil
+     * buffer. Objects can be rendered with some stencil operation to manipulate
+     * the values in the stencil buffer, then, other objects can be rendered
+     * to test against the values written previously.
+     *
+     * @param enabled Set to true to enable stencil functionality. If false
+     * all other parameters are ignored.
+     *
+     * @param _frontStencilStencilFailOperation Sets the operation to occur when
+     * a front-facing triangle fails the front stencil function.
+     * @param _frontStencilDepthFailOperation Sets the operation to occur when
+     * a front-facing triangle fails the depth test.
+     * @param _frontStencilDepthPassOperation Set the operation to occur when
+     * a front-facing triangle passes the depth test.
+     * @param _backStencilStencilFailOperation Set the operation to occur when
+     * a back-facing triangle fails the back stencil function.
+     * @param _backStencilDepthFailOperation Set the operation to occur when
+     * a back-facing triangle fails the depth test.
+     * @param _backStencilDepthPassOperation Set the operation to occur when
+     * a back-facing triangle passes the depth test.
+     * @param _frontStencilFunction Set the test function for front-facing triangles.
+     * @param _backStencilFunction Set the test function for back-facing triangles.
+     */
+    public void setStencil(boolean enabled,
+            StencilOperation _frontStencilStencilFailOperation,
+            StencilOperation _frontStencilDepthFailOperation,
+            StencilOperation _frontStencilDepthPassOperation,
+            StencilOperation _backStencilStencilFailOperation,
+            StencilOperation _backStencilDepthFailOperation,
+            StencilOperation _backStencilDepthPassOperation,
+            TestFunction _frontStencilFunction,
+            TestFunction _backStencilFunction) {
+
+        stencilTest = enabled;
+        applyStencilTest = true;
+        this.frontStencilStencilFailOperation = _frontStencilStencilFailOperation;
+        this.frontStencilDepthFailOperation = _frontStencilDepthFailOperation;
+        this.frontStencilDepthPassOperation = _frontStencilDepthPassOperation;
+        this.backStencilStencilFailOperation = _backStencilStencilFailOperation;
+        this.backStencilDepthFailOperation = _backStencilDepthFailOperation;
+        this.backStencilDepthPassOperation = _backStencilDepthPassOperation;
+        this.frontStencilFunction = _frontStencilFunction;
+        this.backStencilFunction = _backStencilFunction;
+    }
+
+    /**
+     * Check if stencil test is enabled.
+     *
+     * @return True if stencil test is enabled.
+     */
+    public boolean isStencilTest() {
+        return stencilTest;
+    }
+
+    /**
+     * Retrieve the front stencil fail operation.
+     *
+     * @return the front stencil fail operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getFrontStencilStencilFailOperation() {
+        return frontStencilStencilFailOperation;
+    }
+
+    /**
+     * Retrieve the front depth test fail operation.
+     *
+     * @return the front depth test fail operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getFrontStencilDepthFailOperation() {
+        return frontStencilDepthFailOperation;
+    }
+
+    /**
+     * Retrieve the front depth test pass operation.
+     *
+     * @return the front depth test pass operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getFrontStencilDepthPassOperation() {
+        return frontStencilDepthPassOperation;
+    }
+
+    /**
+     * Retrieve the back stencil fail operation.
+     *
+     * @return the back stencil fail operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getBackStencilStencilFailOperation() {
+        return backStencilStencilFailOperation;
+    }
+
+    /**
+     * Retrieve the back depth test fail operation.
+     *
+     * @return the back depth test fail operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getBackStencilDepthFailOperation() {
+        return backStencilDepthFailOperation;
+    }
+
+    /**
+     * Retrieve the back depth test pass operation.
+     *
+     * @return the back depth test pass operation.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public StencilOperation getBackStencilDepthPassOperation() {
+        return backStencilDepthPassOperation;
+    }
+
+    /**
+     * Retrieve the front stencil function.
+     *
+     * @return the front stencil function.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public TestFunction getFrontStencilFunction() {
+        return frontStencilFunction;
+    }
+
+    /**
+     * Retrieve the back stencil function.
+     *
+     * @return the back stencil function.
+     *
+     * @see RenderState#setStencil(boolean,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.StencilOperation,
+     * com.jme3.material.RenderState.TestFunction,
+     * com.jme3.material.RenderState.TestFunction)
+     */
+    public TestFunction getBackStencilFunction() {
+        return backStencilFunction;
+    }
+
+    /**
+     * Retrieve the blend mode.
+     *
+     * @return the blend mode.
+     */
+    public BlendMode getBlendMode() {
+        return blendMode;
+    }
+
+    /**
+     * Check if point sprite mode is enabled
+     *
+     * @return True if point sprite mode is enabled.
+     *
+     * @see RenderState#setPointSprite(boolean)
+     */
+    public boolean isPointSprite() {
+        return pointSprite;
+    }
+
+    /**
+     * Check if alpha test is enabled.
+     *
+     * @return True if alpha test is enabled.
+     *
+     * @see RenderState#setAlphaTest(boolean)
+     */
+    public boolean isAlphaTest() {
+        return alphaTest;
+    }
+
+    /**
+     * Retrieve the face cull mode.
+     *
+     * @return the face cull mode.
+     *
+     * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+     */
+    public FaceCullMode getFaceCullMode() {
+        return cullMode;
+    }
+
+    /**
+     * Check if depth test is enabled.
+     *
+     * @return True if depth test is enabled.
+     *
+     * @see RenderState#setDepthTest(boolean)
+     */
+    public boolean isDepthTest() {
+        return depthTest;
+    }
+
+    /**
+     * Check if depth write is enabled.
+     *
+     * @return True if depth write is enabled.
+     *
+     * @see RenderState#setDepthWrite(boolean)
+     */
+    public boolean isDepthWrite() {
+        return depthWrite;
+    }
+
+    /**
+     * Check if wireframe mode is enabled.
+     *
+     * @return True if wireframe mode is enabled.
+     *
+     * @see RenderState#setWireframe(boolean)
+     */
+    public boolean isWireframe() {
+        return wireframe;
+    }
+
+    /**
+     * Check if color writing is enabled.
+     *
+     * @return True if color writing is enabled.
+     *
+     * @see RenderState#setColorWrite(boolean)
+     */
+    public boolean isColorWrite() {
+        return colorWrite;
+    }
+
+    /**
+     * Retrieve the poly offset factor value.
+     *
+     * @return the poly offset factor value.
+     *
+     * @see RenderState#setPolyOffset(float, float)
+     */
+    public float getPolyOffsetFactor() {
+        return offsetFactor;
+    }
+
+    /**
+     * Retrieve the poly offset units value.
+     *
+     * @return the poly offset units value.
+     *
+     * @see RenderState#setPolyOffset(float, float)
+     */
+    public float getPolyOffsetUnits() {
+        return offsetUnits;
+    }
+
+    /**
+     * Check if polygon offset is enabled.
+     *
+     * @return True if polygon offset is enabled.
+     *
+     * @see RenderState#setPolyOffset(float, float)
+     */
+    public boolean isPolyOffset() {
+        return offsetEnabled;
+    }
+
+    /**
+     * Retrieve the alpha falloff value.
+     *
+     * @return the alpha falloff value.
+     *
+     * @see RenderState#setAlphaFallOff(float)
+     */
+    public float getAlphaFallOff() {
+        return alphaFallOff;
+    }
+
+    public boolean isApplyAlphaFallOff() {
+        return applyAlphaFallOff;
+    }
+
+    public boolean isApplyAlphaTest() {
+        return applyAlphaTest;
+    }
+
+    public boolean isApplyBlendMode() {
+        return applyBlendMode;
+    }
+
+    public boolean isApplyColorWrite() {
+        return applyColorWrite;
+    }
+
+    public boolean isApplyCullMode() {
+        return applyCullMode;
+    }
+
+    public boolean isApplyDepthTest() {
+        return applyDepthTest;
+    }
+
+    public boolean isApplyDepthWrite() {
+        return applyDepthWrite;
+    }
+
+    public boolean isApplyPointSprite() {
+        return applyPointSprite;
+    }
+
+    public boolean isApplyPolyOffset() {
+        return applyPolyOffset;
+    }
+
+    public boolean isApplyWireFrame() {
+        return applyWireFrame;
+    }
+
+    /**
+     * Merges <code>this</code> state and <code>additionalState</code> into
+     * the parameter <code>state</code> based on a specific criteria.
+     *
+     * <p>The criteria for this merge is the following:<br/>
+     * For every given property, such as alpha test or depth write, check
+     * if it was modified from the original in the <code>additionalState</code>
+     * if it was modified, then copy the property from the <code>additionalState</code>
+     * into the parameter <code>state</code>, otherwise, copy the property from <code>this</code>
+     * into the parameter <code>state</code>. If <code>additionalState</code>
+     * is <code>null</code>, then no modifications are made and <code>this</code> is returned,
+     * otherwise, the parameter <code>state</code> is returned with the result
+     * of the merge.
+     *
+     * @param additionalState The <code>additionalState</code>, from which data is taken only
+     * if it was modified by the user.
+     * @param state Contains output of the method if <code>additionalState</code>
+     * is not null.
+     * @return <code>state</code> if <code>additionalState</code> is non-null,
+     * otherwise returns <code>this</code>
+     */
+    public RenderState copyMergedTo(RenderState additionalState, RenderState state) {
+        if (additionalState == null) {
+            return this;
+        }
+
+        if (additionalState.applyPointSprite) {
+            state.pointSprite = additionalState.pointSprite;
+        } else {
+            state.pointSprite = pointSprite;
+        }
+        if (additionalState.applyWireFrame) {
+            state.wireframe = additionalState.wireframe;
+        } else {
+            state.wireframe = wireframe;
+        }
+
+        if (additionalState.applyCullMode) {
+            state.cullMode = additionalState.cullMode;
+        } else {
+            state.cullMode = cullMode;
+        }
+        if (additionalState.applyDepthWrite) {
+            state.depthWrite = additionalState.depthWrite;
+        } else {
+            state.depthWrite = depthWrite;
+        }
+        if (additionalState.applyDepthTest) {
+            state.depthTest = additionalState.depthTest;
+        } else {
+            state.depthTest = depthTest;
+        }
+        if (additionalState.applyColorWrite) {
+            state.colorWrite = additionalState.colorWrite;
+        } else {
+            state.colorWrite = colorWrite;
+        }
+        if (additionalState.applyBlendMode) {
+            state.blendMode = additionalState.blendMode;
+        } else {
+            state.blendMode = blendMode;
+        }
+        if (additionalState.applyAlphaTest) {
+            state.alphaTest = additionalState.alphaTest;
+        } else {
+            state.alphaTest = alphaTest;
+        }
+
+        if (additionalState.applyAlphaFallOff) {
+            state.alphaFallOff = additionalState.alphaFallOff;
+        } else {
+            state.alphaFallOff = alphaFallOff;
+        }
+        if (additionalState.applyPolyOffset) {
+            state.offsetEnabled = additionalState.offsetEnabled;
+            state.offsetFactor = additionalState.offsetFactor;
+            state.offsetUnits = additionalState.offsetUnits;
+        } else {
+            state.offsetEnabled = offsetEnabled;
+            state.offsetFactor = offsetFactor;
+            state.offsetUnits = offsetUnits;
+        }
+        if (additionalState.applyStencilTest){
+            state.stencilTest = additionalState.stencilTest;
+
+            state.frontStencilStencilFailOperation = additionalState.frontStencilStencilFailOperation;
+            state.frontStencilDepthFailOperation   = additionalState.frontStencilDepthFailOperation;
+            state.frontStencilDepthPassOperation   = additionalState.frontStencilDepthPassOperation;
+
+            state.backStencilStencilFailOperation = additionalState.backStencilStencilFailOperation;
+            state.backStencilDepthFailOperation   = additionalState.backStencilDepthFailOperation;
+            state.backStencilDepthPassOperation   = additionalState.backStencilDepthPassOperation;
+
+            state.frontStencilFunction = additionalState.frontStencilFunction;
+            state.backStencilFunction = additionalState.backStencilFunction;
+        }else{
+            state.stencilTest = stencilTest;
+
+            state.frontStencilStencilFailOperation = frontStencilStencilFailOperation;
+            state.frontStencilDepthFailOperation   = frontStencilDepthFailOperation;
+            state.frontStencilDepthPassOperation   = frontStencilDepthPassOperation;
+
+            state.backStencilStencilFailOperation = backStencilStencilFailOperation;
+            state.backStencilDepthFailOperation   = backStencilDepthFailOperation;
+            state.backStencilDepthPassOperation   = backStencilDepthPassOperation;
+
+            state.frontStencilFunction = frontStencilFunction;
+            state.backStencilFunction = backStencilFunction;
+        }
+        return state;
+    }
+
+    @Override
+    public String toString() {
+        return "RenderState[\n" + "pointSprite=" + pointSprite + "\napplyPointSprite=" + applyPointSprite + "\nwireframe=" + wireframe + "\napplyWireFrame=" + applyWireFrame + "\ncullMode=" + cullMode + "\napplyCullMode=" + applyCullMode + "\ndepthWrite=" + depthWrite + "\napplyDepthWrite=" + applyDepthWrite + "\ndepthTest=" + depthTest + "\napplyDepthTest=" + applyDepthTest + "\ncolorWrite=" + colorWrite + "\napplyColorWrite=" + applyColorWrite + "\nblendMode=" + blendMode + "\napplyBlendMode=" + applyBlendMode + "\nalphaTest=" + alphaTest + "\napplyAlphaTest=" + applyAlphaTest + "\nalphaFallOff=" + alphaFallOff + "\napplyAlphaFallOff=" + applyAlphaFallOff + "\noffsetEnabled=" + offsetEnabled + "\napplyPolyOffset=" + applyPolyOffset + "\noffsetFactor=" + offsetFactor + "\noffsetUnits=" + offsetUnits + "\n]";
+    }
+}
diff --git a/engine/src/core/com/jme3/material/Technique.java b/engine/src/core/com/jme3/material/Technique.java
new file mode 100644
index 0000000..5ae20a5
--- /dev/null
+++ b/engine/src/core/com/jme3/material/Technique.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.shader.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Represents a technique instance.
+ */
+public class Technique implements Savable {
+
+    private static final Logger logger = Logger.getLogger(Technique.class.getName());
+    private TechniqueDef def;
+    private Material owner;
+    private ArrayList<Uniform> worldBindUniforms;
+    private DefineList defines;
+    private Shader shader;
+    private boolean needReload = true;
+
+    /**
+     * Creates a new technique instance that implements the given
+     * technique definition.
+     * 
+     * @param owner The material that will own this technique
+     * @param def The technique definition being implemented.
+     */
+    public Technique(Material owner, TechniqueDef def) {
+        this.owner = owner;
+        this.def = def;
+        if (def.isUsingShaders()) {
+            this.worldBindUniforms = new ArrayList<Uniform>();
+            this.defines = new DefineList();
+        }
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Technique() {
+    }
+
+    /**
+     * Returns the technique definition that is implemented by this technique
+     * instance. 
+     * 
+     * @return the technique definition that is implemented by this technique
+     * instance. 
+     */
+    public TechniqueDef getDef() {
+        return def;
+    }
+
+    /**
+     * Returns the shader currently used by this technique instance.
+     * <p>
+     * Shaders are typically loaded dynamically when the technique is first
+     * used, therefore, this variable will most likely be null most of the time.
+     * 
+     * @return the shader currently used by this technique instance.
+     */
+    public Shader getShader() {
+        return shader;
+    }
+
+    /**
+     * Returns a list of uniforms that implements the world parameters
+     * that were requested by the material definition.
+     * 
+     * @return a list of uniforms implementing the world parameters.
+     */
+    public List<Uniform> getWorldBindUniforms() {
+        return worldBindUniforms;
+    }
+
+    /**
+     * Called by the material to tell the technique a parameter was modified
+     */
+    void notifySetParam(String paramName, VarType type, Object value) {
+        String defineName = def.getShaderParamDefine(paramName);
+        if (defineName != null) {
+            needReload = defines.set(defineName, type, value);
+        }
+        if (shader != null) {
+            updateUniformParam(paramName, type, value);
+        }
+    }
+
+    /**
+     * Called by the material to tell the technique a parameter was cleared
+     */
+    void notifyClearParam(String paramName) {
+        String defineName = def.getShaderParamDefine(paramName);
+        if (defineName != null) {
+            needReload = defines.remove(defineName);
+        }
+        if (shader != null) {
+            if (!paramName.startsWith("m_")) {
+                paramName = "m_" + paramName;
+            }
+            shader.removeUniform(paramName);
+        }
+    }
+
+    void updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner) {
+        Uniform u = shader.getUniform(paramName);
+
+//        if (ifNotOwner && u.getLastChanger() == owner)
+//            return;
+
+        switch (type) {
+            case Texture2D: // fall intentional
+            case Texture3D:
+            case TextureArray:
+            case TextureCubeMap:
+            case Int:
+                u.setValue(VarType.Int, value);
+                break;
+            default:
+                u.setValue(type, value);
+                break;
+        }
+//        u.setLastChanger(owner);
+    }
+
+    void updateUniformParam(String paramName, VarType type, Object value) {
+        updateUniformParam(paramName, type, value, false);
+    }
+
+    /**
+     * Returns true if the technique must be reloaded.
+     * <p>
+     * If a technique needs to reload, then the {@link Material} should
+     * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
+     * technique.
+     * 
+     * @return true if the technique must be reloaded.
+     */
+    public boolean isNeedReload() {
+        return needReload;
+    }
+
+    /**
+     * Prepares the technique for use by loading the shader and setting
+     * the proper defines based on material parameters.
+     * 
+     * @param assetManager The asset manager to use for loading shaders.
+     */
+    public void makeCurrent(AssetManager assetManager) {
+        // check if reload is needed..
+        if (def.isUsingShaders()) {
+            DefineList newDefines = new DefineList();
+            Collection<MatParam> params = owner.getParams();
+            for (MatParam param : params) {
+                String defineName = def.getShaderParamDefine(param.getName());
+                if (defineName != null) {
+                    newDefines.set(defineName, param.getVarType(), param.getValue());
+                }
+            }
+
+            if (!needReload && defines.getCompiled().equals(newDefines.getCompiled())) {
+                newDefines = null;
+                // defines have not been changed..
+            } else {
+                defines.clear();
+                defines.addFrom(newDefines);
+                // defines changed, recompile needed
+                loadShader(assetManager);
+            }
+        }
+    }
+
+    private void loadShader(AssetManager manager) {
+        // recompute define list
+        DefineList allDefines = new DefineList();
+        allDefines.addFrom(def.getShaderPresetDefines());
+        allDefines.addFrom(defines);
+
+        ShaderKey key = new ShaderKey(def.getVertexShaderName(),
+                def.getFragmentShaderName(),
+                allDefines,
+                def.getShaderLanguage());
+        shader = manager.loadShader(key);
+        if (shader == null) {
+            logger.warning("Failed to reload shader!");
+            return;
+        }
+
+        // refresh the uniform links
+        //owner.updateUniformLinks();
+
+        // register the world bound uniforms
+        worldBindUniforms.clear();
+        if (def.getWorldBindings() != null) {
+           for (UniformBinding binding : def.getWorldBindings()) {
+               Uniform uniform = shader.getUniform("g_" + binding.name());
+               uniform.setBinding(binding);
+               if (uniform != null) {
+                   worldBindUniforms.add(uniform);
+               }
+           }
+        }
+
+        needReload = false;
+    }
+    
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(def, "def", null);
+        // TODO:
+        // oc.write(owner, "owner", null);
+        oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
+        oc.write(defines, "defines", null);
+        oc.write(shader, "shader", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        def = (TechniqueDef) ic.readSavable("def", null);
+        worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
+        defines = (DefineList) ic.readSavable("defines", null);
+        shader = (Shader) ic.readSavable("shader", null);
+        //if (shader != null)
+        //    owner.updateUniformLinks();
+    }
+}
diff --git a/engine/src/core/com/jme3/material/TechniqueDef.java b/engine/src/core/com/jme3/material/TechniqueDef.java
new file mode 100644
index 0000000..aaeb340
--- /dev/null
+++ b/engine/src/core/com/jme3/material/TechniqueDef.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import com.jme3.export.*;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Describes a technique definition.
+ * 
+ * @author Kirill Vainer
+ */
+public class TechniqueDef implements Savable {
+
+    /**
+     * Describes light rendering mode.
+     */
+    public enum LightMode {
+        /**
+         * Disable light-based rendering
+         */
+        Disable,
+        
+        /**
+         * Enable light rendering by using a single pass. 
+         * <p>
+         * An array of light positions and light colors is passed to the shader
+         * containing the world light list for the geometry being rendered.
+         */
+        SinglePass,
+        
+        /**
+         * Enable light rendering by using multi-pass rendering.
+         * <p>
+         * The geometry will be rendered once for each light. Each time the
+         * light position and light color uniforms are updated to contain
+         * the values for the current light. The ambient light color uniform
+         * is only set to the ambient light color on the first pass, future
+         * passes have it set to black.
+         */
+        MultiPass,
+        
+        /**
+         * Enable light rendering by using the 
+         * {@link Renderer#setLighting(com.jme3.light.LightList) renderer's setLighting} 
+         * method.
+         * <p>
+         * The specific details of rendering the lighting is up to the 
+         * renderer implementation.
+         */
+        FixedPipeline,
+    }
+
+    public enum ShadowMode {
+        Disable,
+        InPass,
+        PostPass,
+    }
+
+    private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
+    private String name;
+
+    private String vertName;
+    private String fragName;
+    private String shaderLang;
+    private DefineList presetDefines;
+    private boolean usesShaders;
+
+    private RenderState renderState;
+    private LightMode lightMode   = LightMode.Disable;
+    private ShadowMode shadowMode = ShadowMode.Disable;
+
+    private HashMap<String, String> defineParams;
+    private ArrayList<UniformBinding> worldBinds;
+
+    /**
+     * Creates a new technique definition.
+     * <p>
+     * Used internally by the J3M/J3MD loader.
+     * 
+     * @param name The name of the technique, should be set to <code>null</code>
+     * for default techniques.
+     */
+    public TechniqueDef(String name){
+        this.name = name == null ? "Default" : name;
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public TechniqueDef(){
+    }
+
+    /**
+     * Returns the name of this technique as specified in the J3MD file.
+     * Default techniques have the name "Default".
+     * 
+     * @return the name of this technique
+     */
+    public String getName(){
+        return name;
+    }
+
+    /**
+     * Returns the light mode.
+     * @return the light mode.
+     * @see LightMode
+     */
+    public LightMode getLightMode() {
+        return lightMode;
+    }
+
+    /**
+     * Set the light mode
+     * 
+     * @param lightMode the light mode
+     * 
+     * @see LightMode
+     */
+    public void setLightMode(LightMode lightMode) {
+        this.lightMode = lightMode;
+    }
+
+    /**
+     * Returns the shadow mode.
+     * @return the shadow mode.
+     */
+    public ShadowMode getShadowMode() {
+        return shadowMode;
+    }
+
+    /**
+     * Set the shadow mode.
+     * 
+     * @param shadowMode the shadow mode.
+     * 
+     * @see ShadowMode
+     */
+    public void setShadowMode(ShadowMode shadowMode) {
+        this.shadowMode = shadowMode;
+    }
+
+    /**
+     * Returns the render state that this technique is using
+     * @return the render state that this technique is using
+     * @see #setRenderState(com.jme3.material.RenderState) 
+     */
+    public RenderState getRenderState() {
+        return renderState;
+    }
+
+    /**
+     * Sets the render state that this technique is using.
+     * 
+     * @param renderState the render state that this technique is using.
+     * 
+     * @see RenderState
+     */
+    public void setRenderState(RenderState renderState) {
+        this.renderState = renderState;
+    }
+
+    /**
+     * Returns true if this technique uses shaders, false otherwise.
+     * 
+     * @return true if this technique uses shaders, false otherwise.
+     * 
+     * @see #setShaderFile(java.lang.String, java.lang.String, java.lang.String) 
+     */
+    public boolean isUsingShaders(){
+        return usesShaders;
+    }
+
+    /**
+     * Gets the {@link Caps renderer capabilities} that are required
+     * by this technique.
+     * 
+     * @return the required renderer capabilities
+     */
+    public EnumSet<Caps> getRequiredCaps() {
+        return requiredCaps;
+    }
+
+    /**
+     * Sets the shaders that this technique definition will use.
+     * 
+     * @param vertexShader The name of the vertex shader
+     * @param fragmentShader The name of the fragment shader
+     * @param shaderLanguage The shader language
+     */
+    public void setShaderFile(String vertexShader, String fragmentShader, String shaderLanguage){
+        this.vertName = vertexShader;
+        this.fragName = fragmentShader;
+        this.shaderLang = shaderLanguage;
+
+        Caps langCap = Caps.valueOf(shaderLanguage);
+        requiredCaps.add(langCap);
+
+        usesShaders = true;
+    }
+
+    /**
+     * Returns the define name which the given material parameter influences.
+     * 
+     * @param paramName The parameter name to look up
+     * @return The define name
+     * 
+     * @see #addShaderParamDefine(java.lang.String, java.lang.String) 
+     */
+    public String getShaderParamDefine(String paramName){
+        if (defineParams == null)
+            return null;
+        
+        return defineParams.get(paramName);
+    }
+
+    /**
+     * Adds a define linked to a material parameter.
+     * <p>
+     * Any time the material parameter on the parent material is altered,
+     * the appropriate define on the technique will be modified as well. 
+     * See the method 
+     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
+     * on the exact details of how the material parameter changes the define.
+     * 
+     * @param paramName The name of the material parameter to link to.
+     * @param defineName The name of the define parameter, e.g. USE_LIGHTING
+     */
+    public void addShaderParamDefine(String paramName, String defineName){
+        if (defineParams == null)
+            defineParams = new HashMap<String, String>();
+
+        defineParams.put(paramName, defineName);
+    }
+
+    /**
+     * Returns the {@link DefineList} for the preset defines.
+     * 
+     * @return the {@link DefineList} for the preset defines.
+     * 
+     * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) 
+     */
+    public DefineList getShaderPresetDefines() {
+        return presetDefines;
+    }
+    
+    /**
+     * Adds a preset define. 
+     * <p>
+     * Preset defines do not depend upon any parameters to be activated,
+     * they are always passed to the shader as long as this technique is used.
+     * 
+     * @param defineName The name of the define parameter, e.g. USE_LIGHTING
+     * @param type The type of the define. See 
+     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
+     * to see why it matters.
+     * 
+     * @param value The value of the define
+     */
+    public void addShaderPresetDefine(String defineName, VarType type, Object value){
+        if (presetDefines == null)
+            presetDefines = new DefineList();
+
+        presetDefines.set(defineName, type, value);
+    }
+
+    /**
+     * Returns the name of the fragment shader used by the technique, or null
+     * if no fragment shader is specified.
+     * 
+     * @return the name of the fragment shader to be used.
+     */
+    public String getFragmentShaderName() {
+        return fragName;
+    }
+
+    
+    /**
+     * Returns the name of the vertex shader used by the technique, or null
+     * if no vertex shader is specified.
+     * 
+     * @return the name of the vertex shader to be used.
+     */
+    public String getVertexShaderName() {
+        return vertName;
+    }
+
+    /**
+     * Returns the shader language of the shaders used in this technique.
+     * 
+     * @return the shader language of the shaders used in this technique.
+     */
+    public String getShaderLanguage() {
+        return shaderLang;
+    }
+
+    /**
+     * Adds a new world parameter by the given name.
+     * 
+     * @param name The world parameter to add.
+     * @return True if the world parameter name was found and added
+     * to the list of world parameters, false otherwise.
+     */
+    public boolean addWorldParam(String name) {
+        if (worldBinds == null){
+            worldBinds = new ArrayList<UniformBinding>();
+        }
+        
+        try {
+            worldBinds.add( UniformBinding.valueOf(name) );
+            return true;
+        } catch (IllegalArgumentException ex){
+            return false;
+        }
+    }
+
+    /**
+     * Returns a list of world parameters that are used by this
+     * technique definition.
+     * 
+     * @return The list of world parameters
+     */
+    public List<UniformBinding> getWorldBindings() {
+        return worldBinds;
+    }
+
+    public void write(JmeExporter ex) throws IOException{
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(name, "name", null);
+        oc.write(vertName, "vertName", null);
+        oc.write(fragName, "fragName", null);
+        oc.write(shaderLang, "shaderLang", null);
+        oc.write(presetDefines, "presetDefines", null);
+        oc.write(lightMode, "lightMode", LightMode.Disable);
+        oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
+        oc.write(renderState, "renderState", null);
+        oc.write(usesShaders, "usesShaders", false);
+        // TODO: Finish this when Map<String, String> export is available
+//        oc.write(defineParams, "defineParams", null);
+        // TODO: Finish this when List<Enum> export is available
+//        oc.write(worldBinds, "worldBinds", null);
+    }
+
+    public void read(JmeImporter im) throws IOException{
+        InputCapsule ic = im.getCapsule(this);
+        name = ic.readString("name", null);
+        vertName = ic.readString("vertName", null);
+        fragName = ic.readString("fragName", null);
+        shaderLang = ic.readString("shaderLang", null);
+        presetDefines = (DefineList) ic.readSavable("presetDefines", null);
+        lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
+        shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
+        renderState = (RenderState) ic.readSavable("renderState", null);
+        usesShaders = ic.readBoolean("usesShaders", false);
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/material/package.html b/engine/src/core/com/jme3/material/package.html
new file mode 100644
index 0000000..9af9cc8
--- /dev/null
+++ b/engine/src/core/com/jme3/material/package.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.material</code> package contains classes for manipulating
+jMonkeyEngine materials.
+Materials are applied to {@link com.jme3.scene.Geometry geometries} in the
+scene. 
+Each geometry has a single material which is used to render that
+geometry.
+<p>
+Materials (also known as material instances) are extended from
+material definitions.
+
+<h3>Material definitions</h3>
+<p>
+Material definitions provide the "logic" for the material. Usually a shader that 
+will handle drawing the object, and corresponding parameters that allow
+configuration of the shader.
+Material definitions can be created through J3MD files.
+The J3MD file abstracts the shader and its configuration away from the user, allowing a
+simple interface where one can simply set a few parameters on the material to change its
+appearance and the way its handled.
+
+<h3>Techniques</h3>
+<p>
+Techniques specify a specific way of rendering a material. Typically
+a technique is used to implement the same material for each configuration
+of the system. For GPUs that do not support shaders, a "fixed function pipeline"
+technique could exist to take care of rendering for that configuration
+
+<h3>Render states</h3>
+<p>
+See {@link com.jme3.material.RenderState}.
+
+<h3>Example Usage</h3>
+<p>
+Creating a textured material
+<code>
+// Create a material instance
+Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+
+// Load the texture. 
+Texture tex = assetManager.loadTexture("Textures/Test/Test.jpg");
+
+// Set the parameters
+mat.setTexture("ColorMap", tex);
+</code>
+
+
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/math/AbstractTriangle.java b/engine/src/core/com/jme3/math/AbstractTriangle.java
new file mode 100644
index 0000000..1ea6da5
--- /dev/null
+++ b/engine/src/core/com/jme3/math/AbstractTriangle.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+
+public abstract class AbstractTriangle implements Collidable {
+
+    public abstract Vector3f get1();
+    public abstract Vector3f get2();
+    public abstract Vector3f get3();
+    public abstract void set(Vector3f v1, Vector3f v2, Vector3f v3);
+
+    public int collideWith(Collidable other, CollisionResults results){
+        return other.collideWith(this, results);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/math/ColorRGBA.java b/engine/src/core/com/jme3/math/ColorRGBA.java
new file mode 100644
index 0000000..a22d453
--- /dev/null
+++ b/engine/src/core/com/jme3/math/ColorRGBA.java
@@ -0,0 +1,549 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+

+/**

+ * <code>ColorRGBA</code> defines a color made from a collection of red, green

+ * and blue values. An alpha value determines is transparency. All values must

+ * be between 0 and 1. If any value is set higher or lower than these

+ * constraints they are clamped to the min or max. That is, if a value smaller

+ * than zero is set the value clamps to zero. If a value higher than 1 is

+ * passed, that value is clamped to 1. However, because the attributes r, g, b,

+ * a are public for efficiency reasons, they can be directly modified with

+ * invalid values. The client should take care when directly addressing the

+ * values. A call to clamp will assure that the values are within the

+ * constraints.

+ *

+ * @author Mark Powell

+ * @version $Id: ColorRGBA.java,v 1.29 2007/09/09 18:25:14 irrisor Exp $

+ */

+public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+    /**

+     * the color black (0,0,0).

+     */

+    public static final ColorRGBA Black = new ColorRGBA(0f, 0f, 0f, 1f);

+    /**

+     * the color white (1,1,1).

+     */

+    public static final ColorRGBA White = new ColorRGBA(1f, 1f, 1f, 1f);

+    /**

+     * the color gray (.2,.2,.2).

+     */

+    public static final ColorRGBA DarkGray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f);

+    /**

+     * the color gray (.5,.5,.5).

+     */

+    public static final ColorRGBA Gray = new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f);

+    /**

+     * the color gray (.8,.8,.8).

+     */

+    public static final ColorRGBA LightGray = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f);

+    /**

+     * the color red (1,0,0).

+     */

+    public static final ColorRGBA Red = new ColorRGBA(1f, 0f, 0f, 1f);

+    /**

+     * the color green (0,1,0).

+     */

+    public static final ColorRGBA Green = new ColorRGBA(0f, 1f, 0f, 1f);

+    /**

+     * the color blue (0,0,1).

+     */

+    public static final ColorRGBA Blue = new ColorRGBA(0f, 0f, 1f, 1f);

+    /**

+     * the color yellow (1,1,0).

+     */

+    public static final ColorRGBA Yellow = new ColorRGBA(1f, 1f, 0f, 1f);

+    /**

+     * the color magenta (1,0,1).

+     */

+    public static final ColorRGBA Magenta = new ColorRGBA(1f, 0f, 1f, 1f);

+    /**

+     * the color cyan (0,1,1).

+     */

+    public static final ColorRGBA Cyan = new ColorRGBA(0f, 1f, 1f, 1f);

+    /**

+     * the color orange (251/255, 130/255,0).

+     */

+    public static final ColorRGBA Orange = new ColorRGBA(251f / 255f, 130f / 255f, 0f, 1f);

+    /**

+     * the color brown (65/255, 40/255, 25/255).

+     */

+    public static final ColorRGBA Brown = new ColorRGBA(65f / 255f, 40f / 255f, 25f / 255f, 1f);

+    /**

+     * the color pink (1, 0.68, 0.68).

+     */

+    public static final ColorRGBA Pink = new ColorRGBA(1f, 0.68f, 0.68f, 1f);

+    /**

+     * the black color with no alpha (0, 0, 0, 0);

+     */

+    public static final ColorRGBA BlackNoAlpha = new ColorRGBA(0f, 0f, 0f, 0f);

+    /**

+     * The red component of the color.

+     */

+    public float r;

+    /**

+     * The green component of the color.

+     */

+    public float g;

+    /**

+     * the blue component of the color.

+     */

+    public float b;

+    /**

+     * the alpha component of the color. 0 is transparent and 1 is opaque

+     */

+    public float a;

+

+    /**

+     * Constructor instantiates a new <code>ColorRGBA</code> object. This

+     * color is the default "white" with all values 1.

+     *

+     */

+    public ColorRGBA() {

+        r = g = b = a = 1.0f;

+    }

+

+    /**

+     * Constructor instantiates a new <code>ColorRGBA</code> object. The

+     * values are defined as passed parameters. These values are then clamped

+     * to insure that they are between 0 and 1.

+     * @param r the red component of this color.

+     * @param g the green component of this color.

+     * @param b the blue component of this color.

+     * @param a the alpha component of this color.

+     */

+    public ColorRGBA(float r, float g, float b, float a) {

+        this.r = r;

+        this.g = g;

+        this.b = b;

+        this.a = a;

+    }

+

+    /**

+     * Copy constructor creates a new <code>ColorRGBA</code> object, based on

+     * a provided color.

+     * @param rgba the <code>ColorRGBA</code> object to copy.

+     */

+    public ColorRGBA(ColorRGBA rgba) {

+        this.a = rgba.a;

+        this.r = rgba.r;

+        this.g = rgba.g;

+        this.b = rgba.b;

+    }

+

+    /**

+     *

+     * <code>set</code> sets the RGBA values of this color. The values are then

+     * clamped to insure that they are between 0 and 1.

+     *

+     * @param r the red component of this color.

+     * @param g the green component of this color.

+     * @param b the blue component of this color.

+     * @param a the alpha component of this color.

+     * @return this

+     */

+    public ColorRGBA set(float r, float g, float b, float a) {

+        this.r = r;

+        this.g = g;

+        this.b = b;

+        this.a = a;

+        return this;

+    }

+

+    /**

+     * <code>set</code> sets the values of this color to those set by a parameter

+     * color.

+     *

+     * @param rgba ColorRGBA the color to set this color to.

+     * @return this

+     */

+    public ColorRGBA set(ColorRGBA rgba) {

+        if (rgba == null) {

+            r = 0;

+            g = 0;

+            b = 0;

+            a = 0;

+        } else {

+            r = rgba.r;

+            g = rgba.g;

+            b = rgba.b;

+            a = rgba.a;

+        }

+        return this;

+    }

+

+    /**

+     * <code>clamp</code> insures that all values are between 0 and 1. If any

+     * are less than 0 they are set to zero. If any are more than 1 they are

+     * set to one.

+     *

+     */

+    public void clamp() {

+        if (r < 0) {

+            r = 0;

+        } else if (r > 1) {

+            r = 1;

+        }

+

+        if (g < 0) {

+            g = 0;

+        } else if (g > 1) {

+            g = 1;

+        }

+

+        if (b < 0) {

+            b = 0;

+        } else if (b > 1) {

+            b = 1;

+        }

+

+        if (a < 0) {

+            a = 0;

+        } else if (a > 1) {

+            a = 1;

+        }

+    }

+

+    /**

+     *

+     * <code>getColorArray</code> retrieves the color values of this object as

+     * a four element float array.

+     * @return the float array that contains the color elements.

+     */

+    public float[] getColorArray() {

+        return new float[]{r, g, b, a};

+    }

+

+    /**

+     * Stores the current r/g/b/a values into the tempf array.  The tempf array must have a

+     * length of 4 or greater, or an array index out of bounds exception will be thrown.

+     * @param store The array of floats to store the values into.

+     * @return The float[] after storage.

+     */

+    public float[] getColorArray(float[] store) {

+        store[0] = r;

+        store[1] = g;

+        store[2] = b;

+        store[3] = a;

+        return store;

+    }

+

+    public float getAlpha() {

+        return a;

+    }

+

+    public float getRed() {

+        return r;

+    }

+

+    public float getBlue() {

+        return b;

+    }

+

+    public float getGreen() {

+        return g;

+    }

+

+    /**

+     * Sets this color to the interpolation by changeAmnt from this to the finalColor

+     * this=(1-changeAmnt)*this + changeAmnt * finalColor

+     * @param finalColor The final color to interpolate towards

+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage

+     *  change from this towards finalColor

+     */

+    public void interpolate(ColorRGBA finalColor, float changeAmnt) {

+        this.r = (1 - changeAmnt) * this.r + changeAmnt * finalColor.r;

+        this.g = (1 - changeAmnt) * this.g + changeAmnt * finalColor.g;

+        this.b = (1 - changeAmnt) * this.b + changeAmnt * finalColor.b;

+        this.a = (1 - changeAmnt) * this.a + changeAmnt * finalColor.a;

+    }

+

+    /**

+     * Sets this color to the interpolation by changeAmnt from beginColor to finalColor

+     * this=(1-changeAmnt)*beginColor + changeAmnt * finalColor

+     * @param beginColor The begining color (changeAmnt=0)

+     * @param finalColor The final color to interpolate towards (changeAmnt=1)

+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage

+     *  change from beginColor towards finalColor

+     */

+    public void interpolate(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmnt) {

+        this.r = (1 - changeAmnt) * beginColor.r + changeAmnt * finalColor.r;

+        this.g = (1 - changeAmnt) * beginColor.g + changeAmnt * finalColor.g;

+        this.b = (1 - changeAmnt) * beginColor.b + changeAmnt * finalColor.b;

+        this.a = (1 - changeAmnt) * beginColor.a + changeAmnt * finalColor.a;

+    }

+

+    /**

+     *

+     * <code>randomColor</code> is a utility method that generates a random

+     * color.

+     *

+     * @return a random color.

+     */

+    public static ColorRGBA randomColor() {

+        ColorRGBA rVal = new ColorRGBA(0, 0, 0, 1);

+        rVal.r = FastMath.nextRandomFloat();

+        rVal.g = FastMath.nextRandomFloat();

+        rVal.b = FastMath.nextRandomFloat();

+        return rVal;

+    }

+

+    /**

+     * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and

+     * returns the result as a new ColorRGBA.  Used as a way of combining colors and lights.

+     * @param c The color to multiply.

+     * @return The new ColorRGBA.  this*c

+     */

+    public ColorRGBA mult(ColorRGBA c) {

+        return new ColorRGBA(c.r * r, c.g * g, c.b * b, c.a * a);

+    }

+

+    /**

+     * Multiplies each r/g/b/a of this color by the given scalar and

+     * returns the result as a new ColorRGBA.  Used as a way of making colors dimmer

+     * or brighter..

+     * @param scalar The scalar to multiply.

+     * @return The new ColorRGBA.  this*scalar

+     */

+    public ColorRGBA mult(float scalar) {

+        return new ColorRGBA(scalar * r, scalar * g, scalar * b, scalar * a);

+    }

+

+    /**

+     * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and

+     * returns the result as a new ColorRGBA.  Used as a way of combining colors and lights.

+     * @param scalar scalar to multiply with

+     * @return The new ColorRGBA.  this*c

+     */

+    public ColorRGBA multLocal(float scalar) {

+        this.r *= scalar;

+        this.g *= scalar;

+        this.b *= scalar;

+        this.a *= scalar;

+        return this;

+    }

+

+    /**

+     * Adds each r/g/b/a of this color by the r/g/b/a of the given color and

+     * returns the result as a new ColorRGBA.

+     * @param c The color to add.

+     * @return The new ColorRGBA.  this+c

+     */

+    public ColorRGBA add(ColorRGBA c) {

+        return new ColorRGBA(c.r + r, c.g + g, c.b + b, c.a + a);

+    }

+

+    /**

+     * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and

+     * returns the result as a new ColorRGBA.  Used as a way of combining colors and lights.

+     * @param c The color to multiply.

+     * @return The new ColorRGBA.  this*c

+     */

+    public ColorRGBA addLocal(ColorRGBA c) {

+        set(c.r + r, c.g + g, c.b + b, c.a + a);

+        return this;

+    }

+

+    /**

+     * <code>toString</code> returns the string representation of this color.

+     * The format of the string is:<br>

+     * <Class Name>: [R=RR.RRRR, G=GG.GGGG, B=BB.BBBB, A=AA.AAAA]

+     * @return the string representation of this color.

+     */

+    public String toString() {

+        return "Color[" + r + ", " + g + ", " + b + ", " + a + "]";

+    }

+

+    @Override

+    public ColorRGBA clone() {

+        try {

+            return (ColorRGBA) super.clone();

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError(); // can not happen

+        }

+    }

+

+    /**

+     * Saves this ColorRGBA into the given float[] object.

+     *

+     * @param floats

+     *            The float[] to take this ColorRGBA. If null, a new float[4] is

+     *            created.

+     * @return The array, with R, G, B, A float values in that order

+     */

+    public float[] toArray(float[] floats) {

+        if (floats == null) {

+            floats = new float[4];

+        }

+        floats[0] = r;

+        floats[1] = g;

+        floats[2] = b;

+        floats[3] = a;

+        return floats;

+    }

+

+    /**

+     * <code>equals</code> returns true if this color is logically equivalent

+     * to a given color. That is, if the values of the two colors are the same.

+     * False is returned otherwise.

+     * @param o the object to compare againts.

+     * @return true if the colors are equal, false otherwise.

+     */

+    public boolean equals(Object o) {

+        if (!(o instanceof ColorRGBA)) {

+            return false;

+        }

+

+        if (this == o) {

+            return true;

+        }

+

+        ColorRGBA comp = (ColorRGBA) o;

+        if (Float.compare(r, comp.r) != 0) {

+            return false;

+        }

+        if (Float.compare(g, comp.g) != 0) {

+            return false;

+        }

+        if (Float.compare(b, comp.b) != 0) {

+            return false;

+        }

+        if (Float.compare(a, comp.a) != 0) {

+            return false;

+        }

+        return true;

+    }

+

+    /**

+     * <code>hashCode</code> returns a unique code for this color object based

+     * on it's values. If two colors are logically equivalent, they will return

+     * the same hash code value.

+     * @return the hash code value of this color.

+     */

+    public int hashCode() {

+        int hash = 37;

+        hash += 37 * hash + Float.floatToIntBits(r);

+        hash += 37 * hash + Float.floatToIntBits(g);

+        hash += 37 * hash + Float.floatToIntBits(b);

+        hash += 37 * hash + Float.floatToIntBits(a);

+        return hash;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(r, "r", 0);

+        capsule.write(g, "g", 0);

+        capsule.write(b, "b", 0);

+        capsule.write(a, "a", 0);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        r = capsule.readFloat("r", 0);

+        g = capsule.readFloat("g", 0);

+        b = capsule.readFloat("b", 0);

+        a = capsule.readFloat("a", 0);

+    }

+

+    public byte[] asBytesRGBA() {

+        byte[] store = new byte[4];

+        store[0] = (byte) ((int) (r * 255) & 0xFF);

+        store[1] = (byte) ((int) (g * 255) & 0xFF);

+        store[2] = (byte) ((int) (b * 255) & 0xFF);

+        store[3] = (byte) ((int) (a * 255) & 0xFF);

+        return store;

+    }

+

+    public int asIntARGB() {

+        int argb = (((int) (a * 255) & 0xFF) << 24)

+                | (((int) (r * 255) & 0xFF) << 16)

+                | (((int) (g * 255) & 0xFF) << 8)

+                | (((int) (b * 255) & 0xFF));

+        return argb;

+    }

+

+    public int asIntRGBA() {

+        int rgba = (((int) (r * 255) & 0xFF) << 24)

+                | (((int) (g * 255) & 0xFF) << 16)

+                | (((int) (b * 255) & 0xFF) << 8)

+                | (((int) (a * 255) & 0xFF));

+        return rgba;

+    }

+

+    public int asIntABGR() {

+        int abgr = (((int) (a * 255) & 0xFF) << 24)

+                | (((int) (b * 255) & 0xFF) << 16)

+                | (((int) (g * 255) & 0xFF) << 8)

+                | (((int) (r * 255) & 0xFF));

+        return abgr;

+    }

+

+    public void fromIntARGB(int color) {

+        a = ((byte) (color >> 24) & 0xFF) / 255f;

+        r = ((byte) (color >> 16) & 0xFF) / 255f;

+        g = ((byte) (color >> 8) & 0xFF) / 255f;

+        b = ((byte) (color) & 0xFF) / 255f;

+    }

+

+    public void fromIntRGBA(int color) {

+        r = ((byte) (color >> 24) & 0xFF) / 255f;

+        g = ((byte) (color >> 16) & 0xFF) / 255f;

+        b = ((byte) (color >> 8) & 0xFF) / 255f;

+        a = ((byte) (color) & 0xFF) / 255f;

+    }

+

+    /**

+     * Transform the current ColorRGBA to a Vector3f using

+     * x = r, y = g, z = b. The Alpha value is not used.

+     *

+     * This method is useful to use for shaders assignment.

+     * @return A Vector3f containing the RGB value of current color definition.

+     */

+    public Vector3f toVector3f() {

+        return new Vector3f(r, g, b);

+    }

+

+    /**

+     * Transform the current ColorRGBA to a Vector4f using

+     * x = r, y = g, z = b, w = a.

+     *

+     * This method is useful to use for shaders assignment.

+     * @return A Vector4f containing the RGBA value of current color definition.

+     */

+    public Vector4f toVector4f() {

+        return new Vector4f(r, g, b, a);

+    }

+}

diff --git a/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java b/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java
new file mode 100644
index 0000000..079880f
--- /dev/null
+++ b/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java
@@ -0,0 +1,133 @@
+package com.jme3.math;

+

+import com.jme3.math.Spline.SplineType;

+import java.util.List;

+

+/**

+ * This class offers methods to help with curves and surfaces calculations.

+ * @author Marcin Roguski (Kealthas)

+ */

+public class CurveAndSurfaceMath {

+	private static final float KNOTS_MINIMUM_DELTA = 0.0001f;

+

+	/**

+	 * A private constructor is defined to avoid instatiation of this class.

+	 */

+	private CurveAndSurfaceMath() {}

+	

+	/**

+	 * This method interpolates tha data for the nurbs curve.

+	 * @param u

+	 *            the u value

+	 * @param nurbSpline

+	 *            the nurbs spline definition

+	 * @param store

+	 *            the resulting point in 3D space

+	 */

+	public static void interpolateNurbs(float u, Spline nurbSpline, Vector3f store) {

+		if (nurbSpline.getType() != SplineType.Nurb) {

+			throw new IllegalArgumentException("Given spline is not of a NURB type!");

+		}

+		List<Vector3f> controlPoints = nurbSpline.getControlPoints();

+		float[] weights = nurbSpline.getWeights();

+		List<Float> knots = nurbSpline.getKnots();

+		int controlPointAmount = controlPoints.size();

+

+		store.set(Vector3f.ZERO);

+		float delimeter = 0;

+		for (int i = 0; i < controlPointAmount; ++i) {

+			float val = weights[i] * CurveAndSurfaceMath.computeBaseFunctionValue(i, nurbSpline.getBasisFunctionDegree(), u, knots);

+			store.addLocal(nurbSpline.getControlPoints().get(i)

+					.mult(val));

+			delimeter += val;

+		}

+		store.divideLocal(delimeter);

+	}

+

+	/**

+	 * This method interpolates tha data for the nurbs surface.

+	 * 

+	 * @param u

+	 *            the u value

+	 * @param v

+	 *            the v value

+	 * @param controlPoints

+	 *            the nurbs' control points

+	 * @param knots

+	 *            the nurbs' knots

+	 * @param basisUFunctionDegree

+	 *            the degree of basis U function

+	 * @param basisVFunctionDegree

+	 *            the degree of basis V function

+	 * @param store

+	 *            the resulting point in 3D space

+	 */

+	public static void interpolate(float u, float v, List<List<Vector4f>> controlPoints, List<Float>[] knots, 

+			int basisUFunctionDegree, int basisVFunctionDegree, Vector3f store) {

+		store.set(Vector3f.ZERO);

+		float delimeter = 0;

+		int vControlPointsAmount = controlPoints.size();

+		int uControlPointsAmount = controlPoints.get(0).size();

+		for (int i = 0; i < vControlPointsAmount; ++i) {

+			for (int j = 0; j < uControlPointsAmount; ++j) {

+				Vector4f controlPoint = controlPoints.get(i).get(j);

+				float val = controlPoint.w

+								* CurveAndSurfaceMath.computeBaseFunctionValue(i, basisVFunctionDegree, v, knots[1])

+								* CurveAndSurfaceMath.computeBaseFunctionValue(j, basisUFunctionDegree, u, knots[0]);

+				store.addLocal(controlPoint.x * val, controlPoint.y * val, controlPoint.z * val);

+				delimeter += val;

+			}

+		}

+		store.divideLocal(delimeter);

+	}

+

+	/**

+	 * This method prepares the knots to be used. If the knots represent non-uniform B-splines (first and last knot values are being

+	 * repeated) it leads to NaN results during calculations. This method adds a small number to each of such knots to avoid NaN's.

+	 * @param knots

+	 *            the knots to be prepared to use

+	 * @param basisFunctionDegree

+	 *            the degree of basis function

+	 */

+	// TODO: improve this; constant delta may lead to errors if the difference between tha last repeated

+	// point and the following one is lower than it

+	public static void prepareNurbsKnots(List<Float> knots, int basisFunctionDegree) {

+		float delta = KNOTS_MINIMUM_DELTA;

+		float prevValue = knots.get(0).floatValue();

+		for(int i=1;i<knots.size();++i) {

+			float value = knots.get(i).floatValue();

+			if(value<=prevValue) {

+				value += delta;

+				knots.set(i, Float.valueOf(value));

+				delta += KNOTS_MINIMUM_DELTA;

+			} else {

+				delta = KNOTS_MINIMUM_DELTA;//reset the delta's value

+			}

+			

+			prevValue = value;

+		}

+	}

+

+	/**

+	 * This method computes the base function value for the NURB curve.

+	 * @param i

+	 *            the knot index

+	 * @param k

+	 *            the base function degree

+	 * @param t

+	 *            the knot value

+	 * @param knots

+	 *            the knots' values

+	 * @return the base function value

+	 */

+	private static float computeBaseFunctionValue(int i, int k, float t, List<Float> knots) {

+		if (k == 1) {

+			return knots.get(i) <= t && t < knots.get(i + 1) ? 1.0f : 0.0f;

+		} else {

+			return (t - knots.get(i)) / (knots.get(i + k - 1) - knots.get(i)) * 

+					CurveAndSurfaceMath.computeBaseFunctionValue(i, k - 1, t, knots)

+					+ (knots.get(i + k) - t) / (knots.get(i + k) - knots.get(i + 1)) * 

+					CurveAndSurfaceMath.computeBaseFunctionValue(i + 1, k - 1, t, knots);

+		}

+	}

+}

diff --git a/engine/src/core/com/jme3/math/Eigen3f.java b/engine/src/core/com/jme3/math/Eigen3f.java
new file mode 100644
index 0000000..e356057
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Eigen3f.java
@@ -0,0 +1,421 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class Eigen3f implements java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+    

+    private static final Logger logger = Logger.getLogger(Eigen3f.class

+            .getName());

+

+    float[] eigenValues = new float[3];

+    Vector3f[] eigenVectors = new Vector3f[3];

+

+    static final double ONE_THIRD_DOUBLE = 1.0 / 3.0;

+    static final double ROOT_THREE_DOUBLE = Math.sqrt(3.0);

+

+    

+    public Eigen3f() {

+    	

+    }

+    

+    public Eigen3f(Matrix3f data) {

+        calculateEigen(data);

+    }

+

+	public void calculateEigen(Matrix3f data) {

+		// prep work...

+        eigenVectors[0] = new Vector3f();

+        eigenVectors[1] = new Vector3f();

+        eigenVectors[2] = new Vector3f();

+

+        Matrix3f scaledData = new Matrix3f(data);

+        float maxMagnitude = scaleMatrix(scaledData);

+

+        // Compute the eigenvalues using double-precision arithmetic.

+        double roots[] = new double[3];

+        computeRoots(scaledData, roots);

+        eigenValues[0] = (float) roots[0];

+        eigenValues[1] = (float) roots[1];

+        eigenValues[2] = (float) roots[2];

+

+        float[] maxValues = new float[3];

+        Vector3f[] maxRows = new Vector3f[3];

+        maxRows[0] = new Vector3f();

+        maxRows[1] = new Vector3f();

+        maxRows[2] = new Vector3f();

+        

+        for (int i = 0; i < 3; i++) {

+            Matrix3f tempMatrix = new Matrix3f(scaledData);

+            tempMatrix.m00 -= eigenValues[i];

+            tempMatrix.m11 -= eigenValues[i];

+            tempMatrix.m22 -= eigenValues[i];

+            float[] val = new float[1];

+            val[0] = maxValues[i];

+            if (!positiveRank(tempMatrix, val, maxRows[i])) {

+                // Rank was 0 (or very close to 0), so just return.

+                // Rescale back to the original size.

+                if (maxMagnitude > 1f) {

+                    for (int j = 0; j < 3; j++) {

+                        eigenValues[j] *= maxMagnitude;

+                    }

+                }

+

+                eigenVectors[0].set(Vector3f.UNIT_X);

+                eigenVectors[1].set(Vector3f.UNIT_Y);

+                eigenVectors[2].set(Vector3f.UNIT_Z);

+                return;

+            }

+            maxValues[i] = val[0];

+        }

+

+        float maxCompare = maxValues[0];

+        int i = 0;

+        if (maxValues[1] > maxCompare) {

+            maxCompare = maxValues[1];

+            i = 1;

+        }

+        if (maxValues[2] > maxCompare) {

+            i = 2;

+        }

+

+        // use the largest row to compute and order the eigen vectors.

+        switch (i) {

+            case 0:

+                maxRows[0].normalizeLocal();

+                computeVectors(scaledData, maxRows[0], 1, 2, 0);

+                break;

+            case 1:

+                maxRows[1].normalizeLocal();

+                computeVectors(scaledData, maxRows[1], 2, 0, 1);

+                break;

+            case 2:

+                maxRows[2].normalizeLocal();

+                computeVectors(scaledData, maxRows[2], 0, 1, 2);

+                break;

+        }

+

+        // Rescale the values back to the original size.

+        if (maxMagnitude > 1f) {

+            for (i = 0; i < 3; i++) {

+                eigenValues[i] *= maxMagnitude;

+            }

+        }

+	}

+

+    /**

+     * Scale the matrix so its entries are in [-1,1]. The scaling is applied

+     * only when at least one matrix entry has magnitude larger than 1.

+     * 

+     * @return the max magnitude in this matrix

+     */

+    private float scaleMatrix(Matrix3f mat) {

+

+        float max = FastMath.abs(mat.m00);

+        float abs = FastMath.abs(mat.m01);

+

+        if (abs > max) {

+            max = abs;

+        }

+        abs = FastMath.abs(mat.m02);

+        if (abs > max) {

+            max = abs;

+        }

+        abs = FastMath.abs(mat.m11);

+        if (abs > max) {

+            max = abs;

+        }

+        abs = FastMath.abs(mat.m12);

+        if (abs > max) {

+            max = abs;

+        }

+        abs = FastMath.abs(mat.m22);

+        if (abs > max) {

+            max = abs;

+        }

+

+        if (max > 1f) {

+            float fInvMax = 1f / max;

+            mat.multLocal(fInvMax);

+        }

+

+        return max;

+    }

+

+    /**

+     * Compute the eigenvectors of the given Matrix, using the 

+     * @param mat

+     * @param vect

+     * @param index1

+     * @param index2

+     * @param index3

+     */

+    private void computeVectors(Matrix3f mat, Vector3f vect, int index1,

+            int index2, int index3) {

+        Vector3f vectorU = new Vector3f(), vectorV = new Vector3f();

+        Vector3f.generateComplementBasis(vectorU, vectorV, vect);

+

+        Vector3f tempVect = mat.mult(vectorU);

+        float p00 = eigenValues[index3] - vectorU.dot(tempVect);

+        float p01 = vectorV.dot(tempVect);

+        float p11 = eigenValues[index3] - vectorV.dot(mat.mult(vectorV));

+        float invLength;

+        float max = FastMath.abs(p00);

+        int row = 0;

+        float fAbs = FastMath.abs(p01);

+        if (fAbs > max) {

+            max = fAbs;

+        }

+        fAbs = FastMath.abs(p11);

+        if (fAbs > max) {

+            max = fAbs;

+            row = 1;

+        }

+

+        if (max >= FastMath.ZERO_TOLERANCE) {

+            if (row == 0) {

+                invLength = FastMath.invSqrt(p00 * p00 + p01 * p01);

+                p00 *= invLength;

+                p01 *= invLength;

+                vectorU.mult(p01, eigenVectors[index3])

+                        .addLocal(vectorV.mult(p00));

+            } else {

+                invLength = FastMath.invSqrt(p11 * p11 + p01 * p01);

+                p11 *= invLength;

+                p01 *= invLength;

+                vectorU.mult(p11, eigenVectors[index3])

+                        .addLocal(vectorV.mult(p01));

+            }

+        } else {

+            if (row == 0) {

+                eigenVectors[index3] = vectorV;

+            } else {

+                eigenVectors[index3] = vectorU;

+            }

+        }

+

+        Vector3f vectorS = vect.cross(eigenVectors[index3]);

+        mat.mult(vect, tempVect);

+        p00 = eigenValues[index1] - vect.dot(tempVect);

+        p01 = vectorS.dot(tempVect);

+        p11 = eigenValues[index1] - vectorS.dot(mat.mult(vectorS));

+        max = FastMath.abs(p00);

+        row = 0;

+        fAbs = FastMath.abs(p01);

+        if (fAbs > max) {

+            max = fAbs;

+        }

+        fAbs = FastMath.abs(p11);

+        if (fAbs > max) {

+            max = fAbs;

+            row = 1;

+        }

+

+        if (max >= FastMath.ZERO_TOLERANCE) {

+            if (row == 0) {

+                invLength = FastMath.invSqrt(p00 * p00 + p01 * p01);

+                p00 *= invLength;

+                p01 *= invLength;

+                eigenVectors[index1] = vect.mult(p01).add(vectorS.mult(p00));

+            } else {

+                invLength = FastMath.invSqrt(p11 * p11 + p01 * p01);

+                p11 *= invLength;

+                p01 *= invLength;

+                eigenVectors[index1] = vect.mult(p11).add(vectorS.mult(p01));

+            }

+        } else {

+            if (row == 0) {

+                eigenVectors[index1].set(vectorS);

+            } else {

+                eigenVectors[index1].set(vect);

+            }

+        }

+

+         eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]);

+    }

+

+    /**

+     * Check the rank of the given Matrix to determine if it is positive. While

+     * doing so, store the max magnitude entry in the given float store and the

+     * max row of the matrix in the Vector store.

+     * 

+     * @param matrix

+     *            the Matrix3f to analyze.

+     * @param maxMagnitudeStore

+     *            a float array in which to store (in the 0th position) the max

+     *            magnitude entry of the matrix.

+     * @param maxRowStore

+     *            a Vector3f to store the values of the row of the matrix

+     *            containing the max magnitude entry.

+     * @return true if the given matrix has a non 0 rank.

+     */

+    private boolean positiveRank(Matrix3f matrix, float[] maxMagnitudeStore, Vector3f maxRowStore) {

+        // Locate the maximum-magnitude entry of the matrix.

+        maxMagnitudeStore[0] = -1f;

+        int iRow, iCol, iMaxRow = -1;

+        for (iRow = 0; iRow < 3; iRow++) {

+            for (iCol = iRow; iCol < 3; iCol++) {

+                float fAbs = FastMath.abs(matrix.get(iRow, iCol));

+                if (fAbs > maxMagnitudeStore[0]) {

+                    maxMagnitudeStore[0] = fAbs;

+                    iMaxRow = iRow;

+                }

+            }

+        }

+

+        // Return the row containing the maximum, to be used for eigenvector

+        // construction.

+        maxRowStore.set(matrix.getRow(iMaxRow));

+

+        return maxMagnitudeStore[0] >= FastMath.ZERO_TOLERANCE;

+    }

+

+    /**

+     * Generate the base eigen values of the given matrix using double precision

+     * math.

+     * 

+     * @param mat

+     *            the Matrix3f to analyze.

+     * @param rootsStore

+     *            a double array to store the results in. Must be at least

+     *            length 3.

+     */

+    private void computeRoots(Matrix3f mat, double[] rootsStore) {

+        // Convert the unique matrix entries to double precision.

+        double a = mat.m00, b = mat.m01, c = mat.m02,

+                            d = mat.m11, e = mat.m12,

+                                         f = mat.m22;

+

+        // The characteristic equation is x^3 - c2*x^2 + c1*x - c0 = 0. The

+        // eigenvalues are the roots to this equation, all guaranteed to be

+        // real-valued, because the matrix is symmetric.

+        double char0 = a * d * f + 2.0 * b * c * e - a

+                * e * e - d * c * c - f * b * b;

+

+        double char1 = a * d - b * b + a * f - c * c

+                + d * f - e * e;

+

+        double char2 = a + d + f;

+

+        // Construct the parameters used in classifying the roots of the

+        // equation and in solving the equation for the roots in closed form.

+        double char2Div3 = char2 * ONE_THIRD_DOUBLE;

+        double abcDiv3 = (char1 - char2 * char2Div3) * ONE_THIRD_DOUBLE;

+        if (abcDiv3 > 0.0) {

+            abcDiv3 = 0.0;

+        }

+

+        double mbDiv2 = 0.5 * (char0 + char2Div3 * (2.0 * char2Div3 * char2Div3 - char1));

+

+        double q = mbDiv2 * mbDiv2 + abcDiv3 * abcDiv3 * abcDiv3;

+        if (q > 0.0) {

+            q = 0.0;

+        }

+

+        // Compute the eigenvalues by solving for the roots of the polynomial.

+        double magnitude = Math.sqrt(-abcDiv3);

+        double angle = Math.atan2(Math.sqrt(-q), mbDiv2) * ONE_THIRD_DOUBLE;

+        double cos = Math.cos(angle);

+        double sin = Math.sin(angle);

+        double root0 = char2Div3 + 2.0 * magnitude * cos;

+        double root1 = char2Div3 - magnitude

+                * (cos + ROOT_THREE_DOUBLE * sin);

+        double root2 = char2Div3 - magnitude

+                * (cos - ROOT_THREE_DOUBLE * sin);

+

+        // Sort in increasing order.

+        if (root1 >= root0) {

+            rootsStore[0] = root0;

+            rootsStore[1] = root1;

+        } else {

+            rootsStore[0] = root1;

+            rootsStore[1] = root0;

+        }

+

+        if (root2 >= rootsStore[1]) {

+            rootsStore[2] = root2;

+        } else {

+            rootsStore[2] = rootsStore[1];

+            if (root2 >= rootsStore[0]) {

+                rootsStore[1] = root2;

+            } else {

+                rootsStore[1] = rootsStore[0];

+                rootsStore[0] = root2;

+            }

+        }

+    }

+

+    public static void main(String[] args) {

+        Matrix3f mat = new Matrix3f(2, 1, 1, 1, 2, 1, 1, 1, 2);

+        Eigen3f eigenSystem = new Eigen3f(mat);

+

+        logger.info("eigenvalues = ");

+        for (int i = 0; i < 3; i++)

+            logger.log(Level.INFO, "{0} ", eigenSystem.getEigenValue(i));

+

+        logger.info("eigenvectors = ");

+        for (int i = 0; i < 3; i++) {

+            Vector3f vector = eigenSystem.getEigenVector(i);

+            logger.info(vector.toString());

+            mat.setColumn(i, vector);

+        }

+        logger.info(mat.toString());

+

+        // eigenvalues =

+        // 1.000000 1.000000 4.000000

+        // eigenvectors =

+        // 0.411953 0.704955 0.577350

+        // 0.404533 -0.709239 0.577350

+        // -0.816485 0.004284 0.577350

+    }

+

+    public float getEigenValue(int i) {

+        return eigenValues[i];

+    }

+

+    public Vector3f getEigenVector(int i) {

+        return eigenVectors[i];

+    }

+

+    public float[] getEigenValues() {

+        return eigenValues;

+    }

+

+    public Vector3f[] getEigenVectors() {

+        return eigenVectors;

+    }

+}

diff --git a/engine/src/core/com/jme3/math/FastMath.java b/engine/src/core/com/jme3/math/FastMath.java
new file mode 100644
index 0000000..6043d57
--- /dev/null
+++ b/engine/src/core/com/jme3/math/FastMath.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import java.util.Random;
+
+/**
+ * <code>FastMath</code> provides 'fast' math approximations and float equivalents of Math
+ * functions.  These are all used as static values and functions.
+ *
+ * @author Various
+ * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $
+ */
+final public class FastMath {
+
+    private FastMath() {
+    }
+    /** A "close to zero" double epsilon value for use*/
+    public static final double DBL_EPSILON = 2.220446049250313E-16d;
+    /** A "close to zero" float epsilon value for use*/
+    public static final float FLT_EPSILON = 1.1920928955078125E-7f;
+    /** A "close to zero" float epsilon value for use*/
+    public static final float ZERO_TOLERANCE = 0.0001f;
+    public static final float ONE_THIRD = 1f / 3f;
+    /** The value PI as a float. (180 degrees) */
+    public static final float PI = (float) Math.PI;
+    /** The value 2PI as a float. (360 degrees) */
+    public static final float TWO_PI = 2.0f * PI;
+    /** The value PI/2 as a float. (90 degrees) */
+    public static final float HALF_PI = 0.5f * PI;
+    /** The value PI/4 as a float. (45 degrees) */
+    public static final float QUARTER_PI = 0.25f * PI;
+    /** The value 1/PI as a float. */
+    public static final float INV_PI = 1.0f / PI;
+    /** The value 1/(2PI) as a float. */
+    public static final float INV_TWO_PI = 1.0f / TWO_PI;
+    /** A value to multiply a degree value by, to convert it to radians. */
+    public static final float DEG_TO_RAD = PI / 180.0f;
+    /** A value to multiply a radian value by, to convert it to degrees. */
+    public static final float RAD_TO_DEG = 180.0f / PI;
+    /** A precreated random object for random numbers. */
+    public static final Random rand = new Random(System.currentTimeMillis());
+
+    /**
+     * Returns true if the number is a power of 2 (2,4,8,16...)
+     * 
+     * A good implementation found on the Java boards. note: a number is a power
+     * of two if and only if it is the smallest number with that number of
+     * significant bits. Therefore, if you subtract 1, you know that the new
+     * number will have fewer bits, so ANDing the original number with anything
+     * less than it will give 0.
+     * 
+     * @param number
+     *            The number to test.
+     * @return True if it is a power of two.
+     */
+    public static boolean isPowerOfTwo(int number) {
+        return (number > 0) && (number & (number - 1)) == 0;
+    }
+
+    public static int nearestPowerOfTwo(int number) {
+        return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2)));
+    }
+
+    /**
+     * Linear interpolation from startValue to endValue by the given percent.
+     * Basically: ((1 - percent) * startValue) + (percent * endValue)
+     * 
+     * @param scale
+     *            scale value to use. if 1, use endValue, if 0, use startValue.
+     * @param startValue
+     *            Begining value. 0% of f
+     * @param endValue
+     *            ending value. 100% of f
+     * @return The interpolated value between startValue and endValue.
+     */
+    public static float interpolateLinear(float scale, float startValue, float endValue) {
+        if (startValue == endValue) {
+            return startValue;
+        }
+        if (scale <= 0f) {
+            return startValue;
+        }
+        if (scale >= 1f) {
+            return endValue;
+        }
+        return ((1f - scale) * startValue) + (scale * endValue);
+    }
+
+    /**
+     * Linear interpolation from startValue to endValue by the given percent.
+     * Basically: ((1 - percent) * startValue) + (percent * endValue)
+     *
+     * @param scale
+     *            scale value to use. if 1, use endValue, if 0, use startValue.
+     * @param startValue
+     *            Begining value. 0% of f
+     * @param endValue
+     *            ending value. 100% of f
+     * @param store a vector3f to store the result
+     * @return The interpolated value between startValue and endValue.
+     */
+    public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        store.x = interpolateLinear(scale, startValue.x, endValue.x);
+        store.y = interpolateLinear(scale, startValue.y, endValue.y);
+        store.z = interpolateLinear(scale, startValue.z, endValue.z);
+        return store;
+    }
+
+    /**
+     * Linear interpolation from startValue to endValue by the given percent.
+     * Basically: ((1 - percent) * startValue) + (percent * endValue)
+     *
+     * @param scale
+     *            scale value to use. if 1, use endValue, if 0, use startValue.
+     * @param startValue
+     *            Begining value. 0% of f
+     * @param endValue
+     *            ending value. 100% of f
+     * @return The interpolated value between startValue and endValue.
+     */
+    public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue) {
+        return interpolateLinear(scale, startValue, endValue, null);
+    }
+
+    /**
+     * Linear extrapolation from startValue to endValue by the given scale.
+     * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+     * if the scale is over 1 the value is linearly extrapolated.
+     * Note that the end value is the value for a scale of 1.
+     * @param scale the scale for extrapolation
+     * @param startValue the starting value (scale = 0)
+     * @param endValue the end value (scale = 1)
+     * @return an extrapolation for the given parameters
+     */
+    public static float extrapolateLinear(float scale, float startValue, float endValue) {
+//        if (scale <= 0f) {
+//            return startValue;
+//        }
+        return ((1f - scale) * startValue) + (scale * endValue);
+    }
+
+    /**
+     * Linear extrapolation from startValue to endValue by the given scale.
+     * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+     * if the scale is over 1 the value is linearly extrapolated.
+     * Note that the end value is the value for a scale of 1. 
+     * @param scale the scale for extrapolation
+     * @param startValue the starting value (scale = 0)
+     * @param endValue the end value (scale = 1)
+     * @param store an initialized vector to store the return value
+     * @return an extrapolation for the given parameters
+     */
+    public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+//        if (scale <= 1f) {
+//            return interpolateLinear(scale, startValue, endValue, store);
+//        }
+        store.x = extrapolateLinear(scale, startValue.x, endValue.x);
+        store.y = extrapolateLinear(scale, startValue.y, endValue.y);
+        store.z = extrapolateLinear(scale, startValue.z, endValue.z);
+        return store;
+    }
+
+    /**
+     * Linear extrapolation from startValue to endValue by the given scale.
+     * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+     * if the scale is over 1 the value is linearly extrapolated.
+     * Note that the end value is the value for a scale of 1.
+     * @param scale the scale for extrapolation
+     * @param startValue the starting value (scale = 0)
+     * @param endValue the end value (scale = 1)
+     * @return an extrapolation for the given parameters
+     */
+    public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue) {
+        return extrapolateLinear(scale, startValue, endValue, null);
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+     * here is the interpolation matrix
+     * m = [ 0.0  1.0  0.0   0.0 ]
+     *     [-T    0.0  T     0.0 ]
+     *     [ 2T   T-3  3-2T  -T  ]
+     *     [-T    2-T  T-2   T   ]
+     * where T is the curve tension
+     * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+     * @param u value from 0 to 1
+     * @param T The tension of the curve
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @return catmull-Rom interpolation
+     */
+    public static float interpolateCatmullRom(float u, float T, float p0, float p1, float p2, float p3) {
+        float c1, c2, c3, c4;
+        c1 = p1;
+        c2 = -1.0f * T * p0 + T * p2;
+        c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3;
+        c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3;
+
+        return (float) (((c4 * u + c3) * u + c2) * u + c1);
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+     * here is the interpolation matrix
+     * m = [ 0.0  1.0  0.0   0.0 ]
+     *     [-T    0.0  T     0.0 ]
+     *     [ 2T   T-3  3-2T  -T  ]
+     *     [-T    2-T  T-2   T   ]
+     * where T is the tension of the curve
+     * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+     * @param u value from 0 to 1
+     * @param T The tension of the curve
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @param store a Vector3f to store the result
+     * @return catmull-Rom interpolation
+     */
+    public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x);
+        store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y);
+        store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z);
+        return store;
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+     * here is the interpolation matrix
+     * m = [ 0.0  1.0  0.0   0.0 ]
+     *     [-T    0.0  T     0.0 ]
+     *     [ 2T   T-3  3-2T  -T  ]
+     *     [-T    2-T  T-2   T   ]
+     * where T is the tension of the curve
+     * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+     * @param u value from 0 to 1
+     * @param T The tension of the curve
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @return catmull-Rom interpolation
+     */
+    public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+        return interpolateCatmullRom(u, T, p0, p1, p2, p3, null);
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Bezier equation.
+     * here is the interpolation matrix
+     * m = [ -1.0   3.0  -3.0    1.0 ]
+     *     [  3.0  -6.0   3.0    0.0 ]
+     *     [ -3.0   3.0   0.0    0.0 ]
+     *     [  1.0   0.0   0.0    0.0 ]
+     * where T is the curve tension
+     * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+     * @param u value from 0 to 1
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @return Bezier interpolation
+     */
+    public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) {
+        float oneMinusU = 1.0f - u;
+        float oneMinusU2 = oneMinusU * oneMinusU;
+        float u2 = u * u;
+        return p0 * oneMinusU2 * oneMinusU
+                + 3.0f * p1 * u * oneMinusU2
+                + 3.0f * p2 * u2 * oneMinusU
+                + p3 * u2 * u;
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Bezier equation.
+     * here is the interpolation matrix
+     * m = [ -1.0   3.0  -3.0    1.0 ]
+     *     [  3.0  -6.0   3.0    0.0 ]
+     *     [ -3.0   3.0   0.0    0.0 ]
+     *     [  1.0   0.0   0.0    0.0 ]
+     * where T is the tension of the curve
+     * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+     * @param u value from 0 to 1
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @param store a Vector3f to store the result
+     * @return Bezier interpolation
+     */
+    public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x);
+        store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y);
+        store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z);
+        return store;
+    }
+
+    /**Interpolate a spline between at least 4 control points following the Bezier equation.
+     * here is the interpolation matrix
+     * m = [ -1.0   3.0  -3.0    1.0 ]
+     *     [  3.0  -6.0   3.0    0.0 ]
+     *     [ -3.0   3.0   0.0    0.0 ]
+     *     [  1.0   0.0   0.0    0.0 ]
+     * where T is the tension of the curve
+     * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+     * @param u value from 0 to 1
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @return Bezier interpolation
+     */
+    public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+        return interpolateBezier(u, p0, p1, p2, p3, null);
+    }
+
+    /**
+     * Compute the lenght on a catmull rom spline between control point 1 and 2
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @param startRange the starting range on the segment (use 0)
+     * @param endRange the end range on the segment (use 1)
+     * @param curveTension the curve tension
+     * @return the length of the segment
+     */
+    public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, float startRange, float endRange, float curveTension) {
+
+        float epsilon = 0.001f;
+        float middleValue = (startRange + endRange) * 0.5f;
+        Vector3f start = p1.clone();
+        if (startRange != 0) {
+            FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3, start);
+        }
+        Vector3f end = p2.clone();
+        if (endRange != 1) {
+            FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3, end);
+        }
+        Vector3f middle = FastMath.interpolateCatmullRom(middleValue, curveTension, p0, p1, p2, p3);
+        float l = end.subtract(start).length();
+        float l1 = middle.subtract(start).length();
+        float l2 = end.subtract(middle).length();
+        float len = l1 + l2;
+        if (l + epsilon < len) {
+            l1 = getCatmullRomP1toP2Length(p0, p1, p2, p3, startRange, middleValue, curveTension);
+            l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange, curveTension);
+        }
+        l = l1 + l2;
+        return l;
+    }
+
+    /**
+     * Compute the lenght on a bezier spline between control point 1 and 2
+     * @param p0 control point 0
+     * @param p1 control point 1
+     * @param p2 control point 2
+     * @param p3 control point 3
+     * @return the length of the segment
+     */
+    public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+        float delta = 0.02f, t = 0.0f, result = 0.0f;
+        Vector3f v1 = p0.clone(), v2 = new Vector3f();
+        while (t <= 1.0f) {
+            FastMath.interpolateBezier(t, p0, p1, p2, p3, v2);
+            result += v1.subtractLocal(v2).length();
+            v1.set(v2);
+            t += delta;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the arc cosine of an angle given in radians.<br>
+     * Special cases:
+     * <ul><li>If fValue is smaller than -1, then the result is PI.
+     * <li>If the argument is greater than 1, then the result is 0.</ul>
+     * @param fValue The angle, in radians.
+     * @return fValue's acos
+     * @see java.lang.Math#acos(double)
+     */
+    public static float acos(float fValue) {
+        if (-1.0f < fValue) {
+            if (fValue < 1.0f) {
+                return (float) Math.acos(fValue);
+            }
+
+            return 0.0f;
+        }
+
+        return PI;
+    }
+
+    /**
+     * Returns the arc sine of an angle given in radians.<br>
+     * Special cases:
+     * <ul><li>If fValue is smaller than -1, then the result is -HALF_PI.
+     * <li>If the argument is greater than 1, then the result is HALF_PI.</ul>
+     * @param fValue The angle, in radians.
+     * @return fValue's asin
+     * @see java.lang.Math#asin(double)
+     */
+    public static float asin(float fValue) {
+        if (-1.0f < fValue) {
+            if (fValue < 1.0f) {
+                return (float) Math.asin(fValue);
+            }
+
+            return HALF_PI;
+        }
+
+        return -HALF_PI;
+    }
+
+    /**
+     * Returns the arc tangent of an angle given in radians.<br>
+     * @param fValue The angle, in radians.
+     * @return fValue's atan
+     * @see java.lang.Math#atan(double)
+     */
+    public static float atan(float fValue) {
+        return (float) Math.atan(fValue);
+    }
+
+    /**
+     * A direct call to Math.atan2.
+     * @param fY
+     * @param fX
+     * @return Math.atan2(fY,fX)
+     * @see java.lang.Math#atan2(double, double)
+     */
+    public static float atan2(float fY, float fX) {
+        return (float) Math.atan2(fY, fX);
+    }
+
+    /**
+     * Rounds a fValue up.  A call to Math.ceil
+     * @param fValue The value.
+     * @return The fValue rounded up
+     * @see java.lang.Math#ceil(double)
+     */
+    public static float ceil(float fValue) {
+        return (float) Math.ceil(fValue);
+    }
+
+    /**
+     * Fast Trig functions for x86. This forces the trig functiosn to stay
+     * within the safe area on the x86 processor (-45 degrees to +45 degrees)
+     * The results may be very slightly off from what the Math and StrictMath
+     * trig functions give due to rounding in the angle reduction but it will be
+     * very very close. 
+     * 
+     * note: code from wiki posting on java.net by jeffpk
+     */
+    public static float reduceSinAngle(float radians) {
+        radians %= TWO_PI; // put us in -2PI to +2PI space
+        if (Math.abs(radians) > PI) { // put us in -PI to +PI space
+            radians = radians - (TWO_PI);
+        }
+        if (Math.abs(radians) > HALF_PI) {// put us in -PI/2 to +PI/2 space
+            radians = PI - radians;
+        }
+
+        return radians;
+    }
+
+    /**
+     * Returns sine of a value. 
+     * 
+     * note: code from wiki posting on java.net by jeffpk
+     * 
+     * @param fValue
+     *            The value to sine, in radians.
+     * @return The sine of fValue.
+     * @see java.lang.Math#sin(double)
+     */
+    public static float sin2(float fValue) {
+        fValue = reduceSinAngle(fValue); // limits angle to between -PI/2 and +PI/2
+        if (Math.abs(fValue) <= Math.PI / 4) {
+            return (float) Math.sin(fValue);
+        }
+
+        return (float) Math.cos(Math.PI / 2 - fValue);
+    }
+
+    /**
+     * Returns cos of a value.
+     * 
+     * @param fValue
+     *            The value to cosine, in radians.
+     * @return The cosine of fValue.
+     * @see java.lang.Math#cos(double)
+     */
+    public static float cos2(float fValue) {
+        return sin2(fValue + HALF_PI);
+    }
+
+    public static float cos(float v) {
+        return (float) Math.cos(v);
+    }
+
+    public static float sin(float v) {
+        return (float) Math.sin(v);
+    }
+
+    /**
+     * Returns E^fValue
+     * @param fValue Value to raise to a power.
+     * @return The value E^fValue
+     * @see java.lang.Math#exp(double)
+     */
+    public static float exp(float fValue) {
+        return (float) Math.exp(fValue);
+    }
+
+    /**
+     * Returns Absolute value of a float.
+     * @param fValue The value to abs.
+     * @return The abs of the value.
+     * @see java.lang.Math#abs(float)
+     */
+    public static float abs(float fValue) {
+        if (fValue < 0) {
+            return -fValue;
+        }
+        return fValue;
+    }
+
+    /**
+     * Returns a number rounded down.
+     * @param fValue The value to round
+     * @return The given number rounded down
+     * @see java.lang.Math#floor(double)
+     */
+    public static float floor(float fValue) {
+        return (float) Math.floor(fValue);
+    }
+
+    /**
+     * Returns 1/sqrt(fValue)
+     * @param fValue The value to process.
+     * @return 1/sqrt(fValue)
+     * @see java.lang.Math#sqrt(double)
+     */
+    public static float invSqrt(float fValue) {
+        return (float) (1.0f / Math.sqrt(fValue));
+    }
+
+    public static float fastInvSqrt(float x) {
+        float xhalf = 0.5f * x;
+        int i = Float.floatToIntBits(x); // get bits for floating value
+        i = 0x5f375a86 - (i >> 1); // gives initial guess y0
+        x = Float.intBitsToFloat(i); // convert bits back to float
+        x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
+        return x;
+    }
+
+    /**
+     * Returns the log base E of a value.
+     * @param fValue The value to log.
+     * @return The log of fValue base E
+     * @see java.lang.Math#log(double)
+     */
+    public static float log(float fValue) {
+        return (float) Math.log(fValue);
+    }
+
+    /**
+     * Returns the logarithm of value with given base, calculated as log(value)/log(base), 
+     * so that pow(base, return)==value (contributed by vear)
+     * @param value The value to log.
+     * @param base Base of logarithm.
+     * @return The logarithm of value with given base
+     */
+    public static float log(float value, float base) {
+        return (float) (Math.log(value) / Math.log(base));
+    }
+
+    /**
+     * Returns a number raised to an exponent power.  fBase^fExponent
+     * @param fBase The base value (IE 2)
+     * @param fExponent The exponent value (IE 3)
+     * @return base raised to exponent (IE 8)
+     * @see java.lang.Math#pow(double, double)
+     */
+    public static float pow(float fBase, float fExponent) {
+        return (float) Math.pow(fBase, fExponent);
+    }
+
+    /**
+     * Returns the value squared.  fValue ^ 2
+     * @param fValue The vaule to square.
+     * @return The square of the given value.
+     */
+    public static float sqr(float fValue) {
+        return fValue * fValue;
+    }
+
+    /**
+     * Returns the square root of a given value.
+     * @param fValue The value to sqrt.
+     * @return The square root of the given value.
+     * @see java.lang.Math#sqrt(double)
+     */
+    public static float sqrt(float fValue) {
+        return (float) Math.sqrt(fValue);
+    }
+
+    /**
+     * Returns the tangent of a value.  If USE_FAST_TRIG is enabled, an approximate value
+     * is returned.  Otherwise, a direct value is used.
+     * @param fValue The value to tangent, in radians.
+     * @return The tangent of fValue.
+     * @see java.lang.Math#tan(double)
+     */
+    public static float tan(float fValue) {
+        return (float) Math.tan(fValue);
+    }
+
+    /**
+     * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
+     * @param iValue The integer to examine.
+     * @return The integer's sign.
+     */
+    public static int sign(int iValue) {
+        if (iValue > 0) {
+            return 1;
+        }
+        if (iValue < 0) {
+            return -1;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
+     * @param fValue The float to examine.
+     * @return The float's sign.
+     */
+    public static float sign(float fValue) {
+        return Math.signum(fValue);
+    }
+
+    /**
+     * Given 3 points in a 2d plane, this function computes if the points going from A-B-C
+     * are moving counter clock wise.
+     * @param p0 Point 0.
+     * @param p1 Point 1.
+     * @param p2 Point 2.
+     * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 and p1.
+     */
+    public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
+        float dx1, dx2, dy1, dy2;
+        dx1 = p1.x - p0.x;
+        dy1 = p1.y - p0.y;
+        dx2 = p2.x - p0.x;
+        dy2 = p2.y - p0.y;
+        if (dx1 * dy2 > dy1 * dx2) {
+            return 1;
+        }
+        if (dx1 * dy2 < dy1 * dx2) {
+            return -1;
+        }
+        if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) {
+            return -1;
+        }
+        if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Test if a point is inside a triangle.  1 if the point is on the ccw side,
+     * -1 if the point is on the cw side, and 0 if it is on neither.
+     * @param t0 First point of the triangle.
+     * @param t1 Second point of the triangle.
+     * @param t2 Third point of the triangle.
+     * @param p The point to test.
+     * @return Value 1 or -1 if inside triangle, 0 otherwise.
+     */
+    public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) {
+        int val1 = counterClockwise(t0, t1, p);
+        if (val1 == 0) {
+            return 1;
+        }
+        int val2 = counterClockwise(t1, t2, p);
+        if (val2 == 0) {
+            return 1;
+        }
+        if (val2 != val1) {
+            return 0;
+        }
+        int val3 = counterClockwise(t2, t0, p);
+        if (val3 == 0) {
+            return 1;
+        }
+        if (val3 != val1) {
+            return 0;
+        }
+        return val3;
+    }
+
+    /**
+     * A method that computes normal for a triangle defined by three vertices.
+     * @param v1 first vertex
+     * @param v2 second vertex
+     * @param v3 third vertex
+     * @return a normal for the face
+     */
+    public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) {
+        Vector3f a1 = v1.subtract(v2);
+        Vector3f a2 = v3.subtract(v2);
+        return a2.crossLocal(a1).normalizeLocal();
+    }
+
+    /**
+     * Returns the determinant of a 4x4 matrix.
+     */
+    public static float determinant(double m00, double m01, double m02,
+            double m03, double m10, double m11, double m12, double m13,
+            double m20, double m21, double m22, double m23, double m30,
+            double m31, double m32, double m33) {
+
+        double det01 = m20 * m31 - m21 * m30;
+        double det02 = m20 * m32 - m22 * m30;
+        double det03 = m20 * m33 - m23 * m30;
+        double det12 = m21 * m32 - m22 * m31;
+        double det13 = m21 * m33 - m23 * m31;
+        double det23 = m22 * m33 - m23 * m32;
+        return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) - m01
+                * (m10 * det23 - m12 * det03 + m13 * det02) + m02
+                * (m10 * det13 - m11 * det03 + m13 * det01) - m03
+                * (m10 * det12 - m11 * det02 + m12 * det01));
+    }
+
+    /**
+     * Returns a random float between 0 and 1.
+     * 
+     * @return A random float between <tt>0.0f</tt> (inclusive) to
+     *         <tt>1.0f</tt> (exclusive).
+     */
+    public static float nextRandomFloat() {
+        return rand.nextFloat();
+    }
+
+    /**
+     * Returns a random float between min and max.
+     * 
+     * @return A random int between <tt>min</tt> (inclusive) to
+     *         <tt>max</tt> (inclusive).
+     */
+    public static int nextRandomInt(int min, int max) {
+        return (int) (nextRandomFloat() * (max - min + 1)) + min;
+    }
+
+    public static int nextRandomInt() {
+        return rand.nextInt();
+    }
+
+    /**
+     * Converts a point from Spherical coordinates to Cartesian (using positive
+     * Y as up) and stores the results in the store var.
+     */
+    public static Vector3f sphericalToCartesian(Vector3f sphereCoords,
+            Vector3f store) {
+        store.y = sphereCoords.x * FastMath.sin(sphereCoords.z);
+        float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+        store.x = a * FastMath.cos(sphereCoords.y);
+        store.z = a * FastMath.sin(sphereCoords.y);
+
+        return store;
+    }
+
+    /**
+     * Converts a point from Cartesian coordinates (using positive Y as up) to
+     * Spherical and stores the results in the store var. (Radius, Azimuth,
+     * Polar)
+     */
+    public static Vector3f cartesianToSpherical(Vector3f cartCoords,
+            Vector3f store) {
+        float x = cartCoords.x;
+        if (x == 0) {
+            x = FastMath.FLT_EPSILON;
+        }
+        store.x = FastMath.sqrt((x * x)
+                + (cartCoords.y * cartCoords.y)
+                + (cartCoords.z * cartCoords.z));
+        store.y = FastMath.atan(cartCoords.z / x);
+        if (x < 0) {
+            store.y += FastMath.PI;
+        }
+        store.z = FastMath.asin(cartCoords.y / store.x);
+        return store;
+    }
+
+    /**
+     * Converts a point from Spherical coordinates to Cartesian (using positive
+     * Z as up) and stores the results in the store var.
+     */
+    public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords,
+            Vector3f store) {
+        store.z = sphereCoords.x * FastMath.sin(sphereCoords.z);
+        float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+        store.x = a * FastMath.cos(sphereCoords.y);
+        store.y = a * FastMath.sin(sphereCoords.y);
+
+        return store;
+    }
+
+    /**
+     * Converts a point from Cartesian coordinates (using positive Z as up) to
+     * Spherical and stores the results in the store var. (Radius, Azimuth,
+     * Polar)
+     */
+    public static Vector3f cartesianZToSpherical(Vector3f cartCoords,
+            Vector3f store) {
+        float x = cartCoords.x;
+        if (x == 0) {
+            x = FastMath.FLT_EPSILON;
+        }
+        store.x = FastMath.sqrt((x * x)
+                + (cartCoords.y * cartCoords.y)
+                + (cartCoords.z * cartCoords.z));
+        store.z = FastMath.atan(cartCoords.z / x);
+        if (x < 0) {
+            store.z += FastMath.PI;
+        }
+        store.y = FastMath.asin(cartCoords.y / store.x);
+        return store;
+    }
+
+    /**
+     * Takes an value and expresses it in terms of min to max.
+     * 
+     * @param val -
+     *            the angle to normalize (in radians)
+     * @return the normalized angle (also in radians)
+     */
+    public static float normalize(float val, float min, float max) {
+        if (Float.isInfinite(val) || Float.isNaN(val)) {
+            return 0f;
+        }
+        float range = max - min;
+        while (val > max) {
+            val -= range;
+        }
+        while (val < min) {
+            val += range;
+        }
+        return val;
+    }
+
+    /**
+     * @param x
+     *            the value whose sign is to be adjusted.
+     * @param y
+     *            the value whose sign is to be used.
+     * @return x with its sign changed to match the sign of y.
+     */
+    public static float copysign(float x, float y) {
+        if (y >= 0 && x <= -0) {
+            return -x;
+        } else if (y < 0 && x >= 0) {
+            return -x;
+        } else {
+            return x;
+        }
+    }
+
+    /**
+     * Take a float input and clamp it between min and max.
+     * 
+     * @param input
+     * @param min
+     * @param max
+     * @return clamped input
+     */
+    public static float clamp(float input, float min, float max) {
+        return (input < min) ? min : (input > max) ? max : input;
+    }
+
+    /**
+     * Clamps the given float to be between 0 and 1.
+     *
+     * @param input
+     * @return input clamped between 0 and 1.
+     */
+    public static float saturate(float input) {
+        return clamp(input, 0f, 1f);
+    }
+
+    /**
+     * Converts a single precision (32 bit) floating point value
+     * into half precision (16 bit).
+     *
+     * <p>Source: <a href="http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf">
+     * http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf</a><br><strong>broken link</strong>
+     *
+     * @param half The half floating point value as a short.
+     * @return floating point value of the half.
+     */
+    public static float convertHalfToFloat(short half) {
+        switch ((int) half) {
+            case 0x0000:
+                return 0f;
+            case 0x8000:
+                return -0f;
+            case 0x7c00:
+                return Float.POSITIVE_INFINITY;
+            case 0xfc00:
+                return Float.NEGATIVE_INFINITY;
+            // TODO: Support for NaN?
+            default:
+                return Float.intBitsToFloat(((half & 0x8000) << 16)
+                        | (((half & 0x7c00) + 0x1C000) << 13)
+                        | ((half & 0x03FF) << 13));
+        }
+    }
+
+    public static short convertFloatToHalf(float flt) {
+        if (Float.isNaN(flt)) {
+            throw new UnsupportedOperationException("NaN to half conversion not supported!");
+        } else if (flt == Float.POSITIVE_INFINITY) {
+            return (short) 0x7c00;
+        } else if (flt == Float.NEGATIVE_INFINITY) {
+            return (short) 0xfc00;
+        } else if (flt == 0f) {
+            return (short) 0x0000;
+        } else if (flt == -0f) {
+            return (short) 0x8000;
+        } else if (flt > 65504f) {
+            // max value supported by half float
+            return 0x7bff;
+        } else if (flt < -65504f) {
+            return (short) (0x7bff | 0x8000);
+        } else if (flt > 0f && flt < 5.96046E-8f) {
+            return 0x0001;
+        } else if (flt < 0f && flt > -5.96046E-8f) {
+            return (short) 0x8001;
+        }
+
+        int f = Float.floatToIntBits(flt);
+        return (short) (((f >> 16) & 0x8000)
+                | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00)
+                | ((f >> 13) & 0x03ff));
+    }
+}
diff --git a/engine/src/core/com/jme3/math/Line.java b/engine/src/core/com/jme3/math/Line.java
new file mode 100644
index 0000000..a0fa79b
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Line.java
@@ -0,0 +1,239 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+

+/**

+ * <code>Line</code> defines a line. Where a line is defined as infinite along

+ * two points. The two points of the line are defined as the origin and direction.

+ * 

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public class Line implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private Vector3f origin;

+    private Vector3f direction;

+

+    /**

+     * Constructor instantiates a new <code>Line</code> object. The origin and

+     * direction are set to defaults (0,0,0).

+     *

+     */

+    public Line() {

+        origin = new Vector3f();

+        direction = new Vector3f();

+    }

+

+    /**

+     * Constructor instantiates a new <code>Line</code> object. The origin

+     * and direction are set via the parameters.

+     * @param origin the origin of the line.

+     * @param direction the direction of the line.

+     */

+    public Line(Vector3f origin, Vector3f direction) {

+        this.origin = origin;

+        this.direction = direction;

+    }

+

+    /**

+     *

+     * <code>getOrigin</code> returns the origin of the line.

+     * @return the origin of the line.

+     */

+    public Vector3f getOrigin() {

+        return origin;

+    }

+

+    /**

+     *

+     * <code>setOrigin</code> sets the origin of the line.

+     * @param origin the origin of the line.

+     */

+    public void setOrigin(Vector3f origin) {

+        this.origin = origin;

+    }

+

+    /**

+     *

+     * <code>getDirection</code> returns the direction of the line.

+     * @return the direction of the line.

+     */

+    public Vector3f getDirection() {

+        return direction;

+    }

+

+    /**

+     *

+     * <code>setDirection</code> sets the direction of the line.

+     * @param direction the direction of the line.

+     */

+    public void setDirection(Vector3f direction) {

+        this.direction = direction;

+    }

+

+    public float distanceSquared(Vector3f point) {

+        TempVars vars = TempVars.get();

+

+        Vector3f compVec1 = vars.vect1;

+        Vector3f compVec2 = vars.vect2;

+

+        point.subtract(origin, compVec1);

+        float lineParameter = direction.dot(compVec1);

+        origin.add(direction.mult(lineParameter, compVec2), compVec2);

+        compVec2.subtract(point, compVec1);

+        float len = compVec1.lengthSquared();

+        vars.release();

+        return len;

+    }

+

+    public float distance(Vector3f point) {

+        return FastMath.sqrt(distanceSquared(point));

+    }

+

+    public void orthogonalLineFit(FloatBuffer points) {

+        if (points == null) {

+            return;

+        }

+

+        TempVars vars = TempVars.get();

+

+        Vector3f compVec1 = vars.vect1;

+        Vector3f compVec2 = vars.vect2;

+        Matrix3f compMat1 = vars.tempMat3;

+        Eigen3f compEigen1 = vars.eigen;

+

+        points.rewind();

+

+        // compute average of points

+        int length = points.remaining() / 3;

+

+        BufferUtils.populateFromBuffer(origin, points, 0);

+        for (int i = 1; i < length; i++) {

+            BufferUtils.populateFromBuffer(compVec1, points, i);

+            origin.addLocal(compVec1);

+        }

+

+        origin.multLocal(1f / (float) length);

+

+        // compute sums of products

+        float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f;

+        float sumYY = 0.0f, sumYZ = 0.0f, sumZZ = 0.0f;

+

+        points.rewind();

+        for (int i = 0; i < length; i++) {

+            BufferUtils.populateFromBuffer(compVec1, points, i);

+            compVec1.subtract(origin, compVec2);

+            sumXX += compVec2.x * compVec2.x;

+            sumXY += compVec2.x * compVec2.y;

+            sumXZ += compVec2.x * compVec2.z;

+            sumYY += compVec2.y * compVec2.y;

+            sumYZ += compVec2.y * compVec2.z;

+            sumZZ += compVec2.z * compVec2.z;

+        }

+

+        //find the smallest eigen vector for the direction vector

+        compMat1.m00 = sumYY + sumZZ;

+        compMat1.m01 = -sumXY;

+        compMat1.m02 = -sumXZ;

+        compMat1.m10 = -sumXY;

+        compMat1.m11 = sumXX + sumZZ;

+        compMat1.m12 = -sumYZ;

+        compMat1.m20 = -sumXZ;

+        compMat1.m21 = -sumYZ;

+        compMat1.m22 = sumXX + sumYY;

+

+        compEigen1.calculateEigen(compMat1);

+        direction = compEigen1.getEigenVector(0);

+

+        vars.release();

+    }

+

+    /**

+     *

+     * <code>random</code> determines a random point along the line.

+     * @return a random point on the line.

+     */

+    public Vector3f random() {

+        return random(null);

+    }

+

+    /**

+     * <code>random</code> determines a random point along the line.

+     * 

+     * @param result Vector to store result in

+     * @return a random point on the line.

+     */

+    public Vector3f random(Vector3f result) {

+        if (result == null) {

+            result = new Vector3f();

+        }

+        float rand = (float) Math.random();

+

+        result.x = (origin.x * (1 - rand)) + (direction.x * rand);

+        result.y = (origin.y * (1 - rand)) + (direction.y * rand);

+        result.z = (origin.z * (1 - rand)) + (direction.z * rand);

+

+        return result;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(origin, "origin", Vector3f.ZERO);

+        capsule.write(direction, "direction", Vector3f.ZERO);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());

+        direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());

+    }

+

+    @Override

+    public Line clone() {

+        try {

+            Line line = (Line) super.clone();

+            line.direction = direction.clone();

+            line.origin = origin.clone();

+            return line;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/LineSegment.java b/engine/src/core/com/jme3/math/LineSegment.java
new file mode 100644
index 0000000..c86619c
--- /dev/null
+++ b/engine/src/core/com/jme3/math/LineSegment.java
@@ -0,0 +1,631 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+

+/**

+ * <p>LineSegment represents a segment in the space. This is a portion of a Line

+ * that has a limited start and end points.</p>

+ * <p>A LineSegment is defined by an origin, a direction and an extent (or length).

+ * Direction should be a normalized vector. It is not internally normalized.</p>

+ * <p>This class provides methods to calculate distances between LineSegments, Rays and Vectors.

+ * It is also possible to retrieve both end points of the segment {@link LineSegment#getPositiveEnd(Vector3f)}

+ * and {@link LineSegment#getNegativeEnd(Vector3f)}. There are also methods to check whether

+ * a point is within the segment bounds.</p>

+ *

+ * @see Ray

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public class LineSegment implements Cloneable, Savable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private Vector3f origin;

+    private Vector3f direction;

+    private float extent;

+

+    public LineSegment() {

+        origin = new Vector3f();

+        direction = new Vector3f();

+    }

+

+    public LineSegment(LineSegment ls) {

+        this.origin = new Vector3f(ls.getOrigin());

+        this.direction = new Vector3f(ls.getDirection());

+        this.extent = ls.getExtent();

+    }

+

+    /**

+     * <p>Creates a new LineSegment with the given origin, direction and extent.</p>

+     * <p>Note that the origin is not one of the ends of the LineSegment, but its center.</p>

+     */

+    public LineSegment(Vector3f origin, Vector3f direction, float extent) {

+        this.origin = origin;

+        this.direction = direction;

+        this.extent = extent;

+    }

+

+    /**

+     * <p>Creates a new LineSegment with a given origin and end. This constructor will calculate the

+     * center, the direction and the extent.</p>

+     */

+    public LineSegment(Vector3f start, Vector3f end) {

+        this.origin = new Vector3f(0.5f * (start.x + end.x), 0.5f * (start.y + end.y), 0.5f * (start.z + end.z));

+        this.direction = end.subtract(start);

+        this.extent = direction.length() * 0.5f;

+        direction.normalizeLocal();

+    }

+

+    public void set(LineSegment ls) {

+        this.origin = new Vector3f(ls.getOrigin());

+        this.direction = new Vector3f(ls.getDirection());

+        this.extent = ls.getExtent();

+    }

+

+    public float distance(Vector3f point) {

+        return FastMath.sqrt(distanceSquared(point));

+    }

+

+    public float distance(LineSegment ls) {

+        return FastMath.sqrt(distanceSquared(ls));

+    }

+

+    public float distance(Ray r) {

+        return FastMath.sqrt(distanceSquared(r));

+    }

+

+    public float distanceSquared(Vector3f point) {

+        TempVars vars = TempVars.get();

+        Vector3f compVec1 = vars.vect1;

+

+        point.subtract(origin, compVec1);

+        float segmentParameter = direction.dot(compVec1);

+

+        if (-extent < segmentParameter) {

+            if (segmentParameter < extent) {

+                origin.add(direction.mult(segmentParameter, compVec1),

+                        compVec1);

+            } else {

+                origin.add(direction.mult(extent, compVec1), compVec1);

+            }

+        } else {

+            origin.subtract(direction.mult(extent, compVec1), compVec1);

+        }

+

+        compVec1.subtractLocal(point);

+        float len = compVec1.lengthSquared();

+        vars.release();

+        return len;

+    }

+

+    public float distanceSquared(LineSegment test) {

+        TempVars vars = TempVars.get();

+        Vector3f compVec1 = vars.vect1;

+

+        origin.subtract(test.getOrigin(), compVec1);

+        float negativeDirectionDot = -(direction.dot(test.getDirection()));

+        float diffThisDot = compVec1.dot(direction);

+        float diffTestDot = -(compVec1.dot(test.getDirection()));

+        float lengthOfDiff = compVec1.lengthSquared();

+        vars.release();

+        float determinant = FastMath.abs(1.0f - negativeDirectionDot

+                * negativeDirectionDot);

+        float s0, s1, squareDistance, extentDeterminant0, extentDeterminant1, tempS0, tempS1;

+

+        if (determinant >= FastMath.FLT_EPSILON) {

+            // segments are not parallel

+            s0 = negativeDirectionDot * diffTestDot - diffThisDot;

+            s1 = negativeDirectionDot * diffThisDot - diffTestDot;

+            extentDeterminant0 = extent * determinant;

+            extentDeterminant1 = test.getExtent() * determinant;

+

+            if (s0 >= -extentDeterminant0) {

+                if (s0 <= extentDeterminant0) {

+                    if (s1 >= -extentDeterminant1) {

+                        if (s1 <= extentDeterminant1) // region 0 (interior)

+                        {

+                            // minimum at two interior points of 3D lines

+                            float inverseDeterminant = ((float) 1.0)

+                                    / determinant;

+                            s0 *= inverseDeterminant;

+                            s1 *= inverseDeterminant;

+                            squareDistance = s0

+                                    * (s0 + negativeDirectionDot * s1 + (2.0f) * diffThisDot)

+                                    + s1

+                                    * (negativeDirectionDot * s0 + s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else // region 3 (side)

+                        {

+                            s1 = test.getExtent();

+                            tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                            if (tempS0 < -extent) {

+                                s0 = -extent;

+                                squareDistance = s0 * (s0 - (2.0f) * tempS0)

+                                        + s1 * (s1 + (2.0f) * diffTestDot)

+                                        + lengthOfDiff;

+                            } else if (tempS0 <= extent) {

+                                s0 = tempS0;

+                                squareDistance = -s0 * s0 + s1

+                                        * (s1 + (2.0f) * diffTestDot)

+                                        + lengthOfDiff;

+                            } else {

+                                s0 = extent;

+                                squareDistance = s0 * (s0 - (2.0f) * tempS0)

+                                        + s1 * (s1 + (2.0f) * diffTestDot)

+                                        + lengthOfDiff;

+                            }

+                        }

+                    } else // region 7 (side)

+                    {

+                        s1 = -test.getExtent();

+                        tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                        if (tempS0 < -extent) {

+                            s0 = -extent;

+                            squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else if (tempS0 <= extent) {

+                            s0 = tempS0;

+                            squareDistance = -s0 * s0 + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else {

+                            s0 = extent;

+                            squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        }

+                    }

+                } else {

+                    if (s1 >= -extentDeterminant1) {

+                        if (s1 <= extentDeterminant1) // region 1 (side)

+                        {

+                            s0 = extent;

+                            tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                            if (tempS1 < -test.getExtent()) {

+                                s1 = -test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else if (tempS1 <= test.getExtent()) {

+                                s1 = tempS1;

+                                squareDistance = -s1 * s1 + s0

+                                        * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else {

+                                s1 = test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            }

+                        } else // region 2 (corner)

+                        {

+                            s1 = test.getExtent();

+                            tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                            if (tempS0 < -extent) {

+                                s0 = -extent;

+                                squareDistance = s0 * (s0 - (2.0f) * tempS0)

+                                        + s1 * (s1 + (2.0f) * diffTestDot)

+                                        + lengthOfDiff;

+                            } else if (tempS0 <= extent) {

+                                s0 = tempS0;

+                                squareDistance = -s0 * s0 + s1

+                                        * (s1 + (2.0f) * diffTestDot)

+                                        + lengthOfDiff;

+                            } else {

+                                s0 = extent;

+                                tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                                if (tempS1 < -test.getExtent()) {

+                                    s1 = -test.getExtent();

+                                    squareDistance = s1

+                                            * (s1 - (2.0f) * tempS1) + s0

+                                            * (s0 + (2.0f) * diffThisDot)

+                                            + lengthOfDiff;

+                                } else if (tempS1 <= test.getExtent()) {

+                                    s1 = tempS1;

+                                    squareDistance = -s1 * s1 + s0

+                                            * (s0 + (2.0f) * diffThisDot)

+                                            + lengthOfDiff;

+                                } else {

+                                    s1 = test.getExtent();

+                                    squareDistance = s1

+                                            * (s1 - (2.0f) * tempS1) + s0

+                                            * (s0 + (2.0f) * diffThisDot)

+                                            + lengthOfDiff;

+                                }

+                            }

+                        }

+                    } else // region 8 (corner)

+                    {

+                        s1 = -test.getExtent();

+                        tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                        if (tempS0 < -extent) {

+                            s0 = -extent;

+                            squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else if (tempS0 <= extent) {

+                            s0 = tempS0;

+                            squareDistance = -s0 * s0 + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else {

+                            s0 = extent;

+                            tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                            if (tempS1 > test.getExtent()) {

+                                s1 = test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else if (tempS1 >= -test.getExtent()) {

+                                s1 = tempS1;

+                                squareDistance = -s1 * s1 + s0

+                                        * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else {

+                                s1 = -test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            }

+                        }

+                    }

+                }

+            } else {

+                if (s1 >= -extentDeterminant1) {

+                    if (s1 <= extentDeterminant1) // region 5 (side)

+                    {

+                        s0 = -extent;

+                        tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                        if (tempS1 < -test.getExtent()) {

+                            s1 = -test.getExtent();

+                            squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        } else if (tempS1 <= test.getExtent()) {

+                            s1 = tempS1;

+                            squareDistance = -s1 * s1 + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        } else {

+                            s1 = test.getExtent();

+                            squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        }

+                    } else // region 4 (corner)

+                    {

+                        s1 = test.getExtent();

+                        tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                        if (tempS0 > extent) {

+                            s0 = extent;

+                            squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else if (tempS0 >= -extent) {

+                            s0 = tempS0;

+                            squareDistance = -s0 * s0 + s1

+                                    * (s1 + (2.0f) * diffTestDot)

+                                    + lengthOfDiff;

+                        } else {

+                            s0 = -extent;

+                            tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                            if (tempS1 < -test.getExtent()) {

+                                s1 = -test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else if (tempS1 <= test.getExtent()) {

+                                s1 = tempS1;

+                                squareDistance = -s1 * s1 + s0

+                                        * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            } else {

+                                s1 = test.getExtent();

+                                squareDistance = s1 * (s1 - (2.0f) * tempS1)

+                                        + s0 * (s0 + (2.0f) * diffThisDot)

+                                        + lengthOfDiff;

+                            }

+                        }

+                    }

+                } else // region 6 (corner)

+                {

+                    s1 = -test.getExtent();

+                    tempS0 = -(negativeDirectionDot * s1 + diffThisDot);

+                    if (tempS0 > extent) {

+                        s0 = extent;

+                        squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1

+                                * (s1 + (2.0f) * diffTestDot) + lengthOfDiff;

+                    } else if (tempS0 >= -extent) {

+                        s0 = tempS0;

+                        squareDistance = -s0 * s0 + s1

+                                * (s1 + (2.0f) * diffTestDot) + lengthOfDiff;

+                    } else {

+                        s0 = -extent;

+                        tempS1 = -(negativeDirectionDot * s0 + diffTestDot);

+                        if (tempS1 < -test.getExtent()) {

+                            s1 = -test.getExtent();

+                            squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        } else if (tempS1 <= test.getExtent()) {

+                            s1 = tempS1;

+                            squareDistance = -s1 * s1 + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        } else {

+                            s1 = test.getExtent();

+                            squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0

+                                    * (s0 + (2.0f) * diffThisDot)

+                                    + lengthOfDiff;

+                        }

+                    }

+                }

+            }

+        } else {

+            // The segments are parallel. The average b0 term is designed to

+            // ensure symmetry of the function. That is, dist(seg0,seg1) and

+            // dist(seg1,seg0) should produce the same number.get

+            float extentSum = extent + test.getExtent();

+            float sign = (negativeDirectionDot > 0.0f ? -1.0f : 1.0f);

+            float averageB0 = (0.5f) * (diffThisDot - sign * diffTestDot);

+            float lambda = -averageB0;

+            if (lambda < -extentSum) {

+                lambda = -extentSum;

+            } else if (lambda > extentSum) {

+                lambda = extentSum;

+            }

+

+            squareDistance = lambda * (lambda + (2.0f) * averageB0)

+                    + lengthOfDiff;

+        }

+

+        return FastMath.abs(squareDistance);

+    }

+

+    public float distanceSquared(Ray r) {

+        Vector3f kDiff = r.getOrigin().subtract(origin);

+        float fA01 = -r.getDirection().dot(direction);

+        float fB0 = kDiff.dot(r.getDirection());

+        float fB1 = -kDiff.dot(direction);

+        float fC = kDiff.lengthSquared();

+        float fDet = FastMath.abs(1.0f - fA01 * fA01);

+        float fS0, fS1, fSqrDist, fExtDet;

+

+        if (fDet >= FastMath.FLT_EPSILON) {

+            // The ray and segment are not parallel.

+            fS0 = fA01 * fB1 - fB0;

+            fS1 = fA01 * fB0 - fB1;

+            fExtDet = extent * fDet;

+

+            if (fS0 >= (float) 0.0) {

+                if (fS1 >= -fExtDet) {

+                    if (fS1 <= fExtDet) // region 0

+                    {

+                        // minimum at interior points of ray and segment

+                        float fInvDet = ((float) 1.0) / fDet;

+                        fS0 *= fInvDet;

+                        fS1 *= fInvDet;

+                        fSqrDist = fS0

+                                * (fS0 + fA01 * fS1 + ((float) 2.0) * fB0)

+                                + fS1

+                                * (fA01 * fS0 + fS1 + ((float) 2.0) * fB1) + fC;

+                    } else // region 1

+                    {

+                        fS1 = extent;

+                        fS0 = -(fA01 * fS1 + fB0);

+                        if (fS0 > (float) 0.0) {

+                            fSqrDist = -fS0 * fS0 + fS1

+                                    * (fS1 + ((float) 2.0) * fB1) + fC;

+                        } else {

+                            fS0 = (float) 0.0;

+                            fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+                        }

+                    }

+                } else // region 5

+                {

+                    fS1 = -extent;

+                    fS0 = -(fA01 * fS1 + fB0);

+                    if (fS0 > (float) 0.0) {

+                        fSqrDist = -fS0 * fS0 + fS1

+                                * (fS1 + ((float) 2.0) * fB1) + fC;

+                    } else {

+                        fS0 = (float) 0.0;

+                        fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+                    }

+                }

+            } else {

+                if (fS1 <= -fExtDet) // region 4

+                {

+                    fS0 = -(-fA01 * extent + fB0);

+                    if (fS0 > (float) 0.0) {

+                        fS1 = -extent;

+                        fSqrDist = -fS0 * fS0 + fS1

+                                * (fS1 + ((float) 2.0) * fB1) + fC;

+                    } else {

+                        fS0 = (float) 0.0;

+                        fS1 = -fB1;

+                        if (fS1 < -extent) {

+                            fS1 = -extent;

+                        } else if (fS1 > extent) {

+                            fS1 = extent;

+                        }

+                        fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+                    }

+                } else if (fS1 <= fExtDet) // region 3

+                {

+                    fS0 = (float) 0.0;

+                    fS1 = -fB1;

+                    if (fS1 < -extent) {

+                        fS1 = -extent;

+                    } else if (fS1 > extent) {

+                        fS1 = extent;

+                    }

+                    fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+                } else // region 2

+                {

+                    fS0 = -(fA01 * extent + fB0);

+                    if (fS0 > (float) 0.0) {

+                        fS1 = extent;

+                        fSqrDist = -fS0 * fS0 + fS1

+                                * (fS1 + ((float) 2.0) * fB1) + fC;

+                    } else {

+                        fS0 = (float) 0.0;

+                        fS1 = -fB1;

+                        if (fS1 < -extent) {

+                            fS1 = -extent;

+                        } else if (fS1 > extent) {

+                            fS1 = extent;

+                        }

+                        fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+                    }

+                }

+            }

+        } else {

+            // ray and segment are parallel

+            if (fA01 > (float) 0.0) {

+                // opposite direction vectors

+                fS1 = -extent;

+            } else {

+                // same direction vectors

+                fS1 = extent;

+            }

+

+            fS0 = -(fA01 * fS1 + fB0);

+            if (fS0 > (float) 0.0) {

+                fSqrDist = -fS0 * fS0 + fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+            } else {

+                fS0 = (float) 0.0;

+                fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;

+            }

+        }

+        return FastMath.abs(fSqrDist);

+    }

+

+    public Vector3f getDirection() {

+        return direction;

+    }

+

+    public void setDirection(Vector3f direction) {

+        this.direction = direction;

+    }

+

+    public float getExtent() {

+        return extent;

+    }

+

+    public void setExtent(float extent) {

+        this.extent = extent;

+    }

+

+    public Vector3f getOrigin() {

+        return origin;

+    }

+

+    public void setOrigin(Vector3f origin) {

+        this.origin = origin;

+    }

+

+    // P+e*D

+    public Vector3f getPositiveEnd(Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        return origin.add((direction.mult(extent, store)), store);

+    }

+

+    // P-e*D

+    public Vector3f getNegativeEnd(Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        return origin.subtract((direction.mult(extent, store)), store);

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(origin, "origin", Vector3f.ZERO);

+        capsule.write(direction, "direction", Vector3f.ZERO);

+        capsule.write(extent, "extent", 0);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());

+        direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());

+        extent = capsule.readFloat("extent", 0);

+    }

+

+    @Override

+    public LineSegment clone() {

+        try {

+            LineSegment segment = (LineSegment) super.clone();

+            segment.direction = direction.clone();

+            segment.origin = origin.clone();

+            return segment;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+

+    /**

+     * <p>Evaluates whether a given point is contained within the axis aligned bounding box

+     * that contains this LineSegment.</p><p>This function is float error aware.</p>

+     */

+    public boolean isPointInsideBounds(Vector3f point) {

+        return isPointInsideBounds(point, Float.MIN_VALUE);

+    }

+

+    /**

+     * <p>Evaluates whether a given point is contained within the axis aligned bounding box

+     * that contains this LineSegment.</p><p>This function accepts an error parameter, which

+     * is added to the extent of the bounding box.</p>

+     */

+    public boolean isPointInsideBounds(Vector3f point, float error) {

+

+        if (FastMath.abs(point.x - origin.x) > FastMath.abs(direction.x * extent) + error) {

+            return false;

+        }

+        if (FastMath.abs(point.y - origin.y) > FastMath.abs(direction.y * extent) + error) {

+            return false;

+        }

+        if (FastMath.abs(point.z - origin.z) > FastMath.abs(direction.z * extent) + error) {

+            return false;

+        }

+

+        return true;

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Matrix3f.java b/engine/src/core/com/jme3/math/Matrix3f.java
new file mode 100644
index 0000000..96bf1b3
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Matrix3f.java
@@ -0,0 +1,1387 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.export.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Logger;
+
+/**
+ * <code>Matrix3f</code> defines a 3x3 matrix. Matrix data is maintained
+ * internally and is accessible via the get and set methods. Convenience methods
+ * are used for matrix operations as well as generating a matrix from a given
+ * set of values.
+ * 
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Matrix3f implements Savable, Cloneable, java.io.Serializable {
+
+    static final long serialVersionUID = 1;
+
+    private static final Logger logger = Logger.getLogger(Matrix3f.class.getName());
+    protected float m00, m01, m02;
+    protected float m10, m11, m12;
+    protected float m20, m21, m22;
+    public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0);
+    public static final Matrix3f IDENTITY = new Matrix3f();
+
+    /**
+     * Constructor instantiates a new <code>Matrix3f</code> object. The
+     * initial values for the matrix is that of the identity matrix.
+     *  
+     */
+    public Matrix3f() {
+        loadIdentity();
+    }
+
+    /**
+     * constructs a matrix with the given values.
+     * 
+     * @param m00
+     *            0x0 in the matrix.
+     * @param m01
+     *            0x1 in the matrix.
+     * @param m02
+     *            0x2 in the matrix.
+     * @param m10
+     *            1x0 in the matrix.
+     * @param m11
+     *            1x1 in the matrix.
+     * @param m12
+     *            1x2 in the matrix.
+     * @param m20
+     *            2x0 in the matrix.
+     * @param m21
+     *            2x1 in the matrix.
+     * @param m22
+     *            2x2 in the matrix.
+     */
+    public Matrix3f(float m00, float m01, float m02, float m10, float m11,
+            float m12, float m20, float m21, float m22) {
+
+        this.m00 = m00;
+        this.m01 = m01;
+        this.m02 = m02;
+        this.m10 = m10;
+        this.m11 = m11;
+        this.m12 = m12;
+        this.m20 = m20;
+        this.m21 = m21;
+        this.m22 = m22;
+    }
+
+    /**
+     * Copy constructor that creates a new <code>Matrix3f</code> object that
+     * is the same as the provided matrix.
+     * 
+     * @param mat
+     *            the matrix to copy.
+     */
+    public Matrix3f(Matrix3f mat) {
+        set(mat);
+    }
+
+    /**
+     * Takes the absolute value of all matrix fields locally.
+     */
+    public void absoluteLocal() {
+        m00 = FastMath.abs(m00);
+        m01 = FastMath.abs(m01);
+        m02 = FastMath.abs(m02);
+        m10 = FastMath.abs(m10);
+        m11 = FastMath.abs(m11);
+        m12 = FastMath.abs(m12);
+        m20 = FastMath.abs(m20);
+        m21 = FastMath.abs(m21);
+        m22 = FastMath.abs(m22);
+    }
+
+    /**
+     * <code>copy</code> transfers the contents of a given matrix to this
+     * matrix. If a null matrix is supplied, this matrix is set to the identity
+     * matrix.
+     * 
+     * @param matrix
+     *            the matrix to copy.
+     * @return this
+     */
+    public Matrix3f set(Matrix3f matrix) {
+        if (null == matrix) {
+            loadIdentity();
+        } else {
+            m00 = matrix.m00;
+            m01 = matrix.m01;
+            m02 = matrix.m02;
+            m10 = matrix.m10;
+            m11 = matrix.m11;
+            m12 = matrix.m12;
+            m20 = matrix.m20;
+            m21 = matrix.m21;
+            m22 = matrix.m22;
+        }
+        return this;
+    }
+
+    /**
+     * <code>get</code> retrieves a value from the matrix at the given
+     * position. If the position is invalid a <code>JmeException</code> is
+     * thrown.
+     * 
+     * @param i
+     *            the row index.
+     * @param j
+     *            the colum index.
+     * @return the value at (i, j).
+     */
+    @SuppressWarnings("fallthrough")
+    public float get(int i, int j) {
+        switch (i) {
+            case 0:
+                switch (j) {
+                    case 0:
+                        return m00;
+                    case 1:
+                        return m01;
+                    case 2:
+                        return m02;
+                }
+            case 1:
+                switch (j) {
+                    case 0:
+                        return m10;
+                    case 1:
+                        return m11;
+                    case 2:
+                        return m12;
+                }
+            case 2:
+                switch (j) {
+                    case 0:
+                        return m20;
+                    case 1:
+                        return m21;
+                    case 2:
+                        return m22;
+                }
+        }
+
+        logger.warning("Invalid matrix index.");
+        throw new IllegalArgumentException("Invalid indices into matrix.");
+    }
+
+    /**
+     * <code>get(float[])</code> returns the matrix in row-major or column-major order.
+     *
+     * @param data
+     *      The array to return the data into. This array can be 9 or 16 floats in size.
+     *      Only the upper 3x3 are assigned to in the case of a 16 element array.
+     * @param rowMajor
+     *      True for row major storage in the array (translation in elements 3, 7, 11 for a 4x4),
+     *      false for column major (translation in elements 12, 13, 14 for a 4x4).
+     */
+    public void get(float[] data, boolean rowMajor) {
+        if (data.length == 9) {
+            if (rowMajor) {
+                data[0] = m00;
+                data[1] = m01;
+                data[2] = m02;
+                data[3] = m10;
+                data[4] = m11;
+                data[5] = m12;
+                data[6] = m20;
+                data[7] = m21;
+                data[8] = m22;
+            } else {
+                data[0] = m00;
+                data[1] = m10;
+                data[2] = m20;
+                data[3] = m01;
+                data[4] = m11;
+                data[5] = m21;
+                data[6] = m02;
+                data[7] = m12;
+                data[8] = m22;
+            }
+        } else if (data.length == 16) {
+            if (rowMajor) {
+                data[0] = m00;
+                data[1] = m01;
+                data[2] = m02;
+                data[4] = m10;
+                data[5] = m11;
+                data[6] = m12;
+                data[8] = m20;
+                data[9] = m21;
+                data[10] = m22;
+            } else {
+                data[0] = m00;
+                data[1] = m10;
+                data[2] = m20;
+                data[4] = m01;
+                data[5] = m11;
+                data[6] = m21;
+                data[8] = m02;
+                data[9] = m12;
+                data[10] = m22;
+            }
+        } else {
+            throw new IndexOutOfBoundsException("Array size must be 9 or 16 in Matrix3f.get().");
+        }
+    }
+
+    /**
+     * <code>getColumn</code> returns one of three columns specified by the
+     * parameter. This column is returned as a <code>Vector3f</code> object.
+     * 
+     * @param i
+     *            the column to retrieve. Must be between 0 and 2.
+     * @return the column specified by the index.
+     */
+    public Vector3f getColumn(int i) {
+        return getColumn(i, null);
+    }
+
+    /**
+     * <code>getColumn</code> returns one of three columns specified by the
+     * parameter. This column is returned as a <code>Vector3f</code> object.
+     * 
+     * @param i
+     *            the column to retrieve. Must be between 0 and 2.
+     * @param store
+     *            the vector object to store the result in. if null, a new one
+     *            is created.
+     * @return the column specified by the index.
+     */
+    public Vector3f getColumn(int i, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        switch (i) {
+            case 0:
+                store.x = m00;
+                store.y = m10;
+                store.z = m20;
+                break;
+            case 1:
+                store.x = m01;
+                store.y = m11;
+                store.z = m21;
+                break;
+            case 2:
+                store.x = m02;
+                store.y = m12;
+                store.z = m22;
+                break;
+            default:
+                logger.warning("Invalid column index.");
+                throw new IllegalArgumentException("Invalid column index. " + i);
+        }
+        return store;
+    }
+
+    /**
+     * <code>getColumn</code> returns one of three rows as specified by the
+     * parameter. This row is returned as a <code>Vector3f</code> object.
+     * 
+     * @param i
+     *            the row to retrieve. Must be between 0 and 2.
+     * @return the row specified by the index.
+     */
+    public Vector3f getRow(int i) {
+        return getRow(i, null);
+    }
+
+    /**
+     * <code>getRow</code> returns one of three rows as specified by the
+     * parameter. This row is returned as a <code>Vector3f</code> object.
+     * 
+     * @param i
+     *            the row to retrieve. Must be between 0 and 2.
+     * @param store
+     *            the vector object to store the result in. if null, a new one
+     *            is created.
+     * @return the row specified by the index.
+     */
+    public Vector3f getRow(int i, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        switch (i) {
+            case 0:
+                store.x = m00;
+                store.y = m01;
+                store.z = m02;
+                break;
+            case 1:
+                store.x = m10;
+                store.y = m11;
+                store.z = m12;
+                break;
+            case 2:
+                store.x = m20;
+                store.y = m21;
+                store.z = m22;
+                break;
+            default:
+                logger.warning("Invalid row index.");
+                throw new IllegalArgumentException("Invalid row index. " + i);
+        }
+        return store;
+    }
+
+    /**
+     * <code>toFloatBuffer</code> returns a FloatBuffer object that contains
+     * the matrix data.
+     * 
+     * @return matrix data as a FloatBuffer.
+     */
+    public FloatBuffer toFloatBuffer() {
+        FloatBuffer fb = BufferUtils.createFloatBuffer(9);
+
+        fb.put(m00).put(m01).put(m02);
+        fb.put(m10).put(m11).put(m12);
+        fb.put(m20).put(m21).put(m22);
+        fb.rewind();
+        return fb;
+    }
+
+    /**
+     * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+     * data.
+     * 
+     * @param fb
+     *            the buffer to fill, starting at current position. Must have
+     *            room for 9 more floats.
+     * @return matrix data as a FloatBuffer. (position is advanced by 9 and any
+     *         limit set is not changed).
+     */
+    public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+//        if (columnMajor){
+//            fb.put(m00).put(m10).put(m20);
+//            fb.put(m01).put(m11).put(m21);
+//            fb.put(m02).put(m12).put(m22);
+//        }else{
+//            fb.put(m00).put(m01).put(m02);
+//            fb.put(m10).put(m11).put(m12);
+//            fb.put(m20).put(m21).put(m22);
+//        }
+
+        TempVars vars = TempVars.get();
+
+
+        fillFloatArray(vars.matrixWrite, columnMajor);
+        fb.put(vars.matrixWrite, 0, 9);
+
+        vars.release();
+
+        return fb;
+    }
+
+    public void fillFloatArray(float[] f, boolean columnMajor) {
+        if (columnMajor) {
+            f[ 0] = m00;
+            f[ 1] = m10;
+            f[ 2] = m20;
+            f[ 3] = m01;
+            f[ 4] = m11;
+            f[ 5] = m21;
+            f[ 6] = m02;
+            f[ 7] = m12;
+            f[ 8] = m22;
+        } else {
+            f[ 0] = m00;
+            f[ 1] = m01;
+            f[ 2] = m02;
+            f[ 3] = m10;
+            f[ 4] = m11;
+            f[ 5] = m12;
+            f[ 6] = m20;
+            f[ 7] = m21;
+            f[ 8] = m22;
+        }
+    }
+
+    /**
+     * 
+     * <code>setColumn</code> sets a particular column of this matrix to that
+     * represented by the provided vector.
+     * 
+     * @param i
+     *            the column to set.
+     * @param column
+     *            the data to set.
+     * @return this
+     */
+    public Matrix3f setColumn(int i, Vector3f column) {
+
+        if (column == null) {
+            logger.warning("Column is null. Ignoring.");
+            return this;
+        }
+        switch (i) {
+            case 0:
+                m00 = column.x;
+                m10 = column.y;
+                m20 = column.z;
+                break;
+            case 1:
+                m01 = column.x;
+                m11 = column.y;
+                m21 = column.z;
+                break;
+            case 2:
+                m02 = column.x;
+                m12 = column.y;
+                m22 = column.z;
+                break;
+            default:
+                logger.warning("Invalid column index.");
+                throw new IllegalArgumentException("Invalid column index. " + i);
+        }
+        return this;
+    }
+
+    /**
+     * 
+     * <code>setRow</code> sets a particular row of this matrix to that
+     * represented by the provided vector.
+     * 
+     * @param i
+     *            the row to set.
+     * @param row
+     *            the data to set.
+     * @return this
+     */
+    public Matrix3f setRow(int i, Vector3f row) {
+
+        if (row == null) {
+            logger.warning("Row is null. Ignoring.");
+            return this;
+        }
+        switch (i) {
+            case 0:
+                m00 = row.x;
+                m01 = row.y;
+                m02 = row.z;
+                break;
+            case 1:
+                m10 = row.x;
+                m11 = row.y;
+                m12 = row.z;
+                break;
+            case 2:
+                m20 = row.x;
+                m21 = row.y;
+                m22 = row.z;
+                break;
+            default:
+                logger.warning("Invalid row index.");
+                throw new IllegalArgumentException("Invalid row index. " + i);
+        }
+        return this;
+    }
+
+    /**
+     * <code>set</code> places a given value into the matrix at the given
+     * position. If the position is invalid a <code>JmeException</code> is
+     * thrown.
+     * 
+     * @param i
+     *            the row index.
+     * @param j
+     *            the colum index.
+     * @param value
+     *            the value for (i, j).
+     * @return this
+     */
+    @SuppressWarnings("fallthrough")
+    public Matrix3f set(int i, int j, float value) {
+        switch (i) {
+            case 0:
+                switch (j) {
+                    case 0:
+                        m00 = value;
+                        return this;
+                    case 1:
+                        m01 = value;
+                        return this;
+                    case 2:
+                        m02 = value;
+                        return this;
+                }
+            case 1:
+                switch (j) {
+                    case 0:
+                        m10 = value;
+                        return this;
+                    case 1:
+                        m11 = value;
+                        return this;
+                    case 2:
+                        m12 = value;
+                        return this;
+                }
+            case 2:
+                switch (j) {
+                    case 0:
+                        m20 = value;
+                        return this;
+                    case 1:
+                        m21 = value;
+                        return this;
+                    case 2:
+                        m22 = value;
+                        return this;
+                }
+        }
+
+        logger.warning("Invalid matrix index.");
+        throw new IllegalArgumentException("Invalid indices into matrix.");
+    }
+
+    /**
+     * 
+     * <code>set</code> sets the values of the matrix to those supplied by the
+     * 3x3 two dimenion array.
+     * 
+     * @param matrix
+     *            the new values of the matrix.
+     * @throws JmeException
+     *             if the array is not of size 9.
+     * @return this
+     */
+    public Matrix3f set(float[][] matrix) {
+        if (matrix.length != 3 || matrix[0].length != 3) {
+            throw new IllegalArgumentException(
+                    "Array must be of size 9.");
+        }
+
+        m00 = matrix[0][0];
+        m01 = matrix[0][1];
+        m02 = matrix[0][2];
+        m10 = matrix[1][0];
+        m11 = matrix[1][1];
+        m12 = matrix[1][2];
+        m20 = matrix[2][0];
+        m21 = matrix[2][1];
+        m22 = matrix[2][2];
+
+        return this;
+    }
+
+    /**
+     * Recreate Matrix using the provided axis.
+     * 
+     * @param uAxis
+     *            Vector3f
+     * @param vAxis
+     *            Vector3f
+     * @param wAxis
+     *            Vector3f
+     */
+    public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) {
+        m00 = uAxis.x;
+        m10 = uAxis.y;
+        m20 = uAxis.z;
+
+        m01 = vAxis.x;
+        m11 = vAxis.y;
+        m21 = vAxis.z;
+
+        m02 = wAxis.x;
+        m12 = wAxis.y;
+        m22 = wAxis.z;
+    }
+
+    /**
+     * <code>set</code> sets the values of this matrix from an array of
+     * values assuming that the data is rowMajor order;
+     * 
+     * @param matrix
+     *            the matrix to set the value to.
+     * @return this
+     */
+    public Matrix3f set(float[] matrix) {
+        return set(matrix, true);
+    }
+
+    /**
+     * <code>set</code> sets the values of this matrix from an array of
+     * values;
+     * 
+     * @param matrix
+     *            the matrix to set the value to.
+     * @param rowMajor
+     *            whether the incoming data is in row or column major order.
+     * @return this
+     */
+    public Matrix3f set(float[] matrix, boolean rowMajor) {
+        if (matrix.length != 9) {
+            throw new IllegalArgumentException(
+                    "Array must be of size 9.");
+        }
+
+        if (rowMajor) {
+            m00 = matrix[0];
+            m01 = matrix[1];
+            m02 = matrix[2];
+            m10 = matrix[3];
+            m11 = matrix[4];
+            m12 = matrix[5];
+            m20 = matrix[6];
+            m21 = matrix[7];
+            m22 = matrix[8];
+        } else {
+            m00 = matrix[0];
+            m01 = matrix[3];
+            m02 = matrix[6];
+            m10 = matrix[1];
+            m11 = matrix[4];
+            m12 = matrix[7];
+            m20 = matrix[2];
+            m21 = matrix[5];
+            m22 = matrix[8];
+        }
+        return this;
+    }
+
+    /**
+     * 
+     * <code>set</code> defines the values of the matrix based on a supplied
+     * <code>Quaternion</code>. It should be noted that all previous values
+     * will be overridden.
+     * 
+     * @param quaternion
+     *            the quaternion to create a rotational matrix from.
+     * @return this
+     */
+    public Matrix3f set(Quaternion quaternion) {
+        return quaternion.toRotationMatrix(this);
+    }
+
+    /**
+     * <code>loadIdentity</code> sets this matrix to the identity matrix.
+     * Where all values are zero except those along the diagonal which are one.
+     *  
+     */
+    public void loadIdentity() {
+        m01 = m02 = m10 = m12 = m20 = m21 = 0;
+        m00 = m11 = m22 = 1;
+    }
+
+    /**
+     * @return true if this matrix is identity
+     */
+    public boolean isIdentity() {
+        return (m00 == 1 && m01 == 0 && m02 == 0)
+                && (m10 == 0 && m11 == 1 && m12 == 0)
+                && (m20 == 0 && m21 == 0 && m22 == 1);
+    }
+
+    /**
+     * <code>fromAngleAxis</code> sets this matrix4f to the values specified
+     * by an angle and an axis of rotation.  This method creates an object, so
+     * use fromAngleNormalAxis if your axis is already normalized.
+     * 
+     * @param angle
+     *            the angle to rotate (in radians).
+     * @param axis
+     *            the axis of rotation.
+     */
+    public void fromAngleAxis(float angle, Vector3f axis) {
+        Vector3f normAxis = axis.normalize();
+        fromAngleNormalAxis(angle, normAxis);
+    }
+
+    /**
+     * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
+     * specified by an angle and a normalized axis of rotation.
+     * 
+     * @param angle
+     *            the angle to rotate (in radians).
+     * @param axis
+     *            the axis of rotation (already normalized).
+     */
+    public void fromAngleNormalAxis(float angle, Vector3f axis) {
+        float fCos = FastMath.cos(angle);
+        float fSin = FastMath.sin(angle);
+        float fOneMinusCos = ((float) 1.0) - fCos;
+        float fX2 = axis.x * axis.x;
+        float fY2 = axis.y * axis.y;
+        float fZ2 = axis.z * axis.z;
+        float fXYM = axis.x * axis.y * fOneMinusCos;
+        float fXZM = axis.x * axis.z * fOneMinusCos;
+        float fYZM = axis.y * axis.z * fOneMinusCos;
+        float fXSin = axis.x * fSin;
+        float fYSin = axis.y * fSin;
+        float fZSin = axis.z * fSin;
+
+        m00 = fX2 * fOneMinusCos + fCos;
+        m01 = fXYM - fZSin;
+        m02 = fXZM + fYSin;
+        m10 = fXYM + fZSin;
+        m11 = fY2 * fOneMinusCos + fCos;
+        m12 = fYZM - fXSin;
+        m20 = fXZM - fYSin;
+        m21 = fYZM + fXSin;
+        m22 = fZ2 * fOneMinusCos + fCos;
+    }
+
+    /**
+     * <code>mult</code> multiplies this matrix by a given matrix. The result
+     * matrix is returned as a new object. If the given matrix is null, a null
+     * matrix is returned.
+     * 
+     * @param mat
+     *            the matrix to multiply this matrix by.
+     * @return the result matrix.
+     */
+    public Matrix3f mult(Matrix3f mat) {
+        return mult(mat, null);
+    }
+
+    /**
+     * <code>mult</code> multiplies this matrix by a given matrix. The result
+     * matrix is returned as a new object.
+     * 
+     * @param mat
+     *            the matrix to multiply this matrix by.
+     * @param product
+     *            the matrix to store the result in. if null, a new matrix3f is
+     *            created.  It is safe for mat and product to be the same object.
+     * @return a matrix3f object containing the result of this operation
+     */
+    public Matrix3f mult(Matrix3f mat, Matrix3f product) {
+
+        float temp00, temp01, temp02;
+        float temp10, temp11, temp12;
+        float temp20, temp21, temp22;
+
+        if (product == null) {
+            product = new Matrix3f();
+        }
+        temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20;
+        temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21;
+        temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22;
+        temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20;
+        temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21;
+        temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22;
+        temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20;
+        temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21;
+        temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22;
+
+        product.m00 = temp00;
+        product.m01 = temp01;
+        product.m02 = temp02;
+        product.m10 = temp10;
+        product.m11 = temp11;
+        product.m12 = temp12;
+        product.m20 = temp20;
+        product.m21 = temp21;
+        product.m22 = temp22;
+
+        return product;
+    }
+
+    /**
+     * <code>mult</code> multiplies this matrix by a given
+     * <code>Vector3f</code> object. The result vector is returned. If the
+     * given vector is null, null will be returned.
+     * 
+     * @param vec
+     *            the vector to multiply this matrix by.
+     * @return the result vector.
+     */
+    public Vector3f mult(Vector3f vec) {
+        return mult(vec, null);
+    }
+
+    /**
+     * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in
+     * product.
+     * 
+     * @param vec
+     *            The Vector3f to multiply.
+     * @param product
+     *            The Vector3f to store the result, it is safe for this to be
+     *            the same as vec.
+     * @return The given product vector.
+     */
+    public Vector3f mult(Vector3f vec, Vector3f product) {
+
+        if (null == product) {
+            product = new Vector3f();
+        }
+
+        float x = vec.x;
+        float y = vec.y;
+        float z = vec.z;
+
+        product.x = m00 * x + m01 * y + m02 * z;
+        product.y = m10 * x + m11 * y + m12 * z;
+        product.z = m20 * x + m21 * y + m22 * z;
+        return product;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this matrix internally by 
+     * a given float scale factor.
+     * 
+     * @param scale
+     *            the value to scale by.
+     * @return this Matrix3f
+     */
+    public Matrix3f multLocal(float scale) {
+        m00 *= scale;
+        m01 *= scale;
+        m02 *= scale;
+        m10 *= scale;
+        m11 *= scale;
+        m12 *= scale;
+        m20 *= scale;
+        m21 *= scale;
+        m22 *= scale;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this matrix by a given
+     * <code>Vector3f</code> object. The result vector is stored inside the
+     * passed vector, then returned . If the given vector is null, null will be
+     * returned.
+     * 
+     * @param vec
+     *            the vector to multiply this matrix by.
+     * @return The passed vector after multiplication
+     */
+    public Vector3f multLocal(Vector3f vec) {
+        if (vec == null) {
+            return null;
+        }
+        float x = vec.x;
+        float y = vec.y;
+        vec.x = m00 * x + m01 * y + m02 * vec.z;
+        vec.y = m10 * x + m11 * y + m12 * vec.z;
+        vec.z = m20 * x + m21 * y + m22 * vec.z;
+        return vec;
+    }
+
+    /**
+     * <code>mult</code> multiplies this matrix by a given matrix. The result
+     * matrix is saved in the current matrix. If the given matrix is null,
+     * nothing happens. The current matrix is returned. This is equivalent to
+     * this*=mat
+     * 
+     * @param mat
+     *            the matrix to multiply this matrix by.
+     * @return This matrix, after the multiplication
+     */
+    public Matrix3f multLocal(Matrix3f mat) {
+        return mult(mat, this);
+    }
+
+    /**
+     * Transposes this matrix in place. Returns this matrix for chaining
+     * 
+     * @return This matrix after transpose
+     */
+    public Matrix3f transposeLocal() {
+//        float[] tmp = new float[9];
+//        get(tmp, false);
+//        set(tmp, true);
+
+        float tmp = m01;
+        m01 = m10;
+        m10 = tmp;
+
+        tmp = m02;
+        m02 = m20;
+        m20 = tmp;
+
+        tmp = m12;
+        m12 = m21;
+        m21 = tmp;
+
+        return this;
+    }
+
+    /**
+     * Inverts this matrix as a new Matrix3f.
+     * 
+     * @return The new inverse matrix
+     */
+    public Matrix3f invert() {
+        return invert(null);
+    }
+
+    /**
+     * Inverts this matrix and stores it in the given store.
+     * 
+     * @return The store
+     */
+    public Matrix3f invert(Matrix3f store) {
+        if (store == null) {
+            store = new Matrix3f();
+        }
+
+        float det = determinant();
+        if (FastMath.abs(det) <= FastMath.FLT_EPSILON) {
+            return store.zero();
+        }
+
+        store.m00 = m11 * m22 - m12 * m21;
+        store.m01 = m02 * m21 - m01 * m22;
+        store.m02 = m01 * m12 - m02 * m11;
+        store.m10 = m12 * m20 - m10 * m22;
+        store.m11 = m00 * m22 - m02 * m20;
+        store.m12 = m02 * m10 - m00 * m12;
+        store.m20 = m10 * m21 - m11 * m20;
+        store.m21 = m01 * m20 - m00 * m21;
+        store.m22 = m00 * m11 - m01 * m10;
+
+        store.multLocal(1f / det);
+        return store;
+    }
+
+    /**
+     * Inverts this matrix locally.
+     * 
+     * @return this
+     */
+    public Matrix3f invertLocal() {
+        float det = determinant();
+        if (FastMath.abs(det) <= 0f) {
+            return zero();
+        }
+
+        float f00 = m11 * m22 - m12 * m21;
+        float f01 = m02 * m21 - m01 * m22;
+        float f02 = m01 * m12 - m02 * m11;
+        float f10 = m12 * m20 - m10 * m22;
+        float f11 = m00 * m22 - m02 * m20;
+        float f12 = m02 * m10 - m00 * m12;
+        float f20 = m10 * m21 - m11 * m20;
+        float f21 = m01 * m20 - m00 * m21;
+        float f22 = m00 * m11 - m01 * m10;
+
+        m00 = f00;
+        m01 = f01;
+        m02 = f02;
+        m10 = f10;
+        m11 = f11;
+        m12 = f12;
+        m20 = f20;
+        m21 = f21;
+        m22 = f22;
+
+        multLocal(1f / det);
+        return this;
+    }
+
+    /**
+     * Returns a new matrix representing the adjoint of this matrix.
+     * 
+     * @return The adjoint matrix
+     */
+    public Matrix3f adjoint() {
+        return adjoint(null);
+    }
+
+    /**
+     * Places the adjoint of this matrix in store (creates store if null.)
+     * 
+     * @param store
+     *            The matrix to store the result in.  If null, a new matrix is created.
+     * @return store
+     */
+    public Matrix3f adjoint(Matrix3f store) {
+        if (store == null) {
+            store = new Matrix3f();
+        }
+
+        store.m00 = m11 * m22 - m12 * m21;
+        store.m01 = m02 * m21 - m01 * m22;
+        store.m02 = m01 * m12 - m02 * m11;
+        store.m10 = m12 * m20 - m10 * m22;
+        store.m11 = m00 * m22 - m02 * m20;
+        store.m12 = m02 * m10 - m00 * m12;
+        store.m20 = m10 * m21 - m11 * m20;
+        store.m21 = m01 * m20 - m00 * m21;
+        store.m22 = m00 * m11 - m01 * m10;
+
+        return store;
+    }
+
+    /**
+     * <code>determinant</code> generates the determinant of this matrix.
+     * 
+     * @return the determinant
+     */
+    public float determinant() {
+        float fCo00 = m11 * m22 - m12 * m21;
+        float fCo10 = m12 * m20 - m10 * m22;
+        float fCo20 = m10 * m21 - m11 * m20;
+        float fDet = m00 * fCo00 + m01 * fCo10 + m02 * fCo20;
+        return fDet;
+    }
+
+    /**
+     * Sets all of the values in this matrix to zero.
+     * 
+     * @return this matrix
+     */
+    public Matrix3f zero() {
+        m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f;
+        return this;
+    }
+
+    /**
+     * <code>transpose</code> <b>locally</b> transposes this Matrix.
+     * This is inconsistent with general value vs local semantics, but is
+     * preserved for backwards compatibility. Use transposeNew() to transpose
+     * to a new object (value).
+     * 
+     * @return this object for chaining.
+     */
+    public Matrix3f transpose() {
+        return transposeLocal();
+    }
+
+    /**
+     * <code>transposeNew</code> returns a transposed version of this matrix.
+     *
+     * @return The new Matrix3f object.
+     */
+    public Matrix3f transposeNew() {
+        Matrix3f ret = new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22);
+        return ret;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this object.
+     * It is in a format of a 3x3 matrix. For example, an identity matrix would
+     * be represented by the following string. com.jme.math.Matrix3f <br>[<br>
+     * 1.0  0.0  0.0 <br>
+     * 0.0  1.0  0.0 <br>
+     * 0.0  0.0  1.0 <br>]<br>
+     * 
+     * @return the string representation of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder("Matrix3f\n[\n");
+        result.append(" ");
+        result.append(m00);
+        result.append("  ");
+        result.append(m01);
+        result.append("  ");
+        result.append(m02);
+        result.append(" \n");
+        result.append(" ");
+        result.append(m10);
+        result.append("  ");
+        result.append(m11);
+        result.append("  ");
+        result.append(m12);
+        result.append(" \n");
+        result.append(" ");
+        result.append(m20);
+        result.append("  ");
+        result.append(m21);
+        result.append("  ");
+        result.append(m22);
+        result.append(" \n]");
+        return result.toString();
+    }
+
+    /**
+     * 
+     * <code>hashCode</code> returns the hash code value as an integer and is
+     * supported for the benefit of hashing based collection classes such as
+     * Hashtable, HashMap, HashSet etc.
+     * 
+     * @return the hashcode for this instance of Matrix4f.
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        int hash = 37;
+        hash = 37 * hash + Float.floatToIntBits(m00);
+        hash = 37 * hash + Float.floatToIntBits(m01);
+        hash = 37 * hash + Float.floatToIntBits(m02);
+
+        hash = 37 * hash + Float.floatToIntBits(m10);
+        hash = 37 * hash + Float.floatToIntBits(m11);
+        hash = 37 * hash + Float.floatToIntBits(m12);
+
+        hash = 37 * hash + Float.floatToIntBits(m20);
+        hash = 37 * hash + Float.floatToIntBits(m21);
+        hash = 37 * hash + Float.floatToIntBits(m22);
+
+        return hash;
+    }
+
+    /**
+     * are these two matrices the same? they are is they both have the same mXX values.
+     *
+     * @param o
+     *            the object to compare for equality
+     * @return true if they are equal
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Matrix3f) || o == null) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        Matrix3f comp = (Matrix3f) o;
+        if (Float.compare(m00, comp.m00) != 0) {
+            return false;
+        }
+        if (Float.compare(m01, comp.m01) != 0) {
+            return false;
+        }
+        if (Float.compare(m02, comp.m02) != 0) {
+            return false;
+        }
+
+        if (Float.compare(m10, comp.m10) != 0) {
+            return false;
+        }
+        if (Float.compare(m11, comp.m11) != 0) {
+            return false;
+        }
+        if (Float.compare(m12, comp.m12) != 0) {
+            return false;
+        }
+
+        if (Float.compare(m20, comp.m20) != 0) {
+            return false;
+        }
+        if (Float.compare(m21, comp.m21) != 0) {
+            return false;
+        }
+        if (Float.compare(m22, comp.m22) != 0) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule cap = e.getCapsule(this);
+        cap.write(m00, "m00", 1);
+        cap.write(m01, "m01", 0);
+        cap.write(m02, "m02", 0);
+        cap.write(m10, "m10", 0);
+        cap.write(m11, "m11", 1);
+        cap.write(m12, "m12", 0);
+        cap.write(m20, "m20", 0);
+        cap.write(m21, "m21", 0);
+        cap.write(m22, "m22", 1);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule cap = e.getCapsule(this);
+        m00 = cap.readFloat("m00", 1);
+        m01 = cap.readFloat("m01", 0);
+        m02 = cap.readFloat("m02", 0);
+        m10 = cap.readFloat("m10", 0);
+        m11 = cap.readFloat("m11", 1);
+        m12 = cap.readFloat("m12", 0);
+        m20 = cap.readFloat("m20", 0);
+        m21 = cap.readFloat("m21", 0);
+        m22 = cap.readFloat("m22", 1);
+    }
+
+    /**
+     * A function for creating a rotation matrix that rotates a vector called
+     * "start" into another vector called "end".
+     * 
+     * @param start
+     *            normalized non-zero starting vector
+     * @param end
+     *            normalized non-zero ending vector
+     * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate \
+     *      One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999"
+     */
+    public void fromStartEndVectors(Vector3f start, Vector3f end) {
+        Vector3f v = new Vector3f();
+        float e, h, f;
+
+        start.cross(end, v);
+        e = start.dot(end);
+        f = (e < 0) ? -e : e;
+
+        // if "from" and "to" vectors are nearly parallel
+        if (f > 1.0f - FastMath.ZERO_TOLERANCE) {
+            Vector3f u = new Vector3f();
+            Vector3f x = new Vector3f();
+            float c1, c2, c3; /* coefficients for later use */
+            int i, j;
+
+            x.x = (start.x > 0.0) ? start.x : -start.x;
+            x.y = (start.y > 0.0) ? start.y : -start.y;
+            x.z = (start.z > 0.0) ? start.z : -start.z;
+
+            if (x.x < x.y) {
+                if (x.x < x.z) {
+                    x.x = 1.0f;
+                    x.y = x.z = 0.0f;
+                } else {
+                    x.z = 1.0f;
+                    x.x = x.y = 0.0f;
+                }
+            } else {
+                if (x.y < x.z) {
+                    x.y = 1.0f;
+                    x.x = x.z = 0.0f;
+                } else {
+                    x.z = 1.0f;
+                    x.x = x.y = 0.0f;
+                }
+            }
+
+            u.x = x.x - start.x;
+            u.y = x.y - start.y;
+            u.z = x.z - start.z;
+            v.x = x.x - end.x;
+            v.y = x.y - end.y;
+            v.z = x.z - end.z;
+
+            c1 = 2.0f / u.dot(u);
+            c2 = 2.0f / v.dot(v);
+            c3 = c1 * c2 * u.dot(v);
+
+            for (i = 0; i < 3; i++) {
+                for (j = 0; j < 3; j++) {
+                    float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i)
+                            * v.get(j) + c3 * v.get(i) * u.get(j);
+                    set(i, j, val);
+                }
+                float val = get(i, i);
+                set(i, i, val + 1.0f);
+            }
+        } else {
+            // the most common case, unless "start"="end", or "start"=-"end"
+            float hvx, hvz, hvxy, hvxz, hvyz;
+            h = 1.0f / (1.0f + e);
+            hvx = h * v.x;
+            hvz = h * v.z;
+            hvxy = hvx * v.y;
+            hvxz = hvx * v.z;
+            hvyz = hvz * v.y;
+            set(0, 0, e + hvx * v.x);
+            set(0, 1, hvxy - v.z);
+            set(0, 2, hvxz + v.y);
+
+            set(1, 0, hvxy + v.z);
+            set(1, 1, e + h * v.y * v.y);
+            set(1, 2, hvyz - v.x);
+
+            set(2, 0, hvxz - v.y);
+            set(2, 1, hvyz + v.x);
+            set(2, 2, e + hvz * v.z);
+        }
+    }
+
+    /**
+     * <code>scale</code> scales the operation performed by this matrix on a
+     * per-component basis.
+     *
+     * @param scale
+     *         The scale applied to each of the X, Y and Z output values.
+     */
+    public void scale(Vector3f scale) {
+        m00 *= scale.x;
+        m10 *= scale.x;
+        m20 *= scale.x;
+        m01 *= scale.y;
+        m11 *= scale.y;
+        m21 *= scale.y;
+        m02 *= scale.z;
+        m12 *= scale.z;
+        m22 *= scale.z;
+    }
+
+    static boolean equalIdentity(Matrix3f mat) {
+        if (Math.abs(mat.m00 - 1) > 1e-4) {
+            return false;
+        }
+        if (Math.abs(mat.m11 - 1) > 1e-4) {
+            return false;
+        }
+        if (Math.abs(mat.m22 - 1) > 1e-4) {
+            return false;
+        }
+
+        if (Math.abs(mat.m01) > 1e-4) {
+            return false;
+        }
+        if (Math.abs(mat.m02) > 1e-4) {
+            return false;
+        }
+
+        if (Math.abs(mat.m10) > 1e-4) {
+            return false;
+        }
+        if (Math.abs(mat.m12) > 1e-4) {
+            return false;
+        }
+
+        if (Math.abs(mat.m20) > 1e-4) {
+            return false;
+        }
+        if (Math.abs(mat.m21) > 1e-4) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public Matrix3f clone() {
+        try {
+            return (Matrix3f) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(); // can not happen
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/math/Matrix4f.java b/engine/src/core/com/jme3/math/Matrix4f.java
new file mode 100644
index 0000000..8521eab
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Matrix4f.java
@@ -0,0 +1,2305 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+import java.util.logging.Logger;

+

+/**

+ * <code>Matrix4f</code> defines and maintains a 4x4 matrix in row major order.

+ * This matrix is intended for use in a translation and rotational capacity. 

+ * It provides convenience methods for creating the matrix from a multitude 

+ * of sources.

+ * 

+ * Matrices are stored assuming column vectors on the right, with the translation

+ * in the rightmost column. Element numbering is row,column, so m03 is the zeroth

+ * row, third column, which is the "x" translation part. This means that the implicit

+ * storage order is column major. However, the get() and set() functions on float

+ * arrays default to row major order!

+ *

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public final class Matrix4f implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private static final Logger logger = Logger.getLogger(Matrix4f.class.getName());

+    public float m00, m01, m02, m03;

+    public float m10, m11, m12, m13;

+    public float m20, m21, m22, m23;

+    public float m30, m31, m32, m33;

+    public static final Matrix4f ZERO = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

+    public static final Matrix4f IDENTITY = new Matrix4f();

+

+    /**

+     * Constructor instantiates a new <code>Matrix</code> that is set to the

+     * identity matrix.

+     *  

+     */

+    public Matrix4f() {

+        loadIdentity();

+    }

+

+    /**

+     * constructs a matrix with the given values.

+     */

+    public Matrix4f(float m00, float m01, float m02, float m03,

+            float m10, float m11, float m12, float m13,

+            float m20, float m21, float m22, float m23,

+            float m30, float m31, float m32, float m33) {

+

+        this.m00 = m00;

+        this.m01 = m01;

+        this.m02 = m02;

+        this.m03 = m03;

+        this.m10 = m10;

+        this.m11 = m11;

+        this.m12 = m12;

+        this.m13 = m13;

+        this.m20 = m20;

+        this.m21 = m21;

+        this.m22 = m22;

+        this.m23 = m23;

+        this.m30 = m30;

+        this.m31 = m31;

+        this.m32 = m32;

+        this.m33 = m33;

+    }

+

+    /**

+     * Create a new Matrix4f, given data in column-major format.

+     *

+     * @param array

+     *		An array of 16 floats in column-major format (translation in elements 12, 13 and 14).

+     */

+    public Matrix4f(float[] array) {

+        set(array, false);

+    }

+

+    /**

+     * Constructor instantiates a new <code>Matrix</code> that is set to the

+     * provided matrix. This constructor copies a given Matrix. If the provided

+     * matrix is null, the constructor sets the matrix to the identity.

+     * 

+     * @param mat

+     *            the matrix to copy.

+     */

+    public Matrix4f(Matrix4f mat) {

+        copy(mat);

+    }

+

+    /**

+     * <code>copy</code> transfers the contents of a given matrix to this

+     * matrix. If a null matrix is supplied, this matrix is set to the identity

+     * matrix.

+     * 

+     * @param matrix

+     *            the matrix to copy.

+     */

+    public void copy(Matrix4f matrix) {

+        if (null == matrix) {

+            loadIdentity();

+        } else {

+            m00 = matrix.m00;

+            m01 = matrix.m01;

+            m02 = matrix.m02;

+            m03 = matrix.m03;

+            m10 = matrix.m10;

+            m11 = matrix.m11;

+            m12 = matrix.m12;

+            m13 = matrix.m13;

+            m20 = matrix.m20;

+            m21 = matrix.m21;

+            m22 = matrix.m22;

+            m23 = matrix.m23;

+            m30 = matrix.m30;

+            m31 = matrix.m31;

+            m32 = matrix.m32;

+            m33 = matrix.m33;

+        }

+    }

+

+    public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector3f left) {

+        loadIdentity();

+

+        TempVars vars = TempVars.get();

+

+        Vector3f f = vars.vect1.set(direction);

+        Vector3f s = vars.vect2.set(f).crossLocal(up);

+        Vector3f u = vars.vect3.set(s).crossLocal(f);

+//        s.normalizeLocal();

+//        u.normalizeLocal();

+

+        m00 = s.x;

+        m01 = s.y;

+        m02 = s.z;

+

+        m10 = u.x;

+        m11 = u.y;

+        m12 = u.z;

+

+        m20 = -f.x;

+        m21 = -f.y;

+        m22 = -f.z;

+

+//        m00 = -left.x;

+//        m10 = -left.y;

+//        m20 = -left.z;

+//

+//        m01 = up.x;

+//        m11 = up.y;

+//        m21 = up.z;

+//

+//        m02 = -direction.x;

+//        m12 = -direction.y;

+//        m22 = -direction.z;

+//

+

+        Matrix4f transMatrix = vars.tempMat4;

+        transMatrix.loadIdentity();

+        transMatrix.m03 = -location.x;

+        transMatrix.m13 = -location.y;

+        transMatrix.m23 = -location.z;

+        this.multLocal(transMatrix);

+

+        vars.release();

+

+//        transMatrix.multLocal(this);

+

+//        set(transMatrix);

+    }

+

+    /**

+     * <code>get</code> retrieves the values of this object into

+     * a float array in row-major order.

+     * 

+     * @param matrix

+     *            the matrix to set the values into.

+     */

+    public void get(float[] matrix) {

+        get(matrix, true);

+    }

+

+    /**

+     * <code>set</code> retrieves the values of this object into

+     * a float array.

+     * 

+     * @param matrix

+     *            the matrix to set the values into.

+     * @param rowMajor

+     *            whether the outgoing data is in row or column major order.

+     */

+    public void get(float[] matrix, boolean rowMajor) {

+        if (matrix.length != 16) {

+            throw new IllegalArgumentException(

+                    "Array must be of size 16.");

+        }

+

+        if (rowMajor) {

+            matrix[0] = m00;

+            matrix[1] = m01;

+            matrix[2] = m02;

+            matrix[3] = m03;

+            matrix[4] = m10;

+            matrix[5] = m11;

+            matrix[6] = m12;

+            matrix[7] = m13;

+            matrix[8] = m20;

+            matrix[9] = m21;

+            matrix[10] = m22;

+            matrix[11] = m23;

+            matrix[12] = m30;

+            matrix[13] = m31;

+            matrix[14] = m32;

+            matrix[15] = m33;

+        } else {

+            matrix[0] = m00;

+            matrix[4] = m01;

+            matrix[8] = m02;

+            matrix[12] = m03;

+            matrix[1] = m10;

+            matrix[5] = m11;

+            matrix[9] = m12;

+            matrix[13] = m13;

+            matrix[2] = m20;

+            matrix[6] = m21;

+            matrix[10] = m22;

+            matrix[14] = m23;

+            matrix[3] = m30;

+            matrix[7] = m31;

+            matrix[11] = m32;

+            matrix[15] = m33;

+        }

+    }

+

+    /**

+     * <code>get</code> retrieves a value from the matrix at the given

+     * position. If the position is invalid a <code>JmeException</code> is

+     * thrown.

+     * 

+     * @param i

+     *            the row index.

+     * @param j

+     *            the colum index.

+     * @return the value at (i, j).

+     */

+    @SuppressWarnings("fallthrough")

+    public float get(int i, int j) {

+        switch (i) {

+            case 0:

+                switch (j) {

+                    case 0:

+                        return m00;

+                    case 1:

+                        return m01;

+                    case 2:

+                        return m02;

+                    case 3:

+                        return m03;

+                }

+            case 1:

+                switch (j) {

+                    case 0:

+                        return m10;

+                    case 1:

+                        return m11;

+                    case 2:

+                        return m12;

+                    case 3:

+                        return m13;

+                }

+            case 2:

+                switch (j) {

+                    case 0:

+                        return m20;

+                    case 1:

+                        return m21;

+                    case 2:

+                        return m22;

+                    case 3:

+                        return m23;

+                }

+            case 3:

+                switch (j) {

+                    case 0:

+                        return m30;

+                    case 1:

+                        return m31;

+                    case 2:

+                        return m32;

+                    case 3:

+                        return m33;

+                }

+        }

+

+        logger.warning("Invalid matrix index.");

+        throw new IllegalArgumentException("Invalid indices into matrix.");

+    }

+

+    /**

+     * <code>getColumn</code> returns one of three columns specified by the

+     * parameter. This column is returned as a float array of length 4.

+     * 

+     * @param i

+     *            the column to retrieve. Must be between 0 and 3.

+     * @return the column specified by the index.

+     */

+    public float[] getColumn(int i) {

+        return getColumn(i, null);

+    }

+

+    /**

+     * <code>getColumn</code> returns one of three columns specified by the

+     * parameter. This column is returned as a float[4].

+     * 

+     * @param i

+     *            the column to retrieve. Must be between 0 and 3.

+     * @param store

+     *            the float array to store the result in. if null, a new one

+     *            is created.

+     * @return the column specified by the index.

+     */

+    public float[] getColumn(int i, float[] store) {

+        if (store == null) {

+            store = new float[4];

+        }

+        switch (i) {

+            case 0:

+                store[0] = m00;

+                store[1] = m10;

+                store[2] = m20;

+                store[3] = m30;

+                break;

+            case 1:

+                store[0] = m01;

+                store[1] = m11;

+                store[2] = m21;

+                store[3] = m31;

+                break;

+            case 2:

+                store[0] = m02;

+                store[1] = m12;

+                store[2] = m22;

+                store[3] = m32;

+                break;

+            case 3:

+                store[0] = m03;

+                store[1] = m13;

+                store[2] = m23;

+                store[3] = m33;

+                break;

+            default:

+                logger.warning("Invalid column index.");

+                throw new IllegalArgumentException("Invalid column index. " + i);

+        }

+        return store;

+    }

+

+    /**

+     * 

+     * <code>setColumn</code> sets a particular column of this matrix to that

+     * represented by the provided vector.

+     * 

+     * @param i

+     *            the column to set.

+     * @param column

+     *            the data to set.

+     */

+    public void setColumn(int i, float[] column) {

+

+        if (column == null) {

+            logger.warning("Column is null. Ignoring.");

+            return;

+        }

+        switch (i) {

+            case 0:

+                m00 = column[0];

+                m10 = column[1];

+                m20 = column[2];

+                m30 = column[3];

+                break;

+            case 1:

+                m01 = column[0];

+                m11 = column[1];

+                m21 = column[2];

+                m31 = column[3];

+                break;

+            case 2:

+                m02 = column[0];

+                m12 = column[1];

+                m22 = column[2];

+                m32 = column[3];

+                break;

+            case 3:

+                m03 = column[0];

+                m13 = column[1];

+                m23 = column[2];

+                m33 = column[3];

+                break;

+            default:

+                logger.warning("Invalid column index.");

+                throw new IllegalArgumentException("Invalid column index. " + i);

+        }

+    }

+

+    /**

+     * <code>set</code> places a given value into the matrix at the given

+     * position. If the position is invalid a <code>JmeException</code> is

+     * thrown.

+     * 

+     * @param i

+     *            the row index.

+     * @param j

+     *            the colum index.

+     * @param value

+     *            the value for (i, j).

+     */

+    @SuppressWarnings("fallthrough")

+    public void set(int i, int j, float value) {

+        switch (i) {

+            case 0:

+                switch (j) {

+                    case 0:

+                        m00 = value;

+                        return;

+                    case 1:

+                        m01 = value;

+                        return;

+                    case 2:

+                        m02 = value;

+                        return;

+                    case 3:

+                        m03 = value;

+                        return;

+                }

+            case 1:

+                switch (j) {

+                    case 0:

+                        m10 = value;

+                        return;

+                    case 1:

+                        m11 = value;

+                        return;

+                    case 2:

+                        m12 = value;

+                        return;

+                    case 3:

+                        m13 = value;

+                        return;

+                }

+            case 2:

+                switch (j) {

+                    case 0:

+                        m20 = value;

+                        return;

+                    case 1:

+                        m21 = value;

+                        return;

+                    case 2:

+                        m22 = value;

+                        return;

+                    case 3:

+                        m23 = value;

+                        return;

+                }

+            case 3:

+                switch (j) {

+                    case 0:

+                        m30 = value;

+                        return;

+                    case 1:

+                        m31 = value;

+                        return;

+                    case 2:

+                        m32 = value;

+                        return;

+                    case 3:

+                        m33 = value;

+                        return;

+                }

+        }

+

+        logger.warning("Invalid matrix index.");

+        throw new IllegalArgumentException("Invalid indices into matrix.");

+    }

+

+    /**

+     * <code>set</code> sets the values of this matrix from an array of

+     * values.

+     * 

+     * @param matrix

+     *            the matrix to set the value to.

+     * @throws JmeException

+     *             if the array is not of size 16.

+     */

+    public void set(float[][] matrix) {

+        if (matrix.length != 4 || matrix[0].length != 4) {

+            throw new IllegalArgumentException(

+                    "Array must be of size 16.");

+        }

+

+        m00 = matrix[0][0];

+        m01 = matrix[0][1];

+        m02 = matrix[0][2];

+        m03 = matrix[0][3];

+        m10 = matrix[1][0];

+        m11 = matrix[1][1];

+        m12 = matrix[1][2];

+        m13 = matrix[1][3];

+        m20 = matrix[2][0];

+        m21 = matrix[2][1];

+        m22 = matrix[2][2];

+        m23 = matrix[2][3];

+        m30 = matrix[3][0];

+        m31 = matrix[3][1];

+        m32 = matrix[3][2];

+        m33 = matrix[3][3];

+    }

+

+    /**

+     * <code>set</code> sets the values of this matrix from another matrix.

+     *

+     * @param matrix

+     *            the matrix to read the value from.

+     */

+    public Matrix4f set(Matrix4f matrix) {

+        m00 = matrix.m00;

+        m01 = matrix.m01;

+        m02 = matrix.m02;

+        m03 = matrix.m03;

+        m10 = matrix.m10;

+        m11 = matrix.m11;

+        m12 = matrix.m12;

+        m13 = matrix.m13;

+        m20 = matrix.m20;

+        m21 = matrix.m21;

+        m22 = matrix.m22;

+        m23 = matrix.m23;

+        m30 = matrix.m30;

+        m31 = matrix.m31;

+        m32 = matrix.m32;

+        m33 = matrix.m33;

+        return this;

+    }

+

+    /**

+     * <code>set</code> sets the values of this matrix from an array of

+     * values assuming that the data is rowMajor order;

+     * 

+     * @param matrix

+     *            the matrix to set the value to.

+     */

+    public void set(float[] matrix) {

+        set(matrix, true);

+    }

+

+    /**

+     * <code>set</code> sets the values of this matrix from an array of

+     * values;

+     * 

+     * @param matrix

+     *            the matrix to set the value to.

+     * @param rowMajor

+     *            whether the incoming data is in row or column major order.

+     */

+    public void set(float[] matrix, boolean rowMajor) {

+        if (matrix.length != 16) {

+            throw new IllegalArgumentException(

+                    "Array must be of size 16.");

+        }

+

+        if (rowMajor) {

+            m00 = matrix[0];

+            m01 = matrix[1];

+            m02 = matrix[2];

+            m03 = matrix[3];

+            m10 = matrix[4];

+            m11 = matrix[5];

+            m12 = matrix[6];

+            m13 = matrix[7];

+            m20 = matrix[8];

+            m21 = matrix[9];

+            m22 = matrix[10];

+            m23 = matrix[11];

+            m30 = matrix[12];

+            m31 = matrix[13];

+            m32 = matrix[14];

+            m33 = matrix[15];

+        } else {

+            m00 = matrix[0];

+            m01 = matrix[4];

+            m02 = matrix[8];

+            m03 = matrix[12];

+            m10 = matrix[1];

+            m11 = matrix[5];

+            m12 = matrix[9];

+            m13 = matrix[13];

+            m20 = matrix[2];

+            m21 = matrix[6];

+            m22 = matrix[10];

+            m23 = matrix[14];

+            m30 = matrix[3];

+            m31 = matrix[7];

+            m32 = matrix[11];

+            m33 = matrix[15];

+        }

+    }

+

+    public Matrix4f transpose() {

+        float[] tmp = new float[16];

+        get(tmp, true);

+        Matrix4f mat = new Matrix4f(tmp);

+        return mat;

+    }

+

+    /**

+     * <code>transpose</code> locally transposes this Matrix.

+     * 

+     * @return this object for chaining.

+     */

+    public Matrix4f transposeLocal() {

+        float tmp = m01;

+        m01 = m10;

+        m10 = tmp;

+

+        tmp = m02;

+        m02 = m20;

+        m20 = tmp;

+

+        tmp = m03;

+        m03 = m30;

+        m30 = tmp;

+

+        tmp = m12;

+        m12 = m21;

+        m21 = tmp;

+

+        tmp = m13;

+        m13 = m31;

+        m31 = tmp;

+

+        tmp = m23;

+        m23 = m32;

+        m32 = tmp;

+

+        return this;

+    }

+

+    /**

+     * <code>toFloatBuffer</code> returns a FloatBuffer object that contains

+     * the matrix data.

+     * 

+     * @return matrix data as a FloatBuffer.

+     */

+    public FloatBuffer toFloatBuffer() {

+        return toFloatBuffer(false);

+    }

+

+    /**

+     * <code>toFloatBuffer</code> returns a FloatBuffer object that contains the

+     * matrix data.

+     * 

+     * @param columnMajor

+     *            if true, this buffer should be filled with column major data,

+     *            otherwise it will be filled row major.

+     * @return matrix data as a FloatBuffer. The position is set to 0 for

+     *         convenience.

+     */

+    public FloatBuffer toFloatBuffer(boolean columnMajor) {

+        FloatBuffer fb = BufferUtils.createFloatBuffer(16);

+        fillFloatBuffer(fb, columnMajor);

+        fb.rewind();

+        return fb;

+    }

+

+    /**

+     * <code>fillFloatBuffer</code> fills a FloatBuffer object with

+     * the matrix data.

+     * @param fb the buffer to fill, must be correct size

+     * @return matrix data as a FloatBuffer.

+     */

+    public FloatBuffer fillFloatBuffer(FloatBuffer fb) {

+        return fillFloatBuffer(fb, false);

+    }

+

+    /**

+     * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix

+     * data.

+     * 

+     * @param fb

+     *            the buffer to fill, starting at current position. Must have

+     *            room for 16 more floats.

+     * @param columnMajor

+     *            if true, this buffer should be filled with column major data,

+     *            otherwise it will be filled row major.

+     * @return matrix data as a FloatBuffer. (position is advanced by 16 and any

+     *         limit set is not changed).

+     */

+    public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) {

+//        if (columnMajor) {

+//            fb.put(m00).put(m10).put(m20).put(m30);

+//            fb.put(m01).put(m11).put(m21).put(m31);

+//            fb.put(m02).put(m12).put(m22).put(m32);

+//            fb.put(m03).put(m13).put(m23).put(m33);

+//        } else {

+//            fb.put(m00).put(m01).put(m02).put(m03);

+//            fb.put(m10).put(m11).put(m12).put(m13);

+//            fb.put(m20).put(m21).put(m22).put(m23);

+//            fb.put(m30).put(m31).put(m32).put(m33);

+//        }

+

+        TempVars vars = TempVars.get();

+

+

+        fillFloatArray(vars.matrixWrite, columnMajor);

+        fb.put(vars.matrixWrite, 0, 16);

+

+        vars.release();

+

+        return fb;

+    }

+

+    public void fillFloatArray(float[] f, boolean columnMajor) {

+        if (columnMajor) {

+            f[ 0] = m00;

+            f[ 1] = m10;

+            f[ 2] = m20;

+            f[ 3] = m30;

+            f[ 4] = m01;

+            f[ 5] = m11;

+            f[ 6] = m21;

+            f[ 7] = m31;

+            f[ 8] = m02;

+            f[ 9] = m12;

+            f[10] = m22;

+            f[11] = m32;

+            f[12] = m03;

+            f[13] = m13;

+            f[14] = m23;

+            f[15] = m33;

+        } else {

+            f[ 0] = m00;

+            f[ 1] = m01;

+            f[ 2] = m02;

+            f[ 3] = m03;

+            f[ 4] = m10;

+            f[ 5] = m11;

+            f[ 6] = m12;

+            f[ 7] = m13;

+            f[ 8] = m20;

+            f[ 9] = m21;

+            f[10] = m22;

+            f[11] = m23;

+            f[12] = m30;

+            f[13] = m31;

+            f[14] = m32;

+            f[15] = m33;

+        }

+    }

+

+    /**

+     * <code>readFloatBuffer</code> reads value for this matrix from a FloatBuffer.

+     * @param fb the buffer to read from, must be correct size

+     * @return this data as a FloatBuffer.

+     */

+    public Matrix4f readFloatBuffer(FloatBuffer fb) {

+        return readFloatBuffer(fb, false);

+    }

+

+    /**

+     * <code>readFloatBuffer</code> reads value for this matrix from a FloatBuffer.

+     * @param fb the buffer to read from, must be correct size

+     * @param columnMajor if true, this buffer should be filled with column

+     * 		major data, otherwise it will be filled row major.

+     * @return this data as a FloatBuffer.

+     */

+    public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) {

+

+        if (columnMajor) {

+            m00 = fb.get();

+            m10 = fb.get();

+            m20 = fb.get();

+            m30 = fb.get();

+            m01 = fb.get();

+            m11 = fb.get();

+            m21 = fb.get();

+            m31 = fb.get();

+            m02 = fb.get();

+            m12 = fb.get();

+            m22 = fb.get();

+            m32 = fb.get();

+            m03 = fb.get();

+            m13 = fb.get();

+            m23 = fb.get();

+            m33 = fb.get();

+        } else {

+            m00 = fb.get();

+            m01 = fb.get();

+            m02 = fb.get();

+            m03 = fb.get();

+            m10 = fb.get();

+            m11 = fb.get();

+            m12 = fb.get();

+            m13 = fb.get();

+            m20 = fb.get();

+            m21 = fb.get();

+            m22 = fb.get();

+            m23 = fb.get();

+            m30 = fb.get();

+            m31 = fb.get();

+            m32 = fb.get();

+            m33 = fb.get();

+        }

+        return this;

+    }

+

+    /**

+     * <code>loadIdentity</code> sets this matrix to the identity matrix,

+     * namely all zeros with ones along the diagonal.

+     *  

+     */

+    public void loadIdentity() {

+        m01 = m02 = m03 = 0.0f;

+        m10 = m12 = m13 = 0.0f;

+        m20 = m21 = m23 = 0.0f;

+        m30 = m31 = m32 = 0.0f;

+        m00 = m11 = m22 = m33 = 1.0f;

+    }

+

+    public void fromFrustum(float near, float far, float left, float right, float top, float bottom, boolean parallel) {

+        loadIdentity();

+        if (parallel) {

+            // scale

+            m00 = 2.0f / (right - left);

+            //m11 = 2.0f / (bottom - top);

+            m11 = 2.0f / (top - bottom);

+            m22 = -2.0f / (far - near);

+            m33 = 1f;

+

+            // translation

+            m03 = -(right + left) / (right - left);

+            //m31 = -(bottom + top) / (bottom - top);

+            m13 = -(top + bottom) / (top - bottom);

+            m23 = -(far + near) / (far - near);

+        } else {

+            m00 = (2.0f * near) / (right - left);

+            m11 = (2.0f * near) / (top - bottom);

+            m32 = -1.0f;

+            m33 = -0.0f;

+

+            // A

+            m02 = (right + left) / (right - left);

+

+            // B 

+            m12 = (top + bottom) / (top - bottom);

+

+            // C

+            m22 = -(far + near) / (far - near);

+

+            // D

+            m23 = -(2.0f * far * near) / (far - near);

+        }

+    }

+

+    /**

+     * <code>fromAngleAxis</code> sets this matrix4f to the values specified

+     * by an angle and an axis of rotation.  This method creates an object, so

+     * use fromAngleNormalAxis if your axis is already normalized.

+     * 

+     * @param angle

+     *            the angle to rotate (in radians).

+     * @param axis

+     *            the axis of rotation.

+     */

+    public void fromAngleAxis(float angle, Vector3f axis) {

+        Vector3f normAxis = axis.normalize();

+        fromAngleNormalAxis(angle, normAxis);

+    }

+

+    /**

+     * <code>fromAngleNormalAxis</code> sets this matrix4f to the values

+     * specified by an angle and a normalized axis of rotation.

+     * 

+     * @param angle

+     *            the angle to rotate (in radians).

+     * @param axis

+     *            the axis of rotation (already normalized).

+     */

+    public void fromAngleNormalAxis(float angle, Vector3f axis) {

+        zero();

+        m33 = 1;

+

+        float fCos = FastMath.cos(angle);

+        float fSin = FastMath.sin(angle);

+        float fOneMinusCos = ((float) 1.0) - fCos;

+        float fX2 = axis.x * axis.x;

+        float fY2 = axis.y * axis.y;

+        float fZ2 = axis.z * axis.z;

+        float fXYM = axis.x * axis.y * fOneMinusCos;

+        float fXZM = axis.x * axis.z * fOneMinusCos;

+        float fYZM = axis.y * axis.z * fOneMinusCos;

+        float fXSin = axis.x * fSin;

+        float fYSin = axis.y * fSin;

+        float fZSin = axis.z * fSin;

+

+        m00 = fX2 * fOneMinusCos + fCos;

+        m01 = fXYM - fZSin;

+        m02 = fXZM + fYSin;

+        m10 = fXYM + fZSin;

+        m11 = fY2 * fOneMinusCos + fCos;

+        m12 = fYZM - fXSin;

+        m20 = fXZM - fYSin;

+        m21 = fYZM + fXSin;

+        m22 = fZ2 * fOneMinusCos + fCos;

+    }

+

+    /**

+     * <code>mult</code> multiplies this matrix by a scalar.

+     * 

+     * @param scalar

+     *            the scalar to multiply this matrix by.

+     */

+    public void multLocal(float scalar) {

+        m00 *= scalar;

+        m01 *= scalar;

+        m02 *= scalar;

+        m03 *= scalar;

+        m10 *= scalar;

+        m11 *= scalar;

+        m12 *= scalar;

+        m13 *= scalar;

+        m20 *= scalar;

+        m21 *= scalar;

+        m22 *= scalar;

+        m23 *= scalar;

+        m30 *= scalar;

+        m31 *= scalar;

+        m32 *= scalar;

+        m33 *= scalar;

+    }

+

+    public Matrix4f mult(float scalar) {

+        Matrix4f out = new Matrix4f();

+        out.set(this);

+        out.multLocal(scalar);

+        return out;

+    }

+

+    public Matrix4f mult(float scalar, Matrix4f store) {

+        store.set(this);

+        store.multLocal(scalar);

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies this matrix with another matrix. The

+     * result matrix will then be returned. This matrix will be on the left hand

+     * side, while the parameter matrix will be on the right.

+     * 

+     * @param in2

+     *            the matrix to multiply this matrix by.

+     * @return the resultant matrix

+     */

+    public Matrix4f mult(Matrix4f in2) {

+        return mult(in2, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies this matrix with another matrix. The

+     * result matrix will then be returned. This matrix will be on the left hand

+     * side, while the parameter matrix will be on the right.

+     * 

+     * @param in2

+     *            the matrix to multiply this matrix by.

+     * @param store

+     *            where to store the result. It is safe for in2 and store to be

+     *            the same object.

+     * @return the resultant matrix

+     */

+    public Matrix4f mult(Matrix4f in2, Matrix4f store) {

+        if (store == null) {

+            store = new Matrix4f();

+        }

+

+        float temp00, temp01, temp02, temp03;

+        float temp10, temp11, temp12, temp13;

+        float temp20, temp21, temp22, temp23;

+        float temp30, temp31, temp32, temp33;

+

+        temp00 = m00 * in2.m00

+                + m01 * in2.m10

+                + m02 * in2.m20

+                + m03 * in2.m30;

+        temp01 = m00 * in2.m01

+                + m01 * in2.m11

+                + m02 * in2.m21

+                + m03 * in2.m31;

+        temp02 = m00 * in2.m02

+                + m01 * in2.m12

+                + m02 * in2.m22

+                + m03 * in2.m32;

+        temp03 = m00 * in2.m03

+                + m01 * in2.m13

+                + m02 * in2.m23

+                + m03 * in2.m33;

+

+        temp10 = m10 * in2.m00

+                + m11 * in2.m10

+                + m12 * in2.m20

+                + m13 * in2.m30;

+        temp11 = m10 * in2.m01

+                + m11 * in2.m11

+                + m12 * in2.m21

+                + m13 * in2.m31;

+        temp12 = m10 * in2.m02

+                + m11 * in2.m12

+                + m12 * in2.m22

+                + m13 * in2.m32;

+        temp13 = m10 * in2.m03

+                + m11 * in2.m13

+                + m12 * in2.m23

+                + m13 * in2.m33;

+

+        temp20 = m20 * in2.m00

+                + m21 * in2.m10

+                + m22 * in2.m20

+                + m23 * in2.m30;

+        temp21 = m20 * in2.m01

+                + m21 * in2.m11

+                + m22 * in2.m21

+                + m23 * in2.m31;

+        temp22 = m20 * in2.m02

+                + m21 * in2.m12

+                + m22 * in2.m22

+                + m23 * in2.m32;

+        temp23 = m20 * in2.m03

+                + m21 * in2.m13

+                + m22 * in2.m23

+                + m23 * in2.m33;

+

+        temp30 = m30 * in2.m00

+                + m31 * in2.m10

+                + m32 * in2.m20

+                + m33 * in2.m30;

+        temp31 = m30 * in2.m01

+                + m31 * in2.m11

+                + m32 * in2.m21

+                + m33 * in2.m31;

+        temp32 = m30 * in2.m02

+                + m31 * in2.m12

+                + m32 * in2.m22

+                + m33 * in2.m32;

+        temp33 = m30 * in2.m03

+                + m31 * in2.m13

+                + m32 * in2.m23

+                + m33 * in2.m33;

+

+        store.m00 = temp00;

+        store.m01 = temp01;

+        store.m02 = temp02;

+        store.m03 = temp03;

+        store.m10 = temp10;

+        store.m11 = temp11;

+        store.m12 = temp12;

+        store.m13 = temp13;

+        store.m20 = temp20;

+        store.m21 = temp21;

+        store.m22 = temp22;

+        store.m23 = temp23;

+        store.m30 = temp30;

+        store.m31 = temp31;

+        store.m32 = temp32;

+        store.m33 = temp33;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies this matrix with another matrix. The

+     * results are stored internally and a handle to this matrix will 

+     * then be returned. This matrix will be on the left hand

+     * side, while the parameter matrix will be on the right.

+     * 

+     * @param in2

+     *            the matrix to multiply this matrix by.

+     * @return the resultant matrix

+     */

+    public Matrix4f multLocal(Matrix4f in2) {

+        return mult(in2, this);

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix. The

+     * resulting vector is returned as a new Vector3f.

+     * 

+     * @param vec

+     *            vec to multiply against.

+     * @return the rotated vector.

+     */

+    public Vector3f mult(Vector3f vec) {

+        return mult(vec, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix and adds

+     * translation. The resulting vector is returned.

+     * 

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in. Created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector3f mult(Vector3f vec, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+        store.x = m00 * vx + m01 * vy + m02 * vz + m03;

+        store.y = m10 * vx + m11 * vy + m12 * vz + m13;

+        store.z = m20 * vx + m21 * vy + m22 * vz + m23;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies a <code>Vector4f</code> about a rotation

+     * matrix. The resulting vector is returned as a new <code>Vector4f</code>.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @return the rotated vector.

+     */

+    public Vector4f mult(Vector4f vec) {

+        return mult(vec, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies a <code>Vector4f</code> about a rotation

+     * matrix. The resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in. Created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector4f mult(Vector4f vec, Vector4f store) {

+        if (null == vec) {

+            logger.info("Source vector is null, null result returned.");

+            return null;

+        }

+        if (store == null) {

+            store = new Vector4f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w;

+        store.x = m00 * vx + m01 * vy + m02 * vz + m03 * vw;

+        store.y = m10 * vx + m11 * vy + m12 * vz + m13 * vw;

+        store.z = m20 * vx + m21 * vy + m22 * vz + m23 * vw;

+        store.w = m30 * vx + m31 * vy + m32 * vz + m33 * vw;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix. The

+     * resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * 

+     * @return the rotated vector.

+     */

+    public Vector4f multAcross(Vector4f vec) {

+        return multAcross(vec, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix. The

+     * resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in.  created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector4f multAcross(Vector4f vec, Vector4f store) {

+        if (null == vec) {

+            logger.info("Source vector is null, null result returned.");

+            return null;

+        }

+        if (store == null) {

+            store = new Vector4f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w;

+        store.x = m00 * vx + m10 * vy + m20 * vz + m30 * vw;

+        store.y = m01 * vx + m11 * vy + m21 * vz + m31 * vw;

+        store.z = m02 * vx + m12 * vy + m22 * vz + m32 * vw;

+        store.z = m03 * vx + m13 * vy + m23 * vz + m33 * vw;

+

+        return store;

+    }

+

+    /**

+     * <code>multNormal</code> multiplies a vector about a rotation matrix, but

+     * does not add translation. The resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in. Created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector3f multNormal(Vector3f vec, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+        store.x = m00 * vx + m01 * vy + m02 * vz;

+        store.y = m10 * vx + m11 * vy + m12 * vz;

+        store.z = m20 * vx + m21 * vy + m22 * vz;

+

+        return store;

+    }

+

+    /**

+     * <code>multNormal</code> multiplies a vector about a rotation matrix, but

+     * does not add translation. The resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in. Created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector3f multNormalAcross(Vector3f vec, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+        store.x = m00 * vx + m10 * vy + m20 * vz;

+        store.y = m01 * vx + m11 * vy + m21 * vz;

+        store.z = m02 * vx + m12 * vy + m22 * vz;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix and adds

+     * translation. The w value is returned as a result of

+     * multiplying the last column of the matrix by 1.0

+     * 

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in. 

+     * @return the W value

+     */

+    public float multProj(Vector3f vec, Vector3f store) {

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+        store.x = m00 * vx + m01 * vy + m02 * vz + m03;

+        store.y = m10 * vx + m11 * vy + m12 * vz + m13;

+        store.z = m20 * vx + m21 * vy + m22 * vz + m23;

+        return m30 * vx + m31 * vy + m32 * vz + m33;

+    }

+

+    /**

+     * <code>mult</code> multiplies a vector about a rotation matrix. The

+     * resulting vector is returned.

+     * 

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a vector to store the result in.  created if null is passed.

+     * @return the rotated vector.

+     */

+    public Vector3f multAcross(Vector3f vec, Vector3f store) {

+        if (null == vec) {

+            logger.info("Source vector is null, null result returned.");

+            return null;

+        }

+        if (store == null) {

+            store = new Vector3f();

+        }

+

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+        store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1;

+        store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1;

+        store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies a quaternion about a matrix. The

+     * resulting vector is returned.

+     *

+     * @param vec

+     *            vec to multiply against.

+     * @param store

+     *            a quaternion to store the result in.  created if null is passed.

+     * @return store = this * vec

+     */

+    public Quaternion mult(Quaternion vec, Quaternion store) {

+

+        if (null == vec) {

+            logger.warning("Source vector is null, null result returned.");

+            return null;

+        }

+        if (store == null) {

+            store = new Quaternion();

+        }

+

+        float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w;

+        float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w;

+        float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w;

+        float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w;

+        store.x = x;

+        store.y = y;

+        store.z = z;

+        store.w = w;

+

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies an array of 4 floats against this rotation 

+     * matrix. The results are stored directly in the array. (vec4f x mat4f)

+     * 

+     * @param vec4f

+     *            float array (size 4) to multiply against the matrix.

+     * @return the vec4f for chaining.

+     */

+    public float[] mult(float[] vec4f) {

+        if (null == vec4f || vec4f.length != 4) {

+            logger.warning("invalid array given, must be nonnull and length 4");

+            return null;

+        }

+

+        float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];

+

+        vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w;

+        vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w;

+        vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w;

+        vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w;

+

+        return vec4f;

+    }

+

+    /**

+     * <code>mult</code> multiplies an array of 4 floats against this rotation 

+     * matrix. The results are stored directly in the array. (vec4f x mat4f)

+     * 

+     * @param vec4f

+     *            float array (size 4) to multiply against the matrix.

+     * @return the vec4f for chaining.

+     */

+    public float[] multAcross(float[] vec4f) {

+        if (null == vec4f || vec4f.length != 4) {

+            logger.warning("invalid array given, must be nonnull and length 4");

+            return null;

+        }

+

+        float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];

+

+        vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w;

+        vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w;

+        vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w;

+        vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w;

+

+        return vec4f;

+    }

+

+    /**

+     * Inverts this matrix as a new Matrix4f.

+     * 

+     * @return The new inverse matrix

+     */

+    public Matrix4f invert() {

+        return invert(null);

+    }

+

+    /**

+     * Inverts this matrix and stores it in the given store.

+     * 

+     * @return The store

+     */

+    public Matrix4f invert(Matrix4f store) {

+        if (store == null) {

+            store = new Matrix4f();

+        }

+

+        float fA0 = m00 * m11 - m01 * m10;

+        float fA1 = m00 * m12 - m02 * m10;

+        float fA2 = m00 * m13 - m03 * m10;

+        float fA3 = m01 * m12 - m02 * m11;

+        float fA4 = m01 * m13 - m03 * m11;

+        float fA5 = m02 * m13 - m03 * m12;

+        float fB0 = m20 * m31 - m21 * m30;

+        float fB1 = m20 * m32 - m22 * m30;

+        float fB2 = m20 * m33 - m23 * m30;

+        float fB3 = m21 * m32 - m22 * m31;

+        float fB4 = m21 * m33 - m23 * m31;

+        float fB5 = m22 * m33 - m23 * m32;

+        float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;

+

+        if (FastMath.abs(fDet) <= 0f) {

+            throw new ArithmeticException("This matrix cannot be inverted");

+        }

+

+        store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;

+        store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;

+        store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;

+        store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;

+        store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;

+        store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;

+        store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;

+        store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;

+        store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;

+        store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;

+        store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;

+        store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;

+        store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;

+        store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;

+        store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;

+        store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;

+

+        float fInvDet = 1.0f / fDet;

+        store.multLocal(fInvDet);

+

+        return store;

+    }

+

+    /**

+     * Inverts this matrix locally.

+     * 

+     * @return this

+     */

+    public Matrix4f invertLocal() {

+

+        float fA0 = m00 * m11 - m01 * m10;

+        float fA1 = m00 * m12 - m02 * m10;

+        float fA2 = m00 * m13 - m03 * m10;

+        float fA3 = m01 * m12 - m02 * m11;

+        float fA4 = m01 * m13 - m03 * m11;

+        float fA5 = m02 * m13 - m03 * m12;

+        float fB0 = m20 * m31 - m21 * m30;

+        float fB1 = m20 * m32 - m22 * m30;

+        float fB2 = m20 * m33 - m23 * m30;

+        float fB3 = m21 * m32 - m22 * m31;

+        float fB4 = m21 * m33 - m23 * m31;

+        float fB5 = m22 * m33 - m23 * m32;

+        float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;

+

+        if (FastMath.abs(fDet) <= 0f) {

+            return zero();

+        }

+

+        float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;

+        float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;

+        float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;

+        float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;

+        float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;

+        float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;

+        float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;

+        float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;

+        float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;

+        float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;

+        float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;

+        float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;

+        float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;

+        float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;

+        float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;

+        float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;

+

+        m00 = f00;

+        m01 = f01;

+        m02 = f02;

+        m03 = f03;

+        m10 = f10;

+        m11 = f11;

+        m12 = f12;

+        m13 = f13;

+        m20 = f20;

+        m21 = f21;

+        m22 = f22;

+        m23 = f23;

+        m30 = f30;

+        m31 = f31;

+        m32 = f32;

+        m33 = f33;

+

+        float fInvDet = 1.0f / fDet;

+        multLocal(fInvDet);

+

+        return this;

+    }

+

+    /**

+     * Returns a new matrix representing the adjoint of this matrix.

+     * 

+     * @return The adjoint matrix

+     */

+    public Matrix4f adjoint() {

+        return adjoint(null);

+    }

+

+    public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) {

+        // Ordering:

+        //    1. Scale

+        //    2. Rotate

+        //    3. Translate

+

+        // Set up final matrix with scale, rotation and translation

+        m00 = scale.x * rotMat.m00;

+        m01 = scale.y * rotMat.m01;

+        m02 = scale.z * rotMat.m02;

+        m03 = position.x;

+        m10 = scale.x * rotMat.m10;

+        m11 = scale.y * rotMat.m11;

+        m12 = scale.z * rotMat.m12;

+        m13 = position.y;

+        m20 = scale.x * rotMat.m20;

+        m21 = scale.y * rotMat.m21;

+        m22 = scale.z * rotMat.m22;

+        m23 = position.z;

+

+        // No projection term

+        m30 = 0;

+        m31 = 0;

+        m32 = 0;

+        m33 = 1;

+    }

+

+    /**

+     * Places the adjoint of this matrix in store (creates store if null.)

+     * 

+     * @param store

+     *            The matrix to store the result in.  If null, a new matrix is created.

+     * @return store

+     */

+    public Matrix4f adjoint(Matrix4f store) {

+        if (store == null) {

+            store = new Matrix4f();

+        }

+

+        float fA0 = m00 * m11 - m01 * m10;

+        float fA1 = m00 * m12 - m02 * m10;

+        float fA2 = m00 * m13 - m03 * m10;

+        float fA3 = m01 * m12 - m02 * m11;

+        float fA4 = m01 * m13 - m03 * m11;

+        float fA5 = m02 * m13 - m03 * m12;

+        float fB0 = m20 * m31 - m21 * m30;

+        float fB1 = m20 * m32 - m22 * m30;

+        float fB2 = m20 * m33 - m23 * m30;

+        float fB3 = m21 * m32 - m22 * m31;

+        float fB4 = m21 * m33 - m23 * m31;

+        float fB5 = m22 * m33 - m23 * m32;

+

+        store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;

+        store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;

+        store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;

+        store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;

+        store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;

+        store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;

+        store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;

+        store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;

+        store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;

+        store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;

+        store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;

+        store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;

+        store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;

+        store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;

+        store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;

+        store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;

+

+        return store;

+    }

+

+    /**

+     * <code>determinant</code> generates the determinate of this matrix.

+     * 

+     * @return the determinate

+     */

+    public float determinant() {

+        float fA0 = m00 * m11 - m01 * m10;

+        float fA1 = m00 * m12 - m02 * m10;

+        float fA2 = m00 * m13 - m03 * m10;

+        float fA3 = m01 * m12 - m02 * m11;

+        float fA4 = m01 * m13 - m03 * m11;

+        float fA5 = m02 * m13 - m03 * m12;

+        float fB0 = m20 * m31 - m21 * m30;

+        float fB1 = m20 * m32 - m22 * m30;

+        float fB2 = m20 * m33 - m23 * m30;

+        float fB3 = m21 * m32 - m22 * m31;

+        float fB4 = m21 * m33 - m23 * m31;

+        float fB5 = m22 * m33 - m23 * m32;

+        float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;

+        return fDet;

+    }

+

+    /**

+     * Sets all of the values in this matrix to zero.

+     * 

+     * @return this matrix

+     */

+    public Matrix4f zero() {

+        m00 = m01 = m02 = m03 = 0.0f;

+        m10 = m11 = m12 = m13 = 0.0f;

+        m20 = m21 = m22 = m23 = 0.0f;

+        m30 = m31 = m32 = m33 = 0.0f;

+        return this;

+    }

+

+    public Matrix4f add(Matrix4f mat) {

+        Matrix4f result = new Matrix4f();

+        result.m00 = this.m00 + mat.m00;

+        result.m01 = this.m01 + mat.m01;

+        result.m02 = this.m02 + mat.m02;

+        result.m03 = this.m03 + mat.m03;

+        result.m10 = this.m10 + mat.m10;

+        result.m11 = this.m11 + mat.m11;

+        result.m12 = this.m12 + mat.m12;

+        result.m13 = this.m13 + mat.m13;

+        result.m20 = this.m20 + mat.m20;

+        result.m21 = this.m21 + mat.m21;

+        result.m22 = this.m22 + mat.m22;

+        result.m23 = this.m23 + mat.m23;

+        result.m30 = this.m30 + mat.m30;

+        result.m31 = this.m31 + mat.m31;

+        result.m32 = this.m32 + mat.m32;

+        result.m33 = this.m33 + mat.m33;

+        return result;

+    }

+

+    /**

+     * <code>add</code> adds the values of a parameter matrix to this matrix.

+     * 

+     * @param mat

+     *            the matrix to add to this.

+     */

+    public void addLocal(Matrix4f mat) {

+        m00 += mat.m00;

+        m01 += mat.m01;

+        m02 += mat.m02;

+        m03 += mat.m03;

+        m10 += mat.m10;

+        m11 += mat.m11;

+        m12 += mat.m12;

+        m13 += mat.m13;

+        m20 += mat.m20;

+        m21 += mat.m21;

+        m22 += mat.m22;

+        m23 += mat.m23;

+        m30 += mat.m30;

+        m31 += mat.m31;

+        m32 += mat.m32;

+        m33 += mat.m33;

+    }

+

+    public Vector3f toTranslationVector() {

+        return new Vector3f(m03, m13, m23);

+    }

+

+    public void toTranslationVector(Vector3f vector) {

+        vector.set(m03, m13, m23);

+    }

+

+    public Quaternion toRotationQuat() {

+        Quaternion quat = new Quaternion();

+        quat.fromRotationMatrix(toRotationMatrix());

+        return quat;

+    }

+

+    public void toRotationQuat(Quaternion q) {

+        q.fromRotationMatrix(toRotationMatrix());

+    }

+

+    public Matrix3f toRotationMatrix() {

+        return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22);

+

+    }

+

+    public void toRotationMatrix(Matrix3f mat) {

+        mat.m00 = m00;

+        mat.m01 = m01;

+        mat.m02 = m02;

+        mat.m10 = m10;

+        mat.m11 = m11;

+        mat.m12 = m12;

+        mat.m20 = m20;

+        mat.m21 = m21;

+        mat.m22 = m22;

+

+    }

+

+    public void setScale(float x, float y, float z) {

+        m00 *= x;

+        m11 *= y;

+        m22 *= z;

+    }

+

+    public void setScale(Vector3f scale) {

+        m00 *= scale.x;

+        m11 *= scale.y;

+        m22 *= scale.z;

+    }

+

+    /**

+     * <code>setTranslation</code> will set the matrix's translation values.

+     * 

+     * @param translation

+     *            the new values for the translation.

+     * @throws JmeException

+     *             if translation is not size 3.

+     */

+    public void setTranslation(float[] translation) {

+        if (translation.length != 3) {

+            throw new IllegalArgumentException(

+                    "Translation size must be 3.");

+        }

+        m03 = translation[0];

+        m13 = translation[1];

+        m23 = translation[2];

+    }

+

+    /**

+     * <code>setTranslation</code> will set the matrix's translation values.

+     * 

+     * @param x

+     *            value of the translation on the x axis

+     * @param y

+     *            value of the translation on the y axis

+     * @param z

+     *            value of the translation on the z axis

+     */

+    public void setTranslation(float x, float y, float z) {

+        m03 = x;

+        m13 = y;

+        m23 = z;

+    }

+

+    /**

+     * <code>setTranslation</code> will set the matrix's translation values.

+     *

+     * @param translation

+     *            the new values for the translation.

+     */

+    public void setTranslation(Vector3f translation) {

+        m03 = translation.x;

+        m13 = translation.y;

+        m23 = translation.z;

+    }

+

+    /**

+     * <code>setInverseTranslation</code> will set the matrix's inverse

+     * translation values.

+     * 

+     * @param translation

+     *            the new values for the inverse translation.

+     * @throws JmeException

+     *             if translation is not size 3.

+     */

+    public void setInverseTranslation(float[] translation) {

+        if (translation.length != 3) {

+            throw new IllegalArgumentException(

+                    "Translation size must be 3.");

+        }

+        m03 = -translation[0];

+        m13 = -translation[1];

+        m23 = -translation[2];

+    }

+

+    /**

+     * <code>angleRotation</code> sets this matrix to that of a rotation about

+     * three axes (x, y, z). Where each axis has a specified rotation in

+     * degrees. These rotations are expressed in a single <code>Vector3f</code>

+     * object.

+     * 

+     * @param angles

+     *            the angles to rotate.

+     */

+    public void angleRotation(Vector3f angles) {

+        float angle;

+        float sr, sp, sy, cr, cp, cy;

+

+        angle = (angles.z * FastMath.DEG_TO_RAD);

+        sy = FastMath.sin(angle);

+        cy = FastMath.cos(angle);

+        angle = (angles.y * FastMath.DEG_TO_RAD);

+        sp = FastMath.sin(angle);

+        cp = FastMath.cos(angle);

+        angle = (angles.x * FastMath.DEG_TO_RAD);

+        sr = FastMath.sin(angle);

+        cr = FastMath.cos(angle);

+

+        // matrix = (Z * Y) * X

+        m00 = cp * cy;

+        m10 = cp * sy;

+        m20 = -sp;

+        m01 = sr * sp * cy + cr * -sy;

+        m11 = sr * sp * sy + cr * cy;

+        m21 = sr * cp;

+        m02 = (cr * sp * cy + -sr * -sy);

+        m12 = (cr * sp * sy + -sr * cy);

+        m22 = cr * cp;

+        m03 = 0.0f;

+        m13 = 0.0f;

+        m23 = 0.0f;

+    }

+

+    /**

+     * <code>setRotationQuaternion</code> builds a rotation from a

+     * <code>Quaternion</code>.

+     * 

+     * @param quat

+     *            the quaternion to build the rotation from.

+     * @throws NullPointerException

+     *             if quat is null.

+     */

+    public void setRotationQuaternion(Quaternion quat) {

+        quat.toRotationMatrix(this);

+    }

+

+    /**

+     * <code>setInverseRotationRadians</code> builds an inverted rotation from

+     * Euler angles that are in radians.

+     * 

+     * @param angles

+     *            the Euler angles in radians.

+     * @throws JmeException

+     *             if angles is not size 3.

+     */

+    public void setInverseRotationRadians(float[] angles) {

+        if (angles.length != 3) {

+            throw new IllegalArgumentException(

+                    "Angles must be of size 3.");

+        }

+        double cr = FastMath.cos(angles[0]);

+        double sr = FastMath.sin(angles[0]);

+        double cp = FastMath.cos(angles[1]);

+        double sp = FastMath.sin(angles[1]);

+        double cy = FastMath.cos(angles[2]);

+        double sy = FastMath.sin(angles[2]);

+

+        m00 = (float) (cp * cy);

+        m10 = (float) (cp * sy);

+        m20 = (float) (-sp);

+

+        double srsp = sr * sp;

+        double crsp = cr * sp;

+

+        m01 = (float) (srsp * cy - cr * sy);

+        m11 = (float) (srsp * sy + cr * cy);

+        m21 = (float) (sr * cp);

+

+        m02 = (float) (crsp * cy + sr * sy);

+        m12 = (float) (crsp * sy - sr * cy);

+        m22 = (float) (cr * cp);

+    }

+

+    /**

+     * <code>setInverseRotationDegrees</code> builds an inverted rotation from

+     * Euler angles that are in degrees.

+     * 

+     * @param angles

+     *            the Euler angles in degrees.

+     * @throws JmeException

+     *             if angles is not size 3.

+     */

+    public void setInverseRotationDegrees(float[] angles) {

+        if (angles.length != 3) {

+            throw new IllegalArgumentException(

+                    "Angles must be of size 3.");

+        }

+        float vec[] = new float[3];

+        vec[0] = (angles[0] * FastMath.RAD_TO_DEG);

+        vec[1] = (angles[1] * FastMath.RAD_TO_DEG);

+        vec[2] = (angles[2] * FastMath.RAD_TO_DEG);

+        setInverseRotationRadians(vec);

+    }

+

+    /**

+     * 

+     * <code>inverseTranslateVect</code> translates a given Vector3f by the

+     * translation part of this matrix.

+     * 

+     * @param vec

+     *            the Vector3f data to be translated.

+     * @throws JmeException

+     *             if the size of the Vector3f is not 3.

+     */

+    public void inverseTranslateVect(float[] vec) {

+        if (vec.length != 3) {

+            throw new IllegalArgumentException(

+                    "vec must be of size 3.");

+        }

+

+        vec[0] = vec[0] - m03;

+        vec[1] = vec[1] - m13;

+        vec[2] = vec[2] - m23;

+    }

+

+    /**

+     * 

+     * <code>inverseTranslateVect</code> translates a given Vector3f by the

+     * translation part of this matrix.

+     * 

+     * @param data

+     *            the Vector3f to be translated.

+     * @throws JmeException

+     *             if the size of the Vector3f is not 3.

+     */

+    public void inverseTranslateVect(Vector3f data) {

+        data.x -= m03;

+        data.y -= m13;

+        data.z -= m23;

+    }

+

+    /**

+     * 

+     * <code>inverseTranslateVect</code> translates a given Vector3f by the

+     * translation part of this matrix.

+     * 

+     * @param data

+     *            the Vector3f to be translated.

+     * @throws JmeException

+     *             if the size of the Vector3f is not 3.

+     */

+    public void translateVect(Vector3f data) {

+        data.x += m03;

+        data.y += m13;

+        data.z += m23;

+    }

+

+    /**

+     * 

+     * <code>inverseRotateVect</code> rotates a given Vector3f by the rotation

+     * part of this matrix.

+     * 

+     * @param vec

+     *            the Vector3f to be rotated.

+     */

+    public void inverseRotateVect(Vector3f vec) {

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+

+        vec.x = vx * m00 + vy * m10 + vz * m20;

+        vec.y = vx * m01 + vy * m11 + vz * m21;

+        vec.z = vx * m02 + vy * m12 + vz * m22;

+    }

+

+    public void rotateVect(Vector3f vec) {

+        float vx = vec.x, vy = vec.y, vz = vec.z;

+

+        vec.x = vx * m00 + vy * m01 + vz * m02;

+        vec.y = vx * m10 + vy * m11 + vz * m12;

+        vec.z = vx * m20 + vy * m21 + vz * m22;

+    }

+

+    /**

+     * <code>toString</code> returns the string representation of this object.

+     * It is in a format of a 4x4 matrix. For example, an identity matrix would

+     * be represented by the following string. com.jme.math.Matrix3f <br>[<br>

+     * 1.0  0.0  0.0  0.0 <br>

+     * 0.0  1.0  0.0  0.0 <br>

+     * 0.0  0.0  1.0  0.0 <br>

+     * 0.0  0.0  0.0  1.0 <br>]<br>

+     * 

+     * @return the string representation of this object.

+     */

+    @Override

+    public String toString() {

+        StringBuilder result = new StringBuilder("Matrix4f\n[\n");

+        result.append(" ");

+        result.append(m00);

+        result.append("  ");

+        result.append(m01);

+        result.append("  ");

+        result.append(m02);

+        result.append("  ");

+        result.append(m03);

+        result.append(" \n");

+        result.append(" ");

+        result.append(m10);

+        result.append("  ");

+        result.append(m11);

+        result.append("  ");

+        result.append(m12);

+        result.append("  ");

+        result.append(m13);

+        result.append(" \n");

+        result.append(" ");

+        result.append(m20);

+        result.append("  ");

+        result.append(m21);

+        result.append("  ");

+        result.append(m22);

+        result.append("  ");

+        result.append(m23);

+        result.append(" \n");

+        result.append(" ");

+        result.append(m30);

+        result.append("  ");

+        result.append(m31);

+        result.append("  ");

+        result.append(m32);

+        result.append("  ");

+        result.append(m33);

+        result.append(" \n]");

+        return result.toString();

+    }

+

+    /**

+     * 

+     * <code>hashCode</code> returns the hash code value as an integer and is

+     * supported for the benefit of hashing based collection classes such as

+     * Hashtable, HashMap, HashSet etc.

+     * 

+     * @return the hashcode for this instance of Matrix4f.

+     * @see java.lang.Object#hashCode()

+     */

+    @Override

+    public int hashCode() {

+        int hash = 37;

+        hash = 37 * hash + Float.floatToIntBits(m00);

+        hash = 37 * hash + Float.floatToIntBits(m01);

+        hash = 37 * hash + Float.floatToIntBits(m02);

+        hash = 37 * hash + Float.floatToIntBits(m03);

+

+        hash = 37 * hash + Float.floatToIntBits(m10);

+        hash = 37 * hash + Float.floatToIntBits(m11);

+        hash = 37 * hash + Float.floatToIntBits(m12);

+        hash = 37 * hash + Float.floatToIntBits(m13);

+

+        hash = 37 * hash + Float.floatToIntBits(m20);

+        hash = 37 * hash + Float.floatToIntBits(m21);

+        hash = 37 * hash + Float.floatToIntBits(m22);

+        hash = 37 * hash + Float.floatToIntBits(m23);

+

+        hash = 37 * hash + Float.floatToIntBits(m30);

+        hash = 37 * hash + Float.floatToIntBits(m31);

+        hash = 37 * hash + Float.floatToIntBits(m32);

+        hash = 37 * hash + Float.floatToIntBits(m33);

+

+        return hash;

+    }

+

+    /**

+     * are these two matrices the same? they are is they both have the same mXX values.

+     *

+     * @param o

+     *            the object to compare for equality

+     * @return true if they are equal

+     */

+    @Override

+    public boolean equals(Object o) {

+        if (!(o instanceof Matrix4f) || o == null) {

+            return false;

+        }

+

+        if (this == o) {

+            return true;

+        }

+

+        Matrix4f comp = (Matrix4f) o;

+        if (Float.compare(m00, comp.m00) != 0) {

+            return false;

+        }

+        if (Float.compare(m01, comp.m01) != 0) {

+            return false;

+        }

+        if (Float.compare(m02, comp.m02) != 0) {

+            return false;

+        }

+        if (Float.compare(m03, comp.m03) != 0) {

+            return false;

+        }

+

+        if (Float.compare(m10, comp.m10) != 0) {

+            return false;

+        }

+        if (Float.compare(m11, comp.m11) != 0) {

+            return false;

+        }

+        if (Float.compare(m12, comp.m12) != 0) {

+            return false;

+        }

+        if (Float.compare(m13, comp.m13) != 0) {

+            return false;

+        }

+

+        if (Float.compare(m20, comp.m20) != 0) {

+            return false;

+        }

+        if (Float.compare(m21, comp.m21) != 0) {

+            return false;

+        }

+        if (Float.compare(m22, comp.m22) != 0) {

+            return false;

+        }

+        if (Float.compare(m23, comp.m23) != 0) {

+            return false;

+        }

+

+        if (Float.compare(m30, comp.m30) != 0) {

+            return false;

+        }

+        if (Float.compare(m31, comp.m31) != 0) {

+            return false;

+        }

+        if (Float.compare(m32, comp.m32) != 0) {

+            return false;

+        }

+        if (Float.compare(m33, comp.m33) != 0) {

+            return false;

+        }

+

+        return true;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule cap = e.getCapsule(this);

+        cap.write(m00, "m00", 1);

+        cap.write(m01, "m01", 0);

+        cap.write(m02, "m02", 0);

+        cap.write(m03, "m03", 0);

+        cap.write(m10, "m10", 0);

+        cap.write(m11, "m11", 1);

+        cap.write(m12, "m12", 0);

+        cap.write(m13, "m13", 0);

+        cap.write(m20, "m20", 0);

+        cap.write(m21, "m21", 0);

+        cap.write(m22, "m22", 1);

+        cap.write(m23, "m23", 0);

+        cap.write(m30, "m30", 0);

+        cap.write(m31, "m31", 0);

+        cap.write(m32, "m32", 0);

+        cap.write(m33, "m33", 1);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule cap = e.getCapsule(this);

+        m00 = cap.readFloat("m00", 1);

+        m01 = cap.readFloat("m01", 0);

+        m02 = cap.readFloat("m02", 0);

+        m03 = cap.readFloat("m03", 0);

+        m10 = cap.readFloat("m10", 0);

+        m11 = cap.readFloat("m11", 1);

+        m12 = cap.readFloat("m12", 0);

+        m13 = cap.readFloat("m13", 0);

+        m20 = cap.readFloat("m20", 0);

+        m21 = cap.readFloat("m21", 0);

+        m22 = cap.readFloat("m22", 1);

+        m23 = cap.readFloat("m23", 0);

+        m30 = cap.readFloat("m30", 0);

+        m31 = cap.readFloat("m31", 0);

+        m32 = cap.readFloat("m32", 0);

+        m33 = cap.readFloat("m33", 1);

+    }

+

+    /**

+     * @return true if this matrix is identity

+     */

+    public boolean isIdentity() {

+        return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0)

+                && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0)

+                && (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0)

+                && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1);

+    }

+

+    /**

+     * Apply a scale to this matrix.

+     * 

+     * @param scale

+     *            the scale to apply

+     */

+    public void scale(Vector3f scale) {

+        m00 *= scale.getX();

+        m10 *= scale.getX();

+        m20 *= scale.getX();

+        m30 *= scale.getX();

+        m01 *= scale.getY();

+        m11 *= scale.getY();

+        m21 *= scale.getY();

+        m31 *= scale.getY();

+        m02 *= scale.getZ();

+        m12 *= scale.getZ();

+        m22 *= scale.getZ();

+        m32 *= scale.getZ();

+    }

+

+    static boolean equalIdentity(Matrix4f mat) {

+        if (Math.abs(mat.m00 - 1) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m11 - 1) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m22 - 1) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m33 - 1) > 1e-4) {

+            return false;

+        }

+

+        if (Math.abs(mat.m01) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m02) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m03) > 1e-4) {

+            return false;

+        }

+

+        if (Math.abs(mat.m10) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m12) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m13) > 1e-4) {

+            return false;

+        }

+

+        if (Math.abs(mat.m20) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m21) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m23) > 1e-4) {

+            return false;

+        }

+

+        if (Math.abs(mat.m30) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m31) > 1e-4) {

+            return false;

+        }

+        if (Math.abs(mat.m32) > 1e-4) {

+            return false;

+        }

+

+        return true;

+    }

+

+    // XXX: This tests more solid than converting the q to a matrix and multiplying... why?

+    public void multLocal(Quaternion rotation) {

+        Vector3f axis = new Vector3f();

+        float angle = rotation.toAngleAxis(axis);

+        Matrix4f matrix4f = new Matrix4f();

+        matrix4f.fromAngleAxis(angle, axis);

+        multLocal(matrix4f);

+    }

+

+    @Override

+    public Matrix4f clone() {

+        try {

+            return (Matrix4f) super.clone();

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError(); // can not happen

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Plane.java b/engine/src/core/com/jme3/math/Plane.java
new file mode 100644
index 0000000..e14e645
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Plane.java
@@ -0,0 +1,284 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+import java.util.logging.Logger;

+

+/**

+ * <code>Plane</code> defines a plane where Normal dot (x,y,z) = Constant.

+ * This provides methods for calculating a "distance" of a point from this

+ * plane. The distance is pseudo due to the fact that it can be negative if the

+ * point is on the non-normal side of the plane.

+ * 

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public class Plane implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private static final Logger logger = Logger

+            .getLogger(Plane.class.getName());

+

+    public static enum Side {

+        None,

+        Positive,

+        Negative

+    }

+

+    /** 

+     * Vector normal to the plane.

+     */

+    protected Vector3f normal = new Vector3f();

+

+    /** 

+     * Constant of the plane. See formula in class definition.

+     */

+    protected float constant;

+

+    /**

+     * Constructor instantiates a new <code>Plane</code> object. This is the

+     * default object and contains a normal of (0,0,0) and a constant of 0.

+     */

+    public Plane() {

+    }

+

+    /**

+     * Constructor instantiates a new <code>Plane</code> object. The normal

+     * and constant values are set at creation.

+     * 

+     * @param normal

+     *            the normal of the plane.

+     * @param constant

+     *            the constant of the plane.

+     */

+    public Plane(Vector3f normal, float constant) {

+        if (normal == null) {

+            throw new IllegalArgumentException("normal cannot be null");

+        }

+

+        this.normal.set(normal);

+        this.constant = constant;

+    }

+

+    /**

+     * <code>setNormal</code> sets the normal of the plane.

+     * 

+     * @param normal

+     *            the new normal of the plane.

+     */

+    public void setNormal(Vector3f normal) {

+        if (normal == null) {

+            throw new IllegalArgumentException("normal cannot be null");

+        }

+        this.normal.set(normal);

+    }

+

+    /**

+     * <code>setNormal</code> sets the normal of the plane.

+     *

+     */

+    public void setNormal(float x, float y, float z) {

+        this.normal.set(x,y,z);

+    }

+

+    /**

+     * <code>getNormal</code> retrieves the normal of the plane.

+     * 

+     * @return the normal of the plane.

+     */

+    public Vector3f getNormal() {

+        return normal;

+    }

+

+    /**

+     * <code>setConstant</code> sets the constant value that helps define the

+     * plane.

+     * 

+     * @param constant

+     *            the new constant value.

+     */

+    public void setConstant(float constant) {

+        this.constant = constant;

+    }

+

+    /**

+     * <code>getConstant</code> returns the constant of the plane.

+     * 

+     * @return the constant of the plane.

+     */

+    public float getConstant() {

+        return constant;

+    }

+

+    public Vector3f getClosestPoint(Vector3f point, Vector3f store){

+//        float t = constant - normal.dot(point);

+//        return store.set(normal).multLocal(t).addLocal(point);

+        float t = (constant - normal.dot(point)) / normal.dot(normal);

+        return store.set(normal).multLocal(t).addLocal(point);

+    }

+

+    public Vector3f getClosestPoint(Vector3f point){

+        return getClosestPoint(point, new Vector3f());

+    }

+

+    public Vector3f reflect(Vector3f point, Vector3f store){

+        if (store == null)

+            store = new Vector3f();

+

+        float d = pseudoDistance(point);

+        store.set(normal).negateLocal().multLocal(d * 2f);

+        store.addLocal(point);

+        return store;

+    }

+

+    /**

+     * <code>pseudoDistance</code> calculates the distance from this plane to

+     * a provided point. If the point is on the negative side of the plane the

+     * distance returned is negative, otherwise it is positive. If the point is

+     * on the plane, it is zero.

+     * 

+     * @param point

+     *            the point to check.

+     * @return the signed distance from the plane to a point.

+     */

+    public float pseudoDistance(Vector3f point) {

+        return normal.dot(point) - constant;

+    }

+

+    /**

+     * <code>whichSide</code> returns the side at which a point lies on the

+     * plane. The positive values returned are: NEGATIVE_SIDE, POSITIVE_SIDE and

+     * NO_SIDE.

+     * 

+     * @param point

+     *            the point to check.

+     * @return the side at which the point lies.

+     */

+    public Side whichSide(Vector3f point) {

+        float dis = pseudoDistance(point);

+        if (dis < 0) {

+            return Side.Negative;

+        } else if (dis > 0) {

+            return Side.Positive;

+        } else {

+            return Side.None;

+        }

+    }

+

+    public boolean isOnPlane(Vector3f point){

+        float dist = pseudoDistance(point);

+        if (dist < FastMath.FLT_EPSILON && dist > -FastMath.FLT_EPSILON)

+            return true;

+        else

+            return false;

+    }

+

+    /**

+     * Initialize this plane using the three points of the given triangle.

+     * 

+     * @param t

+     *            the triangle

+     */

+    public void setPlanePoints(AbstractTriangle t) {

+        setPlanePoints(t.get1(), t.get2(), t.get3());

+    }

+

+    /**

+     * Initialize this plane using a point of origin and a normal.

+     *

+     * @param origin

+     * @param normal

+     */

+    public void setOriginNormal(Vector3f origin, Vector3f normal){

+        this.normal.set(normal);

+        this.constant = normal.x * origin.x + normal.y * origin.y + normal.z * origin.z;

+    }

+

+    /**

+     * Initialize the Plane using the given 3 points as coplanar.

+     * 

+     * @param v1

+     *            the first point

+     * @param v2

+     *            the second point

+     * @param v3

+     *            the third point

+     */

+    public void setPlanePoints(Vector3f v1, Vector3f v2, Vector3f v3) {

+        normal.set(v2).subtractLocal(v1);

+        normal.crossLocal(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z)

+                .normalizeLocal();

+        constant = normal.dot(v1);

+    }

+

+    /**

+     * <code>toString</code> returns a string thta represents the string

+     * representation of this plane. It represents the normal as a

+     * <code>Vector3f</code> object, so the format is the following:

+     * com.jme.math.Plane [Normal: org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY,

+     * Z=ZZ.ZZZZ] - Constant: CC.CCCCC]

+     * 

+     * @return the string representation of this plane.

+     */

+    @Override

+    public String toString() {

+        return getClass().getSimpleName() + " [Normal: " + normal + " - Constant: "

+                + constant + "]";

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(normal, "normal", Vector3f.ZERO);

+        capsule.write(constant, "constant", 0);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        normal = (Vector3f) capsule.readSavable("normal", Vector3f.ZERO.clone());

+        constant = capsule.readFloat("constant", 0);

+    }

+

+    @Override

+    public Plane clone() {

+        try {

+            Plane p = (Plane) super.clone();

+            p.normal = normal.clone();

+            return p;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Quaternion.java b/engine/src/core/com/jme3/math/Quaternion.java
new file mode 100644
index 0000000..5a5a1c9
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Quaternion.java
@@ -0,0 +1,1345 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import com.jme3.util.TempVars;

+import java.io.Externalizable;

+import java.io.IOException;

+import java.io.ObjectInput;

+import java.io.ObjectOutput;

+import java.util.logging.Logger;

+

+/**

+ * <code>Quaternion</code> defines a single example of a more general class of

+ * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a

+ * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth

+ * continuous rotation.

+ * 

+ * <code>Quaternion</code> is defined by four floating point numbers: {x y z

+ * w}.

+ * 

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public final class Quaternion implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private static final Logger logger = Logger.getLogger(Quaternion.class.getName());

+    /**

+     * Represents the identity quaternion rotation (0, 0, 0, 1).

+     */

+    public static final Quaternion IDENTITY = new Quaternion();

+    public static final Quaternion DIRECTION_Z = new Quaternion();

+    public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0);

+

+    static {

+        DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z);

+    }

+    protected float x, y, z, w;

+

+    /**

+     * Constructor instantiates a new <code>Quaternion</code> object

+     * initializing all values to zero, except w which is initialized to 1.

+     *

+     */

+    public Quaternion() {

+        x = 0;

+        y = 0;

+        z = 0;

+        w = 1;

+    }

+

+    /**

+     * Constructor instantiates a new <code>Quaternion</code> object from the

+     * given list of parameters.

+     *

+     * @param x

+     *            the x value of the quaternion.

+     * @param y

+     *            the y value of the quaternion.

+     * @param z

+     *            the z value of the quaternion.

+     * @param w

+     *            the w value of the quaternion.

+     */

+    public Quaternion(float x, float y, float z, float w) {

+        this.x = x;

+        this.y = y;

+        this.z = z;

+        this.w = w;

+    }

+

+    public float getX() {

+        return x;

+    }

+

+    public float getY() {

+        return y;

+    }

+

+    public float getZ() {

+        return z;

+    }

+

+    public float getW() {

+        return w;

+    }

+

+    /**

+     * sets the data in a <code>Quaternion</code> object from the given list

+     * of parameters.

+     *

+     * @param x

+     *            the x value of the quaternion.

+     * @param y

+     *            the y value of the quaternion.

+     * @param z

+     *            the z value of the quaternion.

+     * @param w

+     *            the w value of the quaternion.

+     * @return this

+     */

+    public Quaternion set(float x, float y, float z, float w) {

+        this.x = x;

+        this.y = y;

+        this.z = z;

+        this.w = w;

+        return this;

+    }

+

+    /**

+     * Sets the data in this <code>Quaternion</code> object to be equal to the

+     * passed <code>Quaternion</code> object. The values are copied producing

+     * a new object.

+     *

+     * @param q

+     *            The Quaternion to copy values from.

+     * @return this

+     */

+    public Quaternion set(Quaternion q) {

+        this.x = q.x;

+        this.y = q.y;

+        this.z = q.z;

+        this.w = q.w;

+        return this;

+    }

+

+    /**

+     * Constructor instantiates a new <code>Quaternion</code> object from a

+     * collection of rotation angles.

+     *

+     * @param angles

+     *            the angles of rotation (x, y, z) that will define the

+     *            <code>Quaternion</code>.

+     */

+    public Quaternion(float[] angles) {

+        fromAngles(angles);

+    }

+

+    /**

+     * Constructor instantiates a new <code>Quaternion</code> object from an

+     * interpolation between two other quaternions.

+     *

+     * @param q1

+     *            the first quaternion.

+     * @param q2

+     *            the second quaternion.

+     * @param interp

+     *            the amount to interpolate between the two quaternions.

+     */

+    public Quaternion(Quaternion q1, Quaternion q2, float interp) {

+        slerp(q1, q2, interp);

+    }

+

+    /**

+     * Constructor instantiates a new <code>Quaternion</code> object from an

+     * existing quaternion, creating a copy.

+     *

+     * @param q

+     *            the quaternion to copy.

+     */

+    public Quaternion(Quaternion q) {

+        this.x = q.x;

+        this.y = q.y;

+        this.z = q.z;

+        this.w = q.w;

+    }

+

+    /**

+     * Sets this Quaternion to {0, 0, 0, 1}.  Same as calling set(0,0,0,1).

+     */

+    public void loadIdentity() {

+        x = y = z = 0;

+        w = 1;

+    }

+

+    /**

+     * @return true if this Quaternion is {0,0,0,1}

+     */

+    public boolean isIdentity() {

+        if (x == 0 && y == 0 && z == 0 && w == 1) {

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+    /**

+     * <code>fromAngles</code> builds a quaternion from the Euler rotation

+     * angles (y,r,p).

+     *

+     * @param angles

+     *            the Euler angles of rotation (in radians).

+     */

+    public Quaternion fromAngles(float[] angles) {

+        if (angles.length != 3) {

+            throw new IllegalArgumentException(

+                    "Angles array must have three elements");

+        }

+

+        return fromAngles(angles[0], angles[1], angles[2]);

+    }

+

+    /**

+     * <code>fromAngles</code> builds a Quaternion from the Euler rotation

+     * angles (y,r,p). Note that we are applying in order: roll, pitch, yaw but

+     * we've ordered them in x, y, and z for convenience.

+     * @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm</a>

+     * 

+     * @param yaw

+     *            the Euler yaw of rotation (in radians). (aka Bank, often rot

+     *            around x)

+     * @param roll

+     *            the Euler roll of rotation (in radians). (aka Heading, often

+     *            rot around y)

+     * @param pitch

+     *            the Euler pitch of rotation (in radians). (aka Attitude, often

+     *            rot around z)

+     */

+    public Quaternion fromAngles(float yaw, float roll, float pitch) {

+        float angle;

+        float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw;

+        angle = pitch * 0.5f;

+        sinPitch = FastMath.sin(angle);

+        cosPitch = FastMath.cos(angle);

+        angle = roll * 0.5f;

+        sinRoll = FastMath.sin(angle);

+        cosRoll = FastMath.cos(angle);

+        angle = yaw * 0.5f;

+        sinYaw = FastMath.sin(angle);

+        cosYaw = FastMath.cos(angle);

+

+        // variables used to reduce multiplication calls.

+        float cosRollXcosPitch = cosRoll * cosPitch;

+        float sinRollXsinPitch = sinRoll * sinPitch;

+        float cosRollXsinPitch = cosRoll * sinPitch;

+        float sinRollXcosPitch = sinRoll * cosPitch;

+

+        w = (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw);

+        x = (cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw);

+        y = (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw);

+        z = (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw);

+

+        normalize();

+        return this;

+    }

+

+    /**

+     * <code>toAngles</code> returns this quaternion converted to Euler

+     * rotation angles (yaw,roll,pitch).<br/>

+     * @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm</a>

+     * 

+     * @param angles

+     *            the float[] in which the angles should be stored, or null if

+     *            you want a new float[] to be created

+     * @return the float[] in which the angles are stored.

+     */

+    public float[] toAngles(float[] angles) {

+        if (angles == null) {

+            angles = new float[3];

+        } else if (angles.length != 3) {

+            throw new IllegalArgumentException("Angles array must have three elements");

+        }

+

+        float sqw = w * w;

+        float sqx = x * x;

+        float sqy = y * y;

+        float sqz = z * z;

+        float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise

+        // is correction factor

+        float test = x * y + z * w;

+        if (test > 0.499 * unit) { // singularity at north pole

+            angles[1] = 2 * FastMath.atan2(x, w);

+            angles[2] = FastMath.HALF_PI;

+            angles[0] = 0;

+        } else if (test < -0.499 * unit) { // singularity at south pole

+            angles[1] = -2 * FastMath.atan2(x, w);

+            angles[2] = -FastMath.HALF_PI;

+            angles[0] = 0;

+        } else {

+            angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // roll or heading 

+            angles[2] = FastMath.asin(2 * test / unit); // pitch or attitude

+            angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // yaw or bank

+        }

+        return angles;

+    }

+

+    /**

+     * 

+     * <code>fromRotationMatrix</code> generates a quaternion from a supplied

+     * matrix. This matrix is assumed to be a rotational matrix.

+     * 

+     * @param matrix

+     *            the matrix that defines the rotation.

+     */

+    public Quaternion fromRotationMatrix(Matrix3f matrix) {

+        return fromRotationMatrix(matrix.m00, matrix.m01, matrix.m02, matrix.m10,

+                matrix.m11, matrix.m12, matrix.m20, matrix.m21, matrix.m22);

+    }

+

+    public Quaternion fromRotationMatrix(float m00, float m01, float m02,

+            float m10, float m11, float m12,

+            float m20, float m21, float m22) {

+        // Use the Graphics Gems code, from 

+        // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z

+        // *NOT* the "Matrix and Quaternions FAQ", which has errors!

+

+        // the trace is the sum of the diagonal elements; see

+        // http://mathworld.wolfram.com/MatrixTrace.html

+        float t = m00 + m11 + m22;

+

+        // we protect the division by s by ensuring that s>=1

+        if (t >= 0) { // |w| >= .5

+            float s = FastMath.sqrt(t + 1); // |s|>=1 ...

+            w = 0.5f * s;

+            s = 0.5f / s;                 // so this division isn't bad

+            x = (m21 - m12) * s;

+            y = (m02 - m20) * s;

+            z = (m10 - m01) * s;

+        } else if ((m00 > m11) && (m00 > m22)) {

+            float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1

+            x = s * 0.5f; // |x| >= .5

+            s = 0.5f / s;

+            y = (m10 + m01) * s;

+            z = (m02 + m20) * s;

+            w = (m21 - m12) * s;

+        } else if (m11 > m22) {

+            float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1

+            y = s * 0.5f; // |y| >= .5

+            s = 0.5f / s;

+            x = (m10 + m01) * s;

+            z = (m21 + m12) * s;

+            w = (m02 - m20) * s;

+        } else {

+            float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1

+            z = s * 0.5f; // |z| >= .5

+            s = 0.5f / s;

+            x = (m02 + m20) * s;

+            y = (m21 + m12) * s;

+            w = (m10 - m01) * s;

+        }

+

+        return this;

+    }

+

+    /**

+     * <code>toRotationMatrix</code> converts this quaternion to a rotational

+     * matrix. Note: the result is created from a normalized version of this quat.

+     * 

+     * @return the rotation matrix representation of this quaternion.

+     */

+    public Matrix3f toRotationMatrix() {

+        Matrix3f matrix = new Matrix3f();

+        return toRotationMatrix(matrix);

+    }

+

+    /**

+     * <code>toRotationMatrix</code> converts this quaternion to a rotational

+     * matrix. The result is stored in result.

+     * 

+     * @param result

+     *            The Matrix3f to store the result in.

+     * @return the rotation matrix representation of this quaternion.

+     */

+    public Matrix3f toRotationMatrix(Matrix3f result) {

+

+        float norm = norm();

+        // we explicitly test norm against one here, saving a division

+        // at the cost of a test and branch.  Is it worth it?

+        float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;

+

+        // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs

+        // will be used 2-4 times each.

+        float xs = x * s;

+        float ys = y * s;

+        float zs = z * s;

+        float xx = x * xs;

+        float xy = x * ys;

+        float xz = x * zs;

+        float xw = w * xs;

+        float yy = y * ys;

+        float yz = y * zs;

+        float yw = w * ys;

+        float zz = z * zs;

+        float zw = w * zs;

+

+        // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here

+        result.m00 = 1 - (yy + zz);

+        result.m01 = (xy - zw);

+        result.m02 = (xz + yw);

+        result.m10 = (xy + zw);

+        result.m11 = 1 - (xx + zz);

+        result.m12 = (yz - xw);

+        result.m20 = (xz - yw);

+        result.m21 = (yz + xw);

+        result.m22 = 1 - (xx + yy);

+

+        return result;

+    }

+

+    /**

+     * <code>toRotationMatrix</code> converts this quaternion to a rotational

+     * matrix. The result is stored in result. 4th row and 4th column values are

+     * untouched. Note: the result is created from a normalized version of this quat.

+     * 

+     * @param result

+     *            The Matrix4f to store the result in.

+     * @return the rotation matrix representation of this quaternion.

+     */

+    public Matrix4f toRotationMatrix(Matrix4f result) {

+

+        float norm = norm();

+        // we explicitly test norm against one here, saving a division

+        // at the cost of a test and branch.  Is it worth it?

+        float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;

+

+        // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs

+        // will be used 2-4 times each.

+        float xs = x * s;

+        float ys = y * s;

+        float zs = z * s;

+        float xx = x * xs;

+        float xy = x * ys;

+        float xz = x * zs;

+        float xw = w * xs;

+        float yy = y * ys;

+        float yz = y * zs;

+        float yw = w * ys;

+        float zz = z * zs;

+        float zw = w * zs;

+

+        // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here

+        result.m00 = 1 - (yy + zz);

+        result.m01 = (xy - zw);

+        result.m02 = (xz + yw);

+        result.m10 = (xy + zw);

+        result.m11 = 1 - (xx + zz);

+        result.m12 = (yz - xw);

+        result.m20 = (xz - yw);

+        result.m21 = (yz + xw);

+        result.m22 = 1 - (xx + yy);

+

+        return result;

+    }

+

+    /**

+     * <code>getRotationColumn</code> returns one of three columns specified

+     * by the parameter. This column is returned as a <code>Vector3f</code>

+     * object.

+     *

+     * @param i

+     *            the column to retrieve. Must be between 0 and 2.

+     * @return the column specified by the index.

+     */

+    public Vector3f getRotationColumn(int i) {

+        return getRotationColumn(i, null);

+    }

+

+    /**

+     * <code>getRotationColumn</code> returns one of three columns specified

+     * by the parameter. This column is returned as a <code>Vector3f</code>

+     * object.  The value is retrieved as if this quaternion was first normalized.

+     *

+     * @param i

+     *            the column to retrieve. Must be between 0 and 2.

+     * @param store

+     *            the vector object to store the result in. if null, a new one

+     *            is created.

+     * @return the column specified by the index.

+     */

+    public Vector3f getRotationColumn(int i, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+

+        float norm = norm();

+        if (norm != 1.0f) {

+            norm = FastMath.invSqrt(norm);

+        }

+

+        float xx = x * x * norm;

+        float xy = x * y * norm;

+        float xz = x * z * norm;

+        float xw = x * w * norm;

+        float yy = y * y * norm;

+        float yz = y * z * norm;

+        float yw = y * w * norm;

+        float zz = z * z * norm;

+        float zw = z * w * norm;

+

+        switch (i) {

+            case 0:

+                store.x = 1 - 2 * (yy + zz);

+                store.y = 2 * (xy + zw);

+                store.z = 2 * (xz - yw);

+                break;

+            case 1:

+                store.x = 2 * (xy - zw);

+                store.y = 1 - 2 * (xx + zz);

+                store.z = 2 * (yz + xw);

+                break;

+            case 2:

+                store.x = 2 * (xz + yw);

+                store.y = 2 * (yz - xw);

+                store.z = 1 - 2 * (xx + yy);

+                break;

+            default:

+                logger.warning("Invalid column index.");

+                throw new IllegalArgumentException("Invalid column index. " + i);

+        }

+

+        return store;

+    }

+

+    /**

+     * <code>fromAngleAxis</code> sets this quaternion to the values specified

+     * by an angle and an axis of rotation. This method creates an object, so

+     * use fromAngleNormalAxis if your axis is already normalized.

+     *

+     * @param angle

+     *            the angle to rotate (in radians).

+     * @param axis

+     *            the axis of rotation.

+     * @return this quaternion

+     */

+    public Quaternion fromAngleAxis(float angle, Vector3f axis) {

+        Vector3f normAxis = axis.normalize();

+        fromAngleNormalAxis(angle, normAxis);

+        return this;

+    }

+

+    /**

+     * <code>fromAngleNormalAxis</code> sets this quaternion to the values

+     * specified by an angle and a normalized axis of rotation.

+     *

+     * @param angle

+     *            the angle to rotate (in radians).

+     * @param axis

+     *            the axis of rotation (already normalized).

+     */

+    public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) {

+        if (axis.x == 0 && axis.y == 0 && axis.z == 0) {

+            loadIdentity();

+        } else {

+            float halfAngle = 0.5f * angle;

+            float sin = FastMath.sin(halfAngle);

+            w = FastMath.cos(halfAngle);

+            x = sin * axis.x;

+            y = sin * axis.y;

+            z = sin * axis.z;

+        }

+        return this;

+    }

+

+    /**

+     * <code>toAngleAxis</code> sets a given angle and axis to that

+     * represented by the current quaternion. The values are stored as

+     * following: The axis is provided as a parameter and built by the method,

+     * the angle is returned as a float.

+     *

+     * @param axisStore

+     *            the object we'll store the computed axis in.

+     * @return the angle of rotation in radians.

+     */

+    public float toAngleAxis(Vector3f axisStore) {

+        float sqrLength = x * x + y * y + z * z;

+        float angle;

+        if (sqrLength == 0.0f) {

+            angle = 0.0f;

+            if (axisStore != null) {

+                axisStore.x = 1.0f;

+                axisStore.y = 0.0f;

+                axisStore.z = 0.0f;

+            }

+        } else {

+            angle = (2.0f * FastMath.acos(w));

+            if (axisStore != null) {

+                float invLength = (1.0f / FastMath.sqrt(sqrLength));

+                axisStore.x = x * invLength;

+                axisStore.y = y * invLength;

+                axisStore.z = z * invLength;

+            }

+        }

+

+        return angle;

+    }

+

+    /**

+     * <code>slerp</code> sets this quaternion's value as an interpolation

+     * between two other quaternions.

+     *

+     * @param q1

+     *            the first quaternion.

+     * @param q2

+     *            the second quaternion.

+     * @param t

+     *            the amount to interpolate between the two quaternions.

+     */

+    public Quaternion slerp(Quaternion q1, Quaternion q2, float t) {

+        // Create a local quaternion to store the interpolated quaternion

+        if (q1.x == q2.x && q1.y == q2.y && q1.z == q2.z && q1.w == q2.w) {

+            this.set(q1);

+            return this;

+        }

+

+        float result = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)

+                + (q1.w * q2.w);

+

+        if (result < 0.0f) {

+            // Negate the second quaternion and the result of the dot product

+            q2.x = -q2.x;

+            q2.y = -q2.y;

+            q2.z = -q2.z;

+            q2.w = -q2.w;

+            result = -result;

+        }

+

+        // Set the first and second scale for the interpolation

+        float scale0 = 1 - t;

+        float scale1 = t;

+

+        // Check if the angle between the 2 quaternions was big enough to

+        // warrant such calculations

+        if ((1 - result) > 0.1f) {// Get the angle between the 2 quaternions,

+            // and then store the sin() of that angle

+            float theta = FastMath.acos(result);

+            float invSinTheta = 1f / FastMath.sin(theta);

+

+            // Calculate the scale for q1 and q2, according to the angle and

+            // it's sine value

+            scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;

+            scale1 = FastMath.sin((t * theta)) * invSinTheta;

+        }

+

+        // Calculate the x, y, z and w values for the quaternion by using a

+        // special

+        // form of linear interpolation for quaternions.

+        this.x = (scale0 * q1.x) + (scale1 * q2.x);

+        this.y = (scale0 * q1.y) + (scale1 * q2.y);

+        this.z = (scale0 * q1.z) + (scale1 * q2.z);

+        this.w = (scale0 * q1.w) + (scale1 * q2.w);

+

+        // Return the interpolated quaternion

+        return this;

+    }

+

+    /**

+     * Sets the values of this quaternion to the slerp from itself to q2 by

+     * changeAmnt

+     *

+     * @param q2

+     *            Final interpolation value

+     * @param changeAmnt

+     *            The amount diffrence

+     */

+    public void slerp(Quaternion q2, float changeAmnt) {

+        if (this.x == q2.x && this.y == q2.y && this.z == q2.z

+                && this.w == q2.w) {

+            return;

+        }

+

+        float result = (this.x * q2.x) + (this.y * q2.y) + (this.z * q2.z)

+                + (this.w * q2.w);

+

+        if (result < 0.0f) {

+            // Negate the second quaternion and the result of the dot product

+            q2.x = -q2.x;

+            q2.y = -q2.y;

+            q2.z = -q2.z;

+            q2.w = -q2.w;

+            result = -result;

+        }

+

+        // Set the first and second scale for the interpolation

+        float scale0 = 1 - changeAmnt;

+        float scale1 = changeAmnt;

+

+        // Check if the angle between the 2 quaternions was big enough to

+        // warrant such calculations

+        if ((1 - result) > 0.1f) {

+            // Get the angle between the 2 quaternions, and then store the sin()

+            // of that angle

+            float theta = FastMath.acos(result);

+            float invSinTheta = 1f / FastMath.sin(theta);

+

+            // Calculate the scale for q1 and q2, according to the angle and

+            // it's sine value

+            scale0 = FastMath.sin((1 - changeAmnt) * theta) * invSinTheta;

+            scale1 = FastMath.sin((changeAmnt * theta)) * invSinTheta;

+        }

+

+        // Calculate the x, y, z and w values for the quaternion by using a

+        // special

+        // form of linear interpolation for quaternions.

+        this.x = (scale0 * this.x) + (scale1 * q2.x);

+        this.y = (scale0 * this.y) + (scale1 * q2.y);

+        this.z = (scale0 * this.z) + (scale1 * q2.z);

+        this.w = (scale0 * this.w) + (scale1 * q2.w);

+    }

+

+    /**

+     * Sets the values of this quaternion to the nlerp from itself to q2 by blend.

+     * @param q2

+     * @param blend

+     */

+    public void nlerp(Quaternion q2, float blend) {

+        float dot = dot(q2);

+        float blendI = 1.0f - blend;

+        if (dot < 0.0f) {

+            x = blendI * x - blend * q2.x;

+            y = blendI * y - blend * q2.y;

+            z = blendI * z - blend * q2.z;

+            w = blendI * w - blend * q2.w;

+        } else {

+            x = blendI * x + blend * q2.x;

+            y = blendI * y + blend * q2.y;

+            z = blendI * z + blend * q2.z;

+            w = blendI * w + blend * q2.w;

+        }

+        normalizeLocal();

+    }

+

+    /**

+     * <code>add</code> adds the values of this quaternion to those of the

+     * parameter quaternion. The result is returned as a new quaternion.

+     *

+     * @param q

+     *            the quaternion to add to this.

+     * @return the new quaternion.

+     */

+    public Quaternion add(Quaternion q) {

+        return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w);

+    }

+

+    /**

+     * <code>add</code> adds the values of this quaternion to those of the

+     * parameter quaternion. The result is stored in this Quaternion.

+     *

+     * @param q

+     *            the quaternion to add to this.

+     * @return This Quaternion after addition.

+     */

+    public Quaternion addLocal(Quaternion q) {

+        this.x += q.x;

+        this.y += q.y;

+        this.z += q.z;

+        this.w += q.w;

+        return this;

+    }

+

+    /**

+     * <code>subtract</code> subtracts the values of the parameter quaternion

+     * from those of this quaternion. The result is returned as a new

+     * quaternion.

+     *

+     * @param q

+     *            the quaternion to subtract from this.

+     * @return the new quaternion.

+     */

+    public Quaternion subtract(Quaternion q) {

+        return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w);

+    }

+

+    /**

+     * <code>subtract</code> subtracts the values of the parameter quaternion

+     * from those of this quaternion. The result is stored in this Quaternion.

+     *

+     * @param q

+     *            the quaternion to subtract from this.

+     * @return This Quaternion after subtraction.

+     */

+    public Quaternion subtractLocal(Quaternion q) {

+        this.x -= q.x;

+        this.y -= q.y;

+        this.z -= q.z;

+        this.w -= q.w;

+        return this;

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter quaternion.

+     * The result is returned as a new quaternion. It should be noted that

+     * quaternion multiplication is not commutative so q * p != p * q.

+     *

+     * @param q

+     *            the quaternion to multiply this quaternion by.

+     * @return the new quaternion.

+     */

+    public Quaternion mult(Quaternion q) {

+        return mult(q, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter quaternion.

+     * The result is returned as a new quaternion. It should be noted that

+     * quaternion multiplication is not commutative so q * p != p * q.

+     *

+     * It IS safe for q and res to be the same object.

+     * It IS safe for this and res to be the same object.

+     *

+     * @param q

+     *            the quaternion to multiply this quaternion by.

+     * @param res

+     *            the quaternion to store the result in.

+     * @return the new quaternion.

+     */

+    public Quaternion mult(Quaternion q, Quaternion res) {

+        if (res == null) {

+            res = new Quaternion();

+        }

+        float qw = q.w, qx = q.x, qy = q.y, qz = q.z;

+        res.x = x * qw + y * qz - z * qy + w * qx;

+        res.y = -x * qz + y * qw + z * qx + w * qy;

+        res.z = x * qy - y * qx + z * qw + w * qz;

+        res.w = -x * qx - y * qy - z * qz + w * qw;

+        return res;

+    }

+

+    /**

+     * <code>apply</code> multiplies this quaternion by a parameter matrix

+     * internally.

+     *

+     * @param matrix

+     *            the matrix to apply to this quaternion.

+     */

+    public void apply(Matrix3f matrix) {

+        float oldX = x, oldY = y, oldZ = z, oldW = w;

+        fromRotationMatrix(matrix);

+        float tempX = x, tempY = y, tempZ = z, tempW = w;

+

+        x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX;

+        y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY;

+        z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ;

+        w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW;

+    }

+

+    /**

+     *

+     * <code>fromAxes</code> creates a <code>Quaternion</code> that

+     * represents the coordinate system defined by three axes. These axes are

+     * assumed to be orthogonal and no error checking is applied. Thus, the user

+     * must insure that the three axes being provided indeed represents a proper

+     * right handed coordinate system.

+     *

+     * @param axis

+     *            the array containing the three vectors representing the

+     *            coordinate system.

+     */

+    public Quaternion fromAxes(Vector3f[] axis) {

+        if (axis.length != 3) {

+            throw new IllegalArgumentException(

+                    "Axis array must have three elements");

+        }

+        return fromAxes(axis[0], axis[1], axis[2]);

+    }

+

+    /**

+     *

+     * <code>fromAxes</code> creates a <code>Quaternion</code> that

+     * represents the coordinate system defined by three axes. These axes are

+     * assumed to be orthogonal and no error checking is applied. Thus, the user

+     * must insure that the three axes being provided indeed represents a proper

+     * right handed coordinate system.

+     *

+     * @param xAxis vector representing the x-axis of the coordinate system.

+     * @param yAxis vector representing the y-axis of the coordinate system.

+     * @param zAxis vector representing the z-axis of the coordinate system.

+     */

+    public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) {

+        return fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y,

+                zAxis.y, xAxis.z, yAxis.z, zAxis.z);

+    }

+

+    /**

+     *

+     * <code>toAxes</code> takes in an array of three vectors. Each vector

+     * corresponds to an axis of the coordinate system defined by the quaternion

+     * rotation.

+     *

+     * @param axis

+     *            the array of vectors to be filled.

+     */

+    public void toAxes(Vector3f axis[]) {

+        Matrix3f tempMat = toRotationMatrix();

+        axis[0] = tempMat.getColumn(0, axis[0]);

+        axis[1] = tempMat.getColumn(1, axis[1]);

+        axis[2] = tempMat.getColumn(2, axis[2]);

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter vector. The

+     * result is returned as a new vector.

+     *

+     * @param v

+     *            the vector to multiply this quaternion by.

+     * @return the new vector.

+     */

+    public Vector3f mult(Vector3f v) {

+        return mult(v, null);

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter vector. The

+     * result is stored in the supplied vector

+     *

+     * @param v

+     *            the vector to multiply this quaternion by.

+     * @return v

+     */

+    public Vector3f multLocal(Vector3f v) {

+        float tempX, tempY;

+        tempX = w * w * v.x + 2 * y * w * v.z - 2 * z * w * v.y + x * x * v.x

+                + 2 * y * x * v.y + 2 * z * x * v.z - z * z * v.x - y * y * v.x;

+        tempY = 2 * x * y * v.x + y * y * v.y + 2 * z * y * v.z + 2 * w * z

+                * v.x - z * z * v.y + w * w * v.y - 2 * x * w * v.z - x * x

+                * v.y;

+        v.z = 2 * x * z * v.x + 2 * y * z * v.y + z * z * v.z - 2 * w * y * v.x

+                - y * y * v.z + 2 * w * x * v.y - x * x * v.z + w * w * v.z;

+        v.x = tempX;

+        v.y = tempY;

+        return v;

+    }

+

+    /**

+     * Multiplies this Quaternion by the supplied quaternion. The result is

+     * stored in this Quaternion, which is also returned for chaining. Similar

+     * to this *= q.

+     *

+     * @param q

+     *            The Quaternion to multiply this one by.

+     * @return This Quaternion, after multiplication.

+     */

+    public Quaternion multLocal(Quaternion q) {

+        float x1 = x * q.w + y * q.z - z * q.y + w * q.x;

+        float y1 = -x * q.z + y * q.w + z * q.x + w * q.y;

+        float z1 = x * q.y - y * q.x + z * q.w + w * q.z;

+        w = -x * q.x - y * q.y - z * q.z + w * q.w;

+        x = x1;

+        y = y1;

+        z = z1;

+        return this;

+    }

+

+    /**

+     * Multiplies this Quaternion by the supplied quaternion. The result is

+     * stored in this Quaternion, which is also returned for chaining. Similar

+     * to this *= q.

+     *

+     * @param qx -

+     *            quat x value

+     * @param qy -

+     *            quat y value

+     * @param qz -

+     *            quat z value

+     * @param qw -

+     *            quat w value

+     *

+     * @return This Quaternion, after multiplication.

+     */

+    public Quaternion multLocal(float qx, float qy, float qz, float qw) {

+        float x1 = x * qw + y * qz - z * qy + w * qx;

+        float y1 = -x * qz + y * qw + z * qx + w * qy;

+        float z1 = x * qy - y * qx + z * qw + w * qz;

+        w = -x * qx - y * qy - z * qz + w * qw;

+        x = x1;

+        y = y1;

+        z = z1;

+        return this;

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter vector. The

+     * result is returned as a new vector.

+     * 

+     * @param v

+     *            the vector to multiply this quaternion by.

+     * @param store

+     *            the vector to store the result in. It IS safe for v and store

+     *            to be the same object.

+     * @return the result vector.

+     */

+    public Vector3f mult(Vector3f v, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f();

+        }

+        if (v.x == 0 && v.y == 0 && v.z == 0) {

+            store.set(0, 0, 0);

+        } else {

+            float vx = v.x, vy = v.y, vz = v.z;

+            store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x

+                    * vx + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y

+                    * y * vx;

+            store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w

+                    * z * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x

+                    * x * vy;

+            store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w

+                    * y * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w

+                    * w * vz;

+        }

+        return store;

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter scalar. The

+     * result is returned as a new quaternion.

+     *

+     * @param scalar

+     *            the quaternion to multiply this quaternion by.

+     * @return the new quaternion.

+     */

+    public Quaternion mult(float scalar) {

+        return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w);

+    }

+

+    /**

+     * <code>mult</code> multiplies this quaternion by a parameter scalar. The

+     * result is stored locally.

+     *

+     * @param scalar

+     *            the quaternion to multiply this quaternion by.

+     * @return this.

+     */

+    public Quaternion multLocal(float scalar) {

+        w *= scalar;

+        x *= scalar;

+        y *= scalar;

+        z *= scalar;

+        return this;

+    }

+

+    /**

+     * <code>dot</code> calculates and returns the dot product of this

+     * quaternion with that of the parameter quaternion.

+     *

+     * @param q

+     *            the quaternion to calculate the dot product of.

+     * @return the dot product of this and the parameter quaternion.

+     */

+    public float dot(Quaternion q) {

+        return w * q.w + x * q.x + y * q.y + z * q.z;

+    }

+

+    /**

+     * <code>norm</code> returns the norm of this quaternion. This is the dot

+     * product of this quaternion with itself.

+     *

+     * @return the norm of the quaternion.

+     */

+    public float norm() {

+        return w * w + x * x + y * y + z * z;

+    }

+

+    /**

+     * <code>normalize</code> normalizes the current <code>Quaternion</code>

+     * @deprecated The naming of this method doesn't follow convention.

+     * Please use {@link Quaternion#normalizeLocal() } instead.

+     */

+    @Deprecated

+    public void normalize() {

+        float n = FastMath.invSqrt(norm());

+        x *= n;

+        y *= n;

+        z *= n;

+        w *= n;

+    }

+

+    /**

+     * <code>normalize</code> normalizes the current <code>Quaternion</code>

+     */

+    public void normalizeLocal() {

+        float n = FastMath.invSqrt(norm());

+        x *= n;

+        y *= n;

+        z *= n;

+        w *= n;

+    }

+

+    /**

+     * <code>inverse</code> returns the inverse of this quaternion as a new

+     * quaternion. If this quaternion does not have an inverse (if its normal is

+     * 0 or less), then null is returned.

+     *

+     * @return the inverse of this quaternion or null if the inverse does not

+     *         exist.

+     */

+    public Quaternion inverse() {

+        float norm = norm();

+        if (norm > 0.0) {

+            float invNorm = 1.0f / norm;

+            return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w

+                    * invNorm);

+        }

+        // return an invalid result to flag the error

+        return null;

+    }

+

+    /**

+     * <code>inverse</code> calculates the inverse of this quaternion and

+     * returns this quaternion after it is calculated. If this quaternion does

+     * not have an inverse (if it's norma is 0 or less), nothing happens

+     *

+     * @return the inverse of this quaternion

+     */

+    public Quaternion inverseLocal() {

+        float norm = norm();

+        if (norm > 0.0) {

+            float invNorm = 1.0f / norm;

+            x *= -invNorm;

+            y *= -invNorm;

+            z *= -invNorm;

+            w *= invNorm;

+        }

+        return this;

+    }

+

+    /**

+     * <code>negate</code> inverts the values of the quaternion.

+     *

+     */

+    public void negate() {

+        x *= -1;

+        y *= -1;

+        z *= -1;

+        w *= -1;

+    }

+

+    /**

+     *

+     * <code>toString</code> creates the string representation of this

+     * <code>Quaternion</code>. The values of the quaternion are displace (x,

+     * y, z, w), in the following manner: <br>

+     * (x, y, z, w)

+     *

+     * @return the string representation of this object.

+     * @see java.lang.Object#toString()

+     */

+    @Override

+    public String toString() {

+        return "(" + x + ", " + y + ", " + z + ", " + w + ")";

+    }

+

+    /**

+     * <code>equals</code> determines if two quaternions are logically equal,

+     * that is, if the values of (x, y, z, w) are the same for both quaternions.

+     *

+     * @param o

+     *            the object to compare for equality

+     * @return true if they are equal, false otherwise.

+     */

+    @Override

+    public boolean equals(Object o) {

+        if (!(o instanceof Quaternion)) {

+            return false;

+        }

+

+        if (this == o) {

+            return true;

+        }

+

+        Quaternion comp = (Quaternion) o;

+        if (Float.compare(x, comp.x) != 0) {

+            return false;

+        }

+        if (Float.compare(y, comp.y) != 0) {

+            return false;

+        }

+        if (Float.compare(z, comp.z) != 0) {

+            return false;

+        }

+        if (Float.compare(w, comp.w) != 0) {

+            return false;

+        }

+        return true;

+    }

+

+    /**

+     * 

+     * <code>hashCode</code> returns the hash code value as an integer and is

+     * supported for the benefit of hashing based collection classes such as

+     * Hashtable, HashMap, HashSet etc.

+     * 

+     * @return the hashcode for this instance of Quaternion.

+     * @see java.lang.Object#hashCode()

+     */

+    @Override

+    public int hashCode() {

+        int hash = 37;

+        hash = 37 * hash + Float.floatToIntBits(x);

+        hash = 37 * hash + Float.floatToIntBits(y);

+        hash = 37 * hash + Float.floatToIntBits(z);

+        hash = 37 * hash + Float.floatToIntBits(w);

+        return hash;

+

+    }

+

+    /**

+     * <code>readExternal</code> builds a quaternion from an

+     * <code>ObjectInput</code> object. <br>

+     * NOTE: Used with serialization. Not to be called manually.

+     * 

+     * @param in

+     *            the ObjectInput value to read from.

+     * @throws IOException

+     *             if the ObjectInput value has problems reading a float.

+     * @see java.io.Externalizable

+     */

+    public void readExternal(ObjectInput in) throws IOException {

+        x = in.readFloat();

+        y = in.readFloat();

+        z = in.readFloat();

+        w = in.readFloat();

+    }

+

+    /**

+     * <code>writeExternal</code> writes this quaternion out to a

+     * <code>ObjectOutput</code> object. NOTE: Used with serialization. Not to

+     * be called manually.

+     * 

+     * @param out

+     *            the object to write to.

+     * @throws IOException

+     *             if writing to the ObjectOutput fails.

+     * @see java.io.Externalizable

+     */

+    public void writeExternal(ObjectOutput out) throws IOException {

+        out.writeFloat(x);

+        out.writeFloat(y);

+        out.writeFloat(z);

+        out.writeFloat(w);

+    }

+

+    /**

+     * <code>lookAt</code> is a convienence method for auto-setting the

+     * quaternion based on a direction and an up vector. It computes

+     * the rotation to transform the z-axis to point into 'direction'

+     * and the y-axis to 'up'.

+     *

+     * @param direction

+     *            where to look at in terms of local coordinates

+     * @param up

+     *            a vector indicating the local up direction.

+     *            (typically {0, 1, 0} in jME.)

+     */

+    public void lookAt(Vector3f direction, Vector3f up) {

+        TempVars vars = TempVars.get();

+        vars.vect3.set(direction).normalizeLocal();

+        vars.vect1.set(up).crossLocal(direction).normalizeLocal();

+        vars.vect2.set(direction).crossLocal(vars.vect1).normalizeLocal();

+        fromAxes(vars.vect1, vars.vect2, vars.vect3);

+        vars.release();

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule cap = e.getCapsule(this);

+        cap.write(x, "x", 0);

+        cap.write(y, "y", 0);

+        cap.write(z, "z", 0);

+        cap.write(w, "w", 1);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule cap = e.getCapsule(this);

+        x = cap.readFloat("x", 0);

+        y = cap.readFloat("y", 0);

+        z = cap.readFloat("z", 0);

+        w = cap.readFloat("w", 1);

+    }

+

+    /**

+     * @return A new quaternion that describes a rotation that would point you

+     *         in the exact opposite direction of this Quaternion.

+     */

+    public Quaternion opposite() {

+        return opposite(null);

+    }

+

+    /**

+     * FIXME: This seems to have singularity type issues with angle == 0, possibly others such as PI.

+     * @param store

+     *            A Quaternion to store our result in. If null, a new one is

+     *            created.

+     * @return The store quaternion (or a new Quaterion, if store is null) that

+     *         describes a rotation that would point you in the exact opposite

+     *         direction of this Quaternion.

+     */

+    public Quaternion opposite(Quaternion store) {

+        if (store == null) {

+            store = new Quaternion();

+        }

+

+        Vector3f axis = new Vector3f();

+        float angle = toAngleAxis(axis);

+

+        store.fromAngleAxis(FastMath.PI + angle, axis);

+        return store;

+    }

+

+    /**

+     * @return This Quaternion, altered to describe a rotation that would point

+     *         you in the exact opposite direction of where it is pointing

+     *         currently.

+     */

+    public Quaternion oppositeLocal() {

+        return opposite(this);

+    }

+

+    @Override

+    public Quaternion clone() {

+        try {

+            return (Quaternion) super.clone();

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError(); // can not happen

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Ray.java b/engine/src/core/com/jme3/math/Ray.java
new file mode 100644
index 0000000..f363613
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Ray.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <code>Ray</code> defines a line segment which has an origin and a direction.
+ * That is, a point and an infinite ray is cast from this point. The ray is
+ * defined by the following equation: R(t) = origin + t*direction for t >= 0.
+ * 
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Ray implements Savable, Cloneable, Collidable, java.io.Serializable {
+
+    static final long serialVersionUID = 1;
+
+    /** 
+     * The ray's begining point. 
+     */
+    public Vector3f origin = new Vector3f();
+    
+    /** 
+     * The direction of the ray. 
+     */
+    public Vector3f direction = new Vector3f(0, 0, 1);
+    
+    
+    public float limit = Float.POSITIVE_INFINITY;
+
+    /**
+     * Constructor instantiates a new <code>Ray</code> object. As default, the
+     * origin is (0,0,0) and the direction is (0,0,1).
+     *
+     */
+    public Ray() {
+    }
+
+    /**
+     * Constructor instantiates a new <code>Ray</code> object. The origin and
+     * direction are given.
+     * @param origin the origin of the ray.
+     * @param direction the direction the ray travels in.
+     */
+    public Ray(Vector3f origin, Vector3f direction) {
+        setOrigin(origin);
+        setDirection(direction);
+    }
+
+    /**
+     * <code>intersect</code> determines if the Ray intersects a triangle.
+     * @param t the Triangle to test against.
+     * @return true if the ray collides.
+     */
+//    public boolean intersect(Triangle t) {
+//        return intersect(t.get(0), t.get(1), t.get(2));
+//    }
+    /**
+     * <code>intersect</code> determines if the Ray intersects a triangle
+     * defined by the specified points.
+     *
+     * @param v0
+     *            first point of the triangle.
+     * @param v1
+     *            second point of the triangle.
+     * @param v2
+     *            third point of the triangle.
+     * @return true if the ray collides.
+     */
+//    public boolean intersect(Vector3f v0,Vector3f v1,Vector3f v2){
+//        return intersectWhere(v0, v1, v2, null);
+//    }
+    /**
+     * <code>intersectWhere</code> determines if the Ray intersects a triangle. It then
+     * stores the point of intersection in the given loc vector
+     * @param t the Triangle to test against.
+     * @param loc
+     *            storage vector to save the collision point in (if the ray
+     *            collides)
+     * @return true if the ray collides.
+     */
+    public boolean intersectWhere(Triangle t, Vector3f loc) {
+        return intersectWhere(t.get(0), t.get(1), t.get(2), loc);
+    }
+
+    /**
+     * <code>intersectWhere</code> determines if the Ray intersects a triangle
+     * defined by the specified points and if so it stores the point of
+     * intersection in the given loc vector.
+     *
+     * @param v0
+     *            first point of the triangle.
+     * @param v1
+     *            second point of the triangle.
+     * @param v2
+     *            third point of the triangle.
+     * @param loc
+     *            storage vector to save the collision point in (if the ray
+     *            collides)  if null, only boolean is calculated.
+     * @return true if the ray collides.
+     */
+    public boolean intersectWhere(Vector3f v0, Vector3f v1, Vector3f v2,
+            Vector3f loc) {
+        return intersects(v0, v1, v2, loc, false, false);
+    }
+
+    /**
+     * <code>intersectWherePlanar</code> determines if the Ray intersects a
+     * triangle and if so it stores the point of
+     * intersection in the given loc vector as t, u, v where t is the distance
+     * from the origin to the point of intersection and u,v is the intersection
+     * point in terms of the triangle plane.
+     *
+     * @param t the Triangle to test against.
+     * @param loc
+     *            storage vector to save the collision point in (if the ray
+     *            collides) as t, u, v
+     * @return true if the ray collides.
+     */
+    public boolean intersectWherePlanar(Triangle t, Vector3f loc) {
+        return intersectWherePlanar(t.get(0), t.get(1), t.get(2), loc);
+    }
+
+    /**
+     * <code>intersectWherePlanar</code> determines if the Ray intersects a
+     * triangle defined by the specified points and if so it stores the point of
+     * intersection in the given loc vector as t, u, v where t is the distance
+     * from the origin to the point of intersection and u,v is the intersection
+     * point in terms of the triangle plane.
+     *
+     * @param v0
+     *            first point of the triangle.
+     * @param v1
+     *            second point of the triangle.
+     * @param v2
+     *            third point of the triangle.
+     * @param loc
+     *            storage vector to save the collision point in (if the ray
+     *            collides) as t, u, v
+     * @return true if the ray collides.
+     */
+    public boolean intersectWherePlanar(Vector3f v0, Vector3f v1, Vector3f v2,
+            Vector3f loc) {
+        return intersects(v0, v1, v2, loc, true, false);
+    }
+
+    /**
+     * <code>intersects</code> does the actual intersection work.
+     *
+     * @param v0
+     *            first point of the triangle.
+     * @param v1
+     *            second point of the triangle.
+     * @param v2
+     *            third point of the triangle.
+     * @param store
+     *            storage vector - if null, no intersection is calc'd
+     * @param doPlanar
+     *            true if we are calcing planar results.
+     * @param quad
+     * @return true if ray intersects triangle
+     */
+    private boolean intersects(Vector3f v0, Vector3f v1, Vector3f v2,
+            Vector3f store, boolean doPlanar, boolean quad) {
+        TempVars vars = TempVars.get();
+
+        Vector3f tempVa = vars.vect1,
+                tempVb = vars.vect2,
+                tempVc = vars.vect3,
+                tempVd = vars.vect4;
+
+        Vector3f diff = origin.subtract(v0, tempVa);
+        Vector3f edge1 = v1.subtract(v0, tempVb);
+        Vector3f edge2 = v2.subtract(v0, tempVc);
+        Vector3f norm = edge1.cross(edge2, tempVd);
+
+        float dirDotNorm = direction.dot(norm);
+        float sign;
+        if (dirDotNorm > FastMath.FLT_EPSILON) {
+            sign = 1;
+        } else if (dirDotNorm < -FastMath.FLT_EPSILON) {
+            sign = -1f;
+            dirDotNorm = -dirDotNorm;
+        } else {
+            // ray and triangle/quad are parallel
+            vars.release();
+            return false;
+        }
+
+        float dirDotDiffxEdge2 = sign * direction.dot(diff.cross(edge2, edge2));
+        if (dirDotDiffxEdge2 >= 0.0f) {
+            float dirDotEdge1xDiff = sign
+                    * direction.dot(edge1.crossLocal(diff));
+
+            if (dirDotEdge1xDiff >= 0.0f) {
+                if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) {
+                    float diffDotNorm = -sign * diff.dot(norm);
+                    if (diffDotNorm >= 0.0f) {
+                        // this method always returns
+                        vars.release();
+
+                        // ray intersects triangle
+                        // if storage vector is null, just return true,
+                        if (store == null) {
+                            return true;
+                        }
+
+                        // else fill in.
+                        float inv = 1f / dirDotNorm;
+                        float t = diffDotNorm * inv;
+                        if (!doPlanar) {
+                            store.set(origin).addLocal(direction.x * t,
+                                    direction.y * t, direction.z * t);
+                        } else {
+                            // these weights can be used to determine
+                            // interpolated values, such as texture coord.
+                            // eg. texcoord s,t at intersection point:
+                            // s = w0*s0 + w1*s1 + w2*s2;
+                            // t = w0*t0 + w1*t1 + w2*t2;
+                            float w1 = dirDotDiffxEdge2 * inv;
+                            float w2 = dirDotEdge1xDiff * inv;
+                            //float w0 = 1.0f - w1 - w2;
+                            store.set(t, w1, w2);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+        vars.release();
+        return false;
+    }
+
+    public float intersects(Vector3f v0, Vector3f v1, Vector3f v2) {
+        float edge1X = v1.x - v0.x;
+        float edge1Y = v1.y - v0.y;
+        float edge1Z = v1.z - v0.z;
+
+        float edge2X = v2.x - v0.x;
+        float edge2Y = v2.y - v0.y;
+        float edge2Z = v2.z - v0.z;
+
+        float normX = ((edge1Y * edge2Z) - (edge1Z * edge2Y));
+        float normY = ((edge1Z * edge2X) - (edge1X * edge2Z));
+        float normZ = ((edge1X * edge2Y) - (edge1Y * edge2X));
+
+        float dirDotNorm = direction.x * normX + direction.y * normY + direction.z * normZ;
+
+        float diffX = origin.x - v0.x;
+        float diffY = origin.y - v0.y;
+        float diffZ = origin.z - v0.z;
+
+        float sign;
+        if (dirDotNorm > FastMath.FLT_EPSILON) {
+            sign = 1;
+        } else if (dirDotNorm < -FastMath.FLT_EPSILON) {
+            sign = -1f;
+            dirDotNorm = -dirDotNorm;
+        } else {
+            // ray and triangle/quad are parallel
+            return Float.POSITIVE_INFINITY;
+        }
+
+        float diffEdge2X = ((diffY * edge2Z) - (diffZ * edge2Y));
+        float diffEdge2Y = ((diffZ * edge2X) - (diffX * edge2Z));
+        float diffEdge2Z = ((diffX * edge2Y) - (diffY * edge2X));
+
+        float dirDotDiffxEdge2 = sign * (direction.x * diffEdge2X
+                + direction.y * diffEdge2Y
+                + direction.z * diffEdge2Z);
+
+        if (dirDotDiffxEdge2 >= 0.0f) {
+            diffEdge2X = ((edge1Y * diffZ) - (edge1Z * diffY));
+            diffEdge2Y = ((edge1Z * diffX) - (edge1X * diffZ));
+            diffEdge2Z = ((edge1X * diffY) - (edge1Y * diffX));
+
+            float dirDotEdge1xDiff = sign * (direction.x * diffEdge2X
+                    + direction.y * diffEdge2Y
+                    + direction.z * diffEdge2Z);
+
+            if (dirDotEdge1xDiff >= 0.0f) {
+                if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) {
+                    float diffDotNorm = -sign * (diffX * normX + diffY * normY + diffZ * normZ);
+                    if (diffDotNorm >= 0.0f) {
+                        // ray intersects triangle
+                        // fill in.
+                        float inv = 1f / dirDotNorm;
+                        float t = diffDotNorm * inv;
+                        return t;
+                    }
+                }
+            }
+        }
+
+        return Float.POSITIVE_INFINITY;
+    }
+
+    /**
+     * <code>intersectWherePlanar</code> determines if the Ray intersects a
+     * quad defined by the specified points and if so it stores the point of
+     * intersection in the given loc vector as t, u, v where t is the distance
+     * from the origin to the point of intersection and u,v is the intersection
+     * point in terms of the quad plane.
+     * One edge of the quad is [v0,v1], another one [v0,v2]. The behaviour thus is like
+     * {@link #intersectWherePlanar(Vector3f, Vector3f, Vector3f, Vector3f)} except for
+     * the extended area, which is equivalent to the union of the triangles [v0,v1,v2]
+     * and [-v0+v1+v2,v1,v2].
+     *
+     * @param v0
+     *            top left point of the quad.
+     * @param v1
+     *            top right point of the quad.
+     * @param v2
+     *            bottom left point of the quad.
+     * @param loc
+     *            storage vector to save the collision point in (if the ray
+     *            collides) as t, u, v
+     * @return true if the ray collides with the quad.
+     */
+    public boolean intersectWherePlanarQuad(Vector3f v0, Vector3f v1, Vector3f v2,
+            Vector3f loc) {
+        return intersects(v0, v1, v2, loc, true, true);
+    }
+
+    /**
+     * 
+     * @param p
+     * @param loc
+     * @return true if the ray collides with the given Plane
+     */
+    public boolean intersectsWherePlane(Plane p, Vector3f loc) {
+        float denominator = p.getNormal().dot(direction);
+
+        if (denominator > -FastMath.FLT_EPSILON && denominator < FastMath.FLT_EPSILON) {
+            return false; // coplanar
+        }
+        float numerator = -(p.getNormal().dot(origin) - p.getConstant());
+        float ratio = numerator / denominator;
+
+        if (ratio < FastMath.FLT_EPSILON) {
+            return false; // intersects behind origin
+        }
+        loc.set(direction).multLocal(ratio).addLocal(origin);
+
+        return true;
+    }
+
+    public int collideWith(Collidable other, CollisionResults results) {
+        if (other instanceof BoundingVolume) {
+            BoundingVolume bv = (BoundingVolume) other;
+            return bv.collideWith(this, results);
+        } else if (other instanceof AbstractTriangle) {
+            AbstractTriangle tri = (AbstractTriangle) other;
+            float d = intersects(tri.get1(), tri.get2(), tri.get3());
+            if (Float.isInfinite(d) || Float.isNaN(d)) {
+                return 0;
+            }
+
+            Vector3f point = new Vector3f(direction).multLocal(d).addLocal(origin);
+            results.addCollision(new CollisionResult(point, d));
+            return 1;
+        } else {
+            throw new UnsupportedCollisionException();
+        }
+    }
+
+    public float distanceSquared(Vector3f point) {
+        TempVars vars = TempVars.get();
+
+        Vector3f tempVa = vars.vect1,
+                tempVb = vars.vect2;
+
+        point.subtract(origin, tempVa);
+        float rayParam = direction.dot(tempVa);
+        if (rayParam > 0) {
+            origin.add(direction.mult(rayParam, tempVb), tempVb);
+        } else {
+            tempVb.set(origin);
+            rayParam = 0.0f;
+        }
+
+        tempVb.subtract(point, tempVa);
+        float len = tempVa.lengthSquared();
+        vars.release();
+        return len;
+    }
+
+    /**
+     *
+     * <code>getOrigin</code> retrieves the origin point of the ray.
+     *
+     * @return the origin of the ray.
+     */
+    public Vector3f getOrigin() {
+        return origin;
+    }
+
+    /**
+     *
+     * <code>setOrigin</code> sets the origin of the ray.
+     * @param origin the origin of the ray.
+     */
+    public void setOrigin(Vector3f origin) {
+        this.origin.set(origin);
+    }
+
+    /**
+     * <code>getLimit</code> returns the limit of the ray, aka the length.
+     * If the limit is not infinity, then this ray is a line with length <code>
+     * limit</code>.
+     * 
+     * @return the limit of the ray, aka the length.
+     */
+    public float getLimit() {
+        return limit;
+    }
+
+    /**
+     * <code>setLimit</code> sets the limit of the ray.
+     * @param limit the limit of the ray.
+     * @see Ray#getLimit() 
+     */
+    public void setLimit(float limit) {
+        this.limit = limit;
+    }
+
+    /**
+     *
+     * <code>getDirection</code> retrieves the direction vector of the ray.
+     * @return the direction of the ray.
+     */
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    /**
+     *
+     * <code>setDirection</code> sets the direction vector of the ray.
+     * @param direction the direction of the ray.
+     */
+    public void setDirection(Vector3f direction) {
+        assert direction.isUnitVector();
+        this.direction.set(direction);
+    }
+
+    /**
+     * Copies information from a source ray into this ray.
+     * 
+     * @param source
+     *            the ray to copy information from
+     */
+    public void set(Ray source) {
+        origin.set(source.getOrigin());
+        direction.set(source.getDirection());
+    }
+
+    public String toString() {
+        return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]";
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(origin, "origin", Vector3f.ZERO);
+        capsule.write(direction, "direction", Vector3f.ZERO);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());
+        direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());
+    }
+
+    @Override
+    public Ray clone() {
+        try {
+            Ray r = (Ray) super.clone();
+            r.direction = direction.clone();
+            r.origin = origin.clone();
+            return r;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/math/Rectangle.java b/engine/src/core/com/jme3/math/Rectangle.java
new file mode 100644
index 0000000..310270c
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Rectangle.java
@@ -0,0 +1,197 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+

+

+/**

+ * 

+ * <code>Rectangle</code> defines a finite plane within three dimensional space

+ * that is specified via three points (A, B, C). These three points define a

+ * triangle with the forth point defining the rectangle ((B + C) - A.

+ * 

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+

+public final class Rectangle implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private Vector3f a, b, c;

+

+    /**

+     * Constructor creates a new <code>Rectangle</code> with no defined corners.

+     * A, B, and C must be set to define a valid rectangle.

+     * 

+     */

+    public Rectangle() {

+        a = new Vector3f();

+        b = new Vector3f();

+        c = new Vector3f();

+    }

+

+    /**

+     * Constructor creates a new <code>Rectangle</code> with defined A, B, and C

+     * points that define the area of the rectangle.

+     * 

+     * @param a

+     *            the first corner of the rectangle.

+     * @param b

+     *            the second corner of the rectangle.

+     * @param c

+     *            the third corner of the rectangle.

+     */

+    public Rectangle(Vector3f a, Vector3f b, Vector3f c) {

+        this.a = a;

+        this.b = b;

+        this.c = c;

+    }

+

+    /**

+     * <code>getA</code> returns the first point of the rectangle.

+     * 

+     * @return the first point of the rectangle.

+     */

+    public Vector3f getA() {

+        return a;

+    }

+

+    /**

+     * <code>setA</code> sets the first point of the rectangle.

+     * 

+     * @param a

+     *            the first point of the rectangle.

+     */

+    public void setA(Vector3f a) {

+        this.a = a;

+    }

+

+    /**

+     * <code>getB</code> returns the second point of the rectangle.

+     * 

+     * @return the second point of the rectangle.

+     */

+    public Vector3f getB() {

+        return b;

+    }

+

+    /**

+     * <code>setB</code> sets the second point of the rectangle.

+     * 

+     * @param b

+     *            the second point of the rectangle.

+     */

+    public void setB(Vector3f b) {

+        this.b = b;

+    }

+

+    /**

+     * <code>getC</code> returns the third point of the rectangle.

+     * 

+     * @return the third point of the rectangle.

+     */

+    public Vector3f getC() {

+        return c;

+    }

+

+    /**

+     * <code>setC</code> sets the third point of the rectangle.

+     * 

+     * @param c

+     *            the third point of the rectangle.

+     */

+    public void setC(Vector3f c) {

+        this.c = c;

+    }

+

+    /**

+     * <code>random</code> returns a random point within the plane defined by:

+     * A, B, C, and (B + C) - A.

+     * 

+     * @return a random point within the rectangle.

+     */

+    public Vector3f random() {

+        return random(null);

+    }

+

+    /**

+     * <code>random</code> returns a random point within the plane defined by:

+     * A, B, C, and (B + C) - A.

+     * 

+     * @param result

+     *            Vector to store result in

+     * @return a random point within the rectangle.

+     */

+    public Vector3f random(Vector3f result) {

+        if (result == null) {

+            result = new Vector3f();

+        }

+

+        float s = FastMath.nextRandomFloat();

+        float t = FastMath.nextRandomFloat();

+

+        float aMod = 1.0f - s - t;

+        result.set(a.mult(aMod).addLocal(b.mult(s).addLocal(c.mult(t))));

+        return result;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(a, "a", Vector3f.ZERO);

+        capsule.write(b, "b", Vector3f.ZERO);

+        capsule.write(c, "c", Vector3f.ZERO);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        a = (Vector3f) capsule.readSavable("a", Vector3f.ZERO.clone());

+        b = (Vector3f) capsule.readSavable("b", Vector3f.ZERO.clone());

+        c = (Vector3f) capsule.readSavable("c", Vector3f.ZERO.clone());

+    }

+

+    @Override

+    public Rectangle clone() {

+        try {

+            Rectangle r = (Rectangle) super.clone();

+            r.a = a.clone();

+            r.b = b.clone();

+            r.c = c.clone();

+            return r;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Ring.java b/engine/src/core/com/jme3/math/Ring.java
new file mode 100644
index 0000000..20f1ea4
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Ring.java
@@ -0,0 +1,233 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+

+

+/**

+ * <code>Ring</code> defines a flat ring or disk within three dimensional

+ * space that is specified via the ring's center point, an up vector, an inner

+ * radius, and an outer radius.

+ * 

+ * @author Andrzej Kapolka

+ * @author Joshua Slack

+ */

+

+public final class Ring implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+    

+    private Vector3f center, up;

+    private float innerRadius, outerRadius;

+    private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f();

+

+    /**

+     * Constructor creates a new <code>Ring</code> lying on the XZ plane,

+     * centered at the origin, with an inner radius of zero and an outer radius

+     * of one (a unit disk).

+     */

+    public Ring() {

+        center = new Vector3f();

+        up = Vector3f.UNIT_Y.clone();

+        innerRadius = 0f;

+        outerRadius = 1f;

+    }

+

+    /**

+     * Constructor creates a new <code>Ring</code> with defined center point,

+     * up vector, and inner and outer radii.

+     * 

+     * @param center

+     *            the center of the ring.

+     * @param up

+     *            the unit up vector defining the ring's orientation.

+     * @param innerRadius

+     *            the ring's inner radius.

+     * @param outerRadius

+     *            the ring's outer radius.

+     */

+    public Ring(Vector3f center, Vector3f up, float innerRadius,

+            float outerRadius) {

+        this.center = center;

+        this.up = up;

+        this.innerRadius = innerRadius;

+        this.outerRadius = outerRadius;

+    }

+

+    /**

+     * <code>getCenter</code> returns the center of the ring.

+     * 

+     * @return the center of the ring.

+     */

+    public Vector3f getCenter() {

+        return center;

+    }

+

+    /**

+     * <code>setCenter</code> sets the center of the ring.

+     * 

+     * @param center

+     *            the center of the ring.

+     */

+    public void setCenter(Vector3f center) {

+        this.center = center;

+    }

+

+    /**

+     * <code>getUp</code> returns the ring's up vector.

+     * 

+     * @return the ring's up vector.

+     */

+    public Vector3f getUp() {

+        return up;

+    }

+

+    /**

+     * <code>setUp</code> sets the ring's up vector.

+     * 

+     * @param up

+     *            the ring's up vector.

+     */

+    public void setUp(Vector3f up) {

+        this.up = up;

+    }

+

+    /**

+     * <code>getInnerRadius</code> returns the ring's inner radius.

+     * 

+     * @return the ring's inner radius.

+     */

+    public float getInnerRadius() {

+        return innerRadius;

+    }

+

+    /**

+     * <code>setInnerRadius</code> sets the ring's inner radius.

+     * 

+     * @param innerRadius

+     *            the ring's inner radius.

+     */

+    public void setInnerRadius(float innerRadius) {

+        this.innerRadius = innerRadius;

+    }

+

+    /**

+     * <code>getOuterRadius</code> returns the ring's outer radius.

+     * 

+     * @return the ring's outer radius.

+     */

+    public float getOuterRadius() {

+        return outerRadius;

+    }

+

+    /**

+     * <code>setOuterRadius</code> sets the ring's outer radius.

+     * 

+     * @param outerRadius

+     *            the ring's outer radius.

+     */

+    public void setOuterRadius(float outerRadius) {

+        this.outerRadius = outerRadius;

+    }

+

+    /**

+     * 

+     * <code>random</code> returns a random point within the ring.

+     * 

+     * @return a random point within the ring.

+     */

+    public Vector3f random() {

+        return random(null);

+    }

+

+    /**

+     * 

+     * <code>random</code> returns a random point within the ring.

+     * 

+     * @param result Vector to store result in

+     * @return a random point within the ring.

+     */

+    public Vector3f random(Vector3f result) {

+        if (result == null) {

+            result = new Vector3f();

+        }

+        

+        // compute a random radius according to the ring area distribution

+        float inner2 = innerRadius * innerRadius, outer2 = outerRadius

+                * outerRadius, r = FastMath.sqrt(inner2

+                + FastMath.nextRandomFloat() * (outer2 - inner2)), theta = FastMath

+                .nextRandomFloat()

+                * FastMath.TWO_PI;

+        up.cross(Vector3f.UNIT_X, b1);

+        if (b1.lengthSquared() < FastMath.FLT_EPSILON) {

+            up.cross(Vector3f.UNIT_Y, b1);

+        }

+        b1.normalizeLocal();

+        up.cross(b1, b2);

+        result.set(b1).multLocal(r * FastMath.cos(theta)).addLocal(center);

+        result.scaleAdd(r * FastMath.sin(theta), b2, result);

+        return result;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(center, "center", Vector3f.ZERO);

+        capsule.write(up, "up", Vector3f.UNIT_Z);

+        capsule.write(innerRadius, "innerRadius", 0f);

+        capsule.write(outerRadius, "outerRadius", 1f);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        center = (Vector3f) capsule.readSavable("center",

+                Vector3f.ZERO.clone());

+        up = (Vector3f) capsule

+                .readSavable("up", Vector3f.UNIT_Z.clone());

+        innerRadius = capsule.readFloat("innerRadius", 0f);

+        outerRadius = capsule.readFloat("outerRadius", 1f);

+    }

+

+    @Override

+    public Ring clone() {

+        try {

+            Ring r = (Ring) super.clone();

+            r.center = center.clone();

+            r.up = up.clone();

+            return r;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/math/Spline.java b/engine/src/core/com/jme3/math/Spline.java
new file mode 100644
index 0000000..b28a797
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Spline.java
@@ -0,0 +1,447 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author Nehon
+ */
+public class Spline implements Savable {
+
+    public enum SplineType {
+        Linear,
+        CatmullRom,
+        Bezier,
+        Nurb
+    }
+    
+    private List<Vector3f> controlPoints = new ArrayList<Vector3f>();
+    private List<Float> knots;				//knots of NURBS spline
+    private float[] weights;				//weights of NURBS spline
+    private int basisFunctionDegree;		//degree of NURBS spline basis function (computed automatically)
+    private boolean cycle;
+    private List<Float> segmentsLength;
+    private float totalLength;
+    private List<Vector3f> CRcontrolPoints;
+    private float curveTension = 0.5f;
+    private SplineType type = SplineType.CatmullRom;
+
+    public Spline() {
+    }
+
+    /**
+     * Create a spline
+     * @param splineType the type of the spline @see {SplineType}
+     * @param controlPoints an array of vector to use as control points of the spline
+     * If the type of the curve is Bezier curve the control points should be provided
+     * in the appropriate way. Each point 'p' describing control position in the scene
+     * should be surrounded by two handler points. This applies to every point except
+     * for the border points of the curve, who should have only one handle point.
+     * The pattern should be as follows:
+     * P0 - H0  :  H1 - P1 - H1  :  ...  :  Hn - Pn
+     * 
+     * n is the amount of 'P' - points.
+     * @param curveTension the tension of the spline
+     * @param cycle true if the spline cycle.
+     */
+    public Spline(SplineType splineType, Vector3f[] controlPoints, float curveTension, boolean cycle) {
+    	if(splineType==SplineType.Nurb) {
+    		throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
+    	}
+        for (int i = 0; i < controlPoints.length; i++) {
+            Vector3f vector3f = controlPoints[i];
+            this.controlPoints.add(vector3f);
+        }
+        type = splineType;
+        this.curveTension = curveTension;
+        this.cycle = cycle;
+        this.computeTotalLentgh();
+    }
+
+    /**
+     * Create a spline
+     * @param splineType the type of the spline @see {SplineType}
+     * @param controlPoints a list of vector to use as control points of the spline
+     * If the type of the curve is Bezier curve the control points should be provided
+     * in the appropriate way. Each point 'p' describing control position in the scene
+     * should be surrounded by two handler points. This applies to every point except
+     * for the border points of the curve, who should have only one handle point.
+     * The pattern should be as follows:
+     * P0 - H0  :  H1 - P1 - H1  :  ...  :  Hn - Pn
+     * 
+     * n is the amount of 'P' - points.
+     * @param curveTension the tension of the spline
+     * @param cycle true if the spline cycle.
+     */
+    public Spline(SplineType splineType, List<Vector3f> controlPoints, float curveTension, boolean cycle) {
+    	if(splineType==SplineType.Nurb) {
+    		throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
+    	}
+        type = splineType;
+        this.controlPoints.addAll(controlPoints);
+        this.curveTension = curveTension;
+        this.cycle = cycle;
+        this.computeTotalLentgh();
+    }
+    
+    /**
+     * Create a NURBS spline. A spline type is automatically set to SplineType.Nurb.
+     * The cycle is set to <b>false</b> by default.
+     * @param controlPoints a list of vector to use as control points of the spline
+	 * @param nurbKnots the nurb's spline knots
+     */
+    public Spline(List<Vector4f> controlPoints, List<Float> nurbKnots) {
+    	//input data control
+    	for(int i=0;i<nurbKnots.size()-1;++i) {
+    		if(nurbKnots.get(i)>nurbKnots.get(i+1)) {
+    			throw new IllegalArgumentException("The knots values cannot decrease!");
+    		}
+    	}
+
+    	//storing the data
+        type = SplineType.Nurb;
+        this.weights = new float[controlPoints.size()];
+        this.knots = nurbKnots;
+        this.basisFunctionDegree = nurbKnots.size() - weights.length;
+        for(int i=0;i<controlPoints.size();++i) {
+        	Vector4f controlPoint = controlPoints.get(i);
+        	this.controlPoints.add(new Vector3f(controlPoint.x, controlPoint.y, controlPoint.z));
+        	this.weights[i] = controlPoint.w;
+        }
+        CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
+        this.computeTotalLentgh();
+    }
+
+    private void initCatmullRomWayPoints(List<Vector3f> list) {
+        if (CRcontrolPoints == null) {
+            CRcontrolPoints = new ArrayList<Vector3f>();
+        } else {
+            CRcontrolPoints.clear();
+        }
+        int nb = list.size() - 1;
+
+        if (cycle) {
+            CRcontrolPoints.add(list.get(list.size() - 2));
+        } else {
+            CRcontrolPoints.add(list.get(0).subtract(list.get(1).subtract(list.get(0))));
+        }
+
+        for (Iterator<Vector3f> it = list.iterator(); it.hasNext();) {
+            Vector3f vector3f = it.next();
+            CRcontrolPoints.add(vector3f);
+        }
+        if (cycle) {
+            CRcontrolPoints.add(list.get(1));
+        } else {
+            CRcontrolPoints.add(list.get(nb).add(list.get(nb).subtract(list.get(nb - 1))));
+        }
+
+    }
+
+    /**
+     * Adds a controlPoint to the spline
+     * @param controlPoint a position in world space
+     */
+    public void addControlPoint(Vector3f controlPoint) {
+        if (controlPoints.size() > 2 && this.cycle) {
+            controlPoints.remove(controlPoints.size() - 1);
+        }
+        controlPoints.add(controlPoint);
+        if (controlPoints.size() >= 2 && this.cycle) {
+            controlPoints.add(controlPoints.get(0));
+        }
+        if (controlPoints.size() > 1) {
+            this.computeTotalLentgh();
+        }
+    }
+
+    /**
+     * remove the controlPoint from the spline
+     * @param controlPoint the controlPoint to remove
+     */
+    public void removeControlPoint(Vector3f controlPoint) {
+        controlPoints.remove(controlPoint);
+        if (controlPoints.size() > 1) {
+            this.computeTotalLentgh();
+        }
+    }
+    
+    public void clearControlPoints(){
+        controlPoints.clear();
+        totalLength = 0;
+    }
+
+    /**
+     * This method computes the total length of the curve.
+     */
+    private void computeTotalLentgh() {
+        totalLength = 0;
+        float l = 0;
+        if (segmentsLength == null) {
+            segmentsLength = new ArrayList<Float>();
+        } else {
+            segmentsLength.clear();
+        }
+        if (type == SplineType.Linear) {
+            if (controlPoints.size() > 1) {
+                for (int i = 0; i < controlPoints.size() - 1; i++) {
+                    l = controlPoints.get(i + 1).subtract(controlPoints.get(i)).length();
+                    segmentsLength.add(l);
+                    totalLength += l;
+                }
+            }
+        } else if(type == SplineType.Bezier) { 
+        	this.computeBezierLength();
+        } else if(type == SplineType.Nurb) {
+        	this.computeNurbLength();
+        } else {
+            this.initCatmullRomWayPoints(controlPoints);
+            this.computeCatmulLength();
+        }
+    }
+
+    /**
+     * This method computes the Catmull Rom curve length.
+     */
+    private void computeCatmulLength() {
+        float l = 0;
+        if (controlPoints.size() > 1) {
+            for (int i = 0; i < controlPoints.size() - 1; i++) {
+                l = FastMath.getCatmullRomP1toP2Length(CRcontrolPoints.get(i),
+                        CRcontrolPoints.get(i + 1), CRcontrolPoints.get(i + 2), CRcontrolPoints.get(i + 3), 0, 1, curveTension);
+                segmentsLength.add(l);
+                totalLength += l;
+            }
+        }
+    }
+    
+    /**
+     * This method calculates the Bezier curve length.
+     */
+    private void computeBezierLength() {
+    	float l = 0;
+        if (controlPoints.size() > 1) {
+            for (int i = 0; i < controlPoints.size() - 1; i+=3) {
+                l = FastMath.getBezierP1toP2Length(controlPoints.get(i),
+                		controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3));
+                segmentsLength.add(l);
+                totalLength += l;
+            }
+        }
+    }
+    
+    /**
+     * This method calculates the NURB curve length.
+     */
+    private void computeNurbLength() {
+    	//TODO: implement
+    }
+
+    /**
+     * Iterpolate a position on the spline
+     * @param value a value from 0 to 1 that represent the postion between the curent control point and the next one
+     * @param currentControlPoint the current control point
+     * @param store a vector to store the result (use null to create a new one that will be returned by the method)
+     * @return the position
+     */
+    public Vector3f interpolate(float value, int currentControlPoint, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+        switch (type) {
+            case CatmullRom:
+                FastMath.interpolateCatmullRom(value, curveTension, CRcontrolPoints.get(currentControlPoint), CRcontrolPoints.get(currentControlPoint + 1), CRcontrolPoints.get(currentControlPoint + 2), CRcontrolPoints.get(currentControlPoint + 3), store);
+                break;
+            case Linear:
+                FastMath.interpolateLinear(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), store);
+                break;
+            case Bezier:
+            	FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store);
+            	break;
+            case Nurb:
+            	CurveAndSurfaceMath.interpolateNurbs(value, this, store);
+            	break;
+            default:
+                break;
+        }
+        return store;
+    }
+
+    /**
+     * returns the curve tension
+     */
+    public float getCurveTension() {
+        return curveTension;
+    }
+
+    /**
+     * sets the curve tension
+     *
+     * @param curveTension the tension
+     */
+    public void setCurveTension(float curveTension) {
+        this.curveTension = curveTension;
+        if(type==SplineType.CatmullRom) {
+        	this.computeTotalLentgh();
+        }
+    }
+
+    /**
+     * returns true if the spline cycle
+     */
+    public boolean isCycle() {
+        return cycle;
+    }
+
+    /**
+     * set to true to make the spline cycle
+     * @param cycle
+     */
+    public void setCycle(boolean cycle) {
+    	if(type!=SplineType.Nurb) {
+    		if (controlPoints.size() >= 2) {
+    			if (this.cycle && !cycle) {
+    				controlPoints.remove(controlPoints.size() - 1);
+    			}
+    			if (!this.cycle && cycle) {
+    				controlPoints.add(controlPoints.get(0));
+    			}
+    			this.cycle = cycle;
+    			this.computeTotalLentgh();
+    		} else {
+    			this.cycle = cycle;
+    		}
+    	}
+    }
+
+    /**
+     * return the total lenght of the spline
+     */
+    public float getTotalLength() {
+        return totalLength;
+    }
+
+    /**
+     * return the type of the spline
+     */
+    public SplineType getType() {
+        return type;
+    }
+
+    /**
+     * Sets the type of the spline
+     * @param type
+     */
+    public void setType(SplineType type) {
+        this.type = type;
+        this.computeTotalLentgh();
+    }
+
+    /**
+     * returns this spline control points
+     */
+    public List<Vector3f> getControlPoints() {
+        return controlPoints;
+    }
+
+    /**
+     * returns a list of float representing the segments lenght
+     */
+    public List<Float> getSegmentsLength() {
+        return segmentsLength;
+    }
+    
+    //////////// NURBS getters /////////////////////
+    
+	/**
+	 * This method returns the minimum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
+	 * type - NPE will be thrown.
+	 * @return the minimum nurb curve knot value
+	 */
+    public float getMinNurbKnot() {
+    	return knots.get(basisFunctionDegree - 1);
+    }
+    
+    /**
+	 * This method returns the maximum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
+	 * type - NPE will be thrown.
+	 * @return the maximum nurb curve knot value
+	 */
+    public float getMaxNurbKnot() {
+    	return knots.get(weights.length);
+    }
+    
+    /**
+     * This method returns NURBS' spline knots.
+     * @return NURBS' spline knots
+     */
+    public List<Float> getKnots() {
+		return knots;
+	}
+    
+    /**
+     * This method returns NURBS' spline weights.
+     * @return NURBS' spline weights
+     */
+    public float[] getWeights() {
+		return weights;
+	}
+    
+    /**
+     * This method returns NURBS' spline basis function degree.
+     * @return NURBS' spline basis function degree
+     */
+    public int getBasisFunctionDegree() {
+		return basisFunctionDegree;
+	}
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
+        oc.write(type, "type", SplineType.CatmullRom);
+        float list[] = new float[segmentsLength.size()];
+        for (int i = 0; i < segmentsLength.size(); i++) {
+            list[i] = segmentsLength.get(i);
+        }
+        oc.write(list, "segmentsLength", null);
+
+        oc.write(totalLength, "totalLength", 0);
+        oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null);
+        oc.write(curveTension, "curveTension", 0.5f);
+        oc.write(cycle, "cycle", false);
+        oc.writeSavableArrayList((ArrayList<Float>)knots, "knots", null);
+        oc.write(weights, "weights", null);
+        oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+
+        controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("wayPoints", null);
+        float list[] = in.readFloatArray("segmentsLength", null);
+        if (list != null) {
+            segmentsLength = new ArrayList<Float>();
+            for (int i = 0; i < list.length; i++) {
+                segmentsLength.add(new Float(list[i]));
+            }
+        }
+        type = in.readEnum("pathSplineType", SplineType.class, SplineType.CatmullRom);
+        totalLength = in.readFloat("totalLength", 0);
+        CRcontrolPoints = (ArrayList<Vector3f>) in.readSavableArrayList("CRControlPoints", null);
+        curveTension = in.readFloat("curveTension", 0.5f);
+        cycle = in.readBoolean("cycle", false);
+        knots = in.readSavableArrayList("knots", null);
+        weights = in.readFloatArray("weights", null);
+        basisFunctionDegree = in.readInt("basisFunctionDegree", 0);
+    }
+}
diff --git a/engine/src/core/com/jme3/math/Transform.java b/engine/src/core/com/jme3/math/Transform.java
new file mode 100644
index 0000000..7ccd847
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Transform.java
@@ -0,0 +1,318 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+

+/**

+ * Started Date: Jul 16, 2004<br><br>

+ * Represents a translation, rotation and scale in one object.

+ * 

+ * @author Jack Lindamood

+ * @author Joshua Slack

+ */

+public final class Transform implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    public static final Transform IDENTITY = new Transform();

+

+    private Quaternion rot = new Quaternion();

+    private Vector3f translation = new Vector3f();

+    private Vector3f scale = new Vector3f(1,1,1);

+

+    public Transform(Vector3f translation, Quaternion rot){

+        this.translation.set(translation);

+        this.rot.set(rot);

+    }

+    

+    public Transform(Vector3f translation, Quaternion rot, Vector3f scale){

+        this(translation, rot);

+        this.scale.set(scale);

+    }

+

+    public Transform(Vector3f translation){

+        this(translation, Quaternion.IDENTITY);

+    }

+

+    public Transform(Quaternion rot){

+        this(Vector3f.ZERO, rot);

+    }

+

+    public Transform(){

+        this(Vector3f.ZERO, Quaternion.IDENTITY);

+    }

+

+    /**

+     * Sets this rotation to the given Quaternion value.

+     * @param rot The new rotation for this matrix.

+     * @return this

+     */

+    public Transform setRotation(Quaternion rot) {

+        this.rot.set(rot);

+        return this;

+    }

+

+    /**

+     * Sets this translation to the given value.

+     * @param trans The new translation for this matrix.

+     * @return this

+     */

+    public Transform setTranslation(Vector3f trans) {

+        this.translation.set(trans);

+        return this;

+    }

+

+    /**

+     * Return the translation vector in this matrix.

+     * @return translation vector.

+     */

+    public Vector3f getTranslation() {

+        return translation;

+    }

+

+    /**

+     * Sets this scale to the given value.

+     * @param scale The new scale for this matrix.

+     * @return this

+     */

+    public Transform setScale(Vector3f scale) {

+        this.scale.set(scale);

+        return this;

+    }

+

+    /**

+     * Sets this scale to the given value.

+     * @param scale The new scale for this matrix.

+     * @return this

+     */

+    public Transform setScale(float scale) {

+        this.scale.set(scale, scale, scale);

+        return this;

+    }

+

+    /**

+     * Return the scale vector in this matrix.

+     * @return scale vector.

+     */

+    public Vector3f getScale() {

+        return scale;

+    }

+

+    /**

+     * Stores this translation value into the given vector3f.  If trans is null, a new vector3f is created to

+     * hold the value.  The value, once stored, is returned.

+     * @param trans The store location for this matrix's translation.

+     * @return The value of this matrix's translation.

+     */

+    public Vector3f getTranslation(Vector3f trans) {

+        if (trans==null) trans=new Vector3f();

+        trans.set(this.translation);

+        return trans;

+    }

+

+    /**

+     * Stores this rotation value into the given Quaternion.  If quat is null, a new Quaternion is created to

+     * hold the value.  The value, once stored, is returned.

+     * @param quat The store location for this matrix's rotation.

+     * @return The value of this matrix's rotation.

+     */

+    public Quaternion getRotation(Quaternion quat) {

+        if (quat==null) quat=new Quaternion();

+        quat.set(rot);

+        return quat;

+    }

+    

+    /**

+     * Return the rotation quaternion in this matrix.

+     * @return rotation quaternion.

+     */

+    public Quaternion getRotation() {

+        return rot;

+    } 

+    

+    /**

+     * Stores this scale value into the given vector3f.  If scale is null, a new vector3f is created to

+     * hold the value.  The value, once stored, is returned.

+     * @param scale The store location for this matrix's scale.

+     * @return The value of this matrix's scale.

+     */

+    public Vector3f getScale(Vector3f scale) {

+        if (scale==null) scale=new Vector3f();

+        scale.set(this.scale);

+        return scale;

+    }

+

+    /**

+     * Sets this matrix to the interpolation between the first matrix and the second by delta amount.

+     * @param t1 The begining transform.

+     * @param t2 The ending transform.

+     * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.

+     */

+    public void interpolateTransforms(Transform t1, Transform t2, float delta) {

+        this.rot.slerp(t1.rot,t2.rot,delta);

+        this.translation.interpolate(t1.translation,t2.translation,delta);

+        this.scale.interpolate(t1.scale,t2.scale,delta);

+    }

+

+    /**

+     * Changes the values of this matrix acording to it's parent.  Very similar to the concept of Node/Spatial transforms.

+     * @param parent The parent matrix.

+     * @return This matrix, after combining.

+     */

+    public Transform combineWithParent(Transform parent) {

+        scale.multLocal(parent.scale);

+//        rot.multLocal(parent.rot);

+        parent.rot.mult(rot, rot);

+

+        // This here, is evil code

+//        parent

+//            .rot

+//            .multLocal(translation)

+//            .multLocal(parent.scale)

+//            .addLocal(parent.translation);

+

+        translation.multLocal(parent.scale);

+        parent

+            .rot

+            .multLocal(translation)

+            .addLocal(parent.translation);

+        return this;

+    }

+

+    /**

+     * Sets this matrix's translation to the given x,y,z values.

+     * @param x This matrix's new x translation.

+     * @param y This matrix's new y translation.

+     * @param z This matrix's new z translation.

+     * @return this

+     */

+    public Transform setTranslation(float x,float y, float z) {

+        translation.set(x,y,z);

+        return this;

+    }

+

+    /**

+     * Sets this matrix's scale to the given x,y,z values.

+     * @param x This matrix's new x scale.

+     * @param y This matrix's new y scale.

+     * @param z This matrix's new z scale.

+     * @return this

+     */

+    public Transform setScale(float x, float y, float z) {

+        scale.set(x,y,z);

+        return this;

+    }

+

+    public Vector3f transformVector(final Vector3f in, Vector3f store){

+        if (store == null)

+            store = new Vector3f();

+

+        // multiply with scale first, then rotate, finally translate (cf.

+        // Eberly)

+        return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation);

+    }

+

+    public Vector3f transformInverseVector(final Vector3f in, Vector3f store){

+        if (store == null)

+            store = new Vector3f();

+

+        // The author of this code should look above and take the inverse of that

+        // But for some reason, they didnt ..

+//        in.subtract(translation, store).divideLocal(scale);

+//        rot.inverse().mult(store, store);

+

+        in.subtract(translation, store);

+        rot.inverse().mult(store, store);

+        store.divideLocal(scale);

+

+        return store;

+    }

+

+    /**

+     * Loads the identity.  Equal to translation=1,1,1 scale=0,0,0 rot=0,0,0,1.

+     */

+    public void loadIdentity() {

+        translation.set(0,0,0);

+        scale.set(1,1,1);

+        rot.set(0,0,0,1);

+    }

+

+    @Override

+    public String toString(){

+        return getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n"

+                                          + "[ " + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "]\n"

+                                          + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]";

+    }

+

+    /**

+     * Sets this matrix to be equal to the given matrix.

+     * @param matrixQuat The matrix to be equal to.

+     * @return this

+     */

+    public Transform set(Transform matrixQuat) {

+        this.translation.set(matrixQuat.translation);

+        this.rot.set(matrixQuat.rot);

+        this.scale.set(matrixQuat.scale);

+        return this;

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(rot, "rot", new Quaternion());

+        capsule.write(translation, "translation", Vector3f.ZERO);

+        capsule.write(scale, "scale", Vector3f.UNIT_XYZ);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        

+        rot = (Quaternion)capsule.readSavable("rot", new Quaternion());

+        translation = (Vector3f)capsule.readSavable("translation", Vector3f.ZERO);

+        scale = (Vector3f)capsule.readSavable("scale", Vector3f.UNIT_XYZ);

+    }

+    

+    @Override

+    public Transform clone() {

+        try {

+            Transform tq = (Transform) super.clone();

+            tq.rot = rot.clone();

+            tq.scale = scale.clone();

+            tq.translation = translation.clone();

+            return tq;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Triangle.java b/engine/src/core/com/jme3/math/Triangle.java
new file mode 100644
index 0000000..6faa53e
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Triangle.java
@@ -0,0 +1,302 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.Savable;

+import java.io.IOException;

+

+/**

+ * <code>Triangle</code> defines an object for containing triangle information.

+ * The triangle is defined by a collection of three {@link Vector3f}

+ * objects.

+ * 

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public class Triangle extends AbstractTriangle implements Savable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+

+    private Vector3f pointa = new Vector3f();

+    private Vector3f pointb = new Vector3f();

+    private Vector3f pointc = new Vector3f();

+    private transient Vector3f center;

+    private transient Vector3f normal;

+    private float projection;

+    private int index;

+

+    public Triangle() {

+    }

+

+    /**

+     * Constructor instantiates a new <Code>Triangle</code> object with the

+     * supplied vectors as the points. It is recommended that the vertices

+     * be supplied in a counter clockwise winding to support normals for a

+     * right handed coordinate system.

+     * @param p1 the first point of the triangle.

+     * @param p2 the second point of the triangle.

+     * @param p3 the third point of the triangle.

+     */

+    public Triangle(Vector3f p1, Vector3f p2, Vector3f p3) {

+        pointa.set(p1);

+        pointb.set(p2);

+        pointc.set(p3);

+    }

+

+    /**

+     *

+     * <code>get</code> retrieves a point on the triangle denoted by the index

+     * supplied.

+     * @param i the index of the point.

+     * @return the point.

+     */

+    public Vector3f get(int i) {

+        switch (i) {

+            case 0:

+                return pointa;

+            case 1:

+                return pointb;

+            case 2:

+                return pointc;

+            default:

+                return null;

+        }

+    }

+

+    public Vector3f get1() {

+        return pointa;

+    }

+

+    public Vector3f get2() {

+        return pointb;

+    }

+

+    public Vector3f get3() {

+        return pointc;

+    }

+

+    /**

+     *

+     * <code>set</code> sets one of the triangle's points to that specified as

+     * a parameter.

+     * @param i the index to place the point.

+     * @param point the point to set.

+     */

+    public void set(int i, Vector3f point) {

+        switch (i) {

+            case 0:

+                pointa.set(point);

+                break;

+            case 1:

+                pointb.set(point);

+                break;

+            case 2:

+                pointc.set(point);

+                break;

+        }

+    }

+

+    /**

+     *

+     * <code>set</code> sets one of the triangle's points to that specified as

+     * a parameter.

+     * @param i the index to place the point.

+     */

+    public void set(int i, float x, float y, float z) {

+        switch (i) {

+            case 0:

+                pointa.set(x, y, z);

+                break;

+            case 1:

+                pointb.set(x, y, z);

+                break;

+            case 2:

+                pointc.set(x, y, z);

+                break;

+        }

+    }

+

+    public void set1(Vector3f v) {

+        pointa.set(v);

+    }

+

+    public void set2(Vector3f v) {

+        pointb.set(v);

+    }

+

+    public void set3(Vector3f v) {

+        pointc.set(v);

+    }

+

+    public void set(Vector3f v1, Vector3f v2, Vector3f v3) {

+        pointa.set(v1);

+        pointb.set(v2);

+        pointc.set(v3);

+    }

+

+    /**

+     * calculateCenter finds the average point of the triangle. 

+     *

+     */

+    public void calculateCenter() {

+        if (center == null) {

+            center = new Vector3f(pointa);

+        } else {

+            center.set(pointa);

+        }

+        center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD);

+    }

+

+    /**

+     * calculateNormal generates the normal for this triangle

+     *

+     */

+    public void calculateNormal() {

+        if (normal == null) {

+            normal = new Vector3f(pointb);

+        } else {

+            normal.set(pointb);

+        }

+        normal.subtractLocal(pointa).crossLocal(pointc.x - pointa.x, pointc.y - pointa.y, pointc.z - pointa.z);

+        normal.normalizeLocal();

+    }

+

+    /**

+     * obtains the center point of this triangle (average of the three triangles)

+     * @return the center point.

+     */

+    public Vector3f getCenter() {

+        if (center == null) {

+            calculateCenter();

+        }

+        return center;

+    }

+

+    /**

+     * sets the center point of this triangle (average of the three triangles)

+     * @param center the center point.

+     */

+    public void setCenter(Vector3f center) {

+        this.center = center;

+    }

+

+    /**

+     * obtains the unit length normal vector of this triangle, if set or

+     * calculated

+     * 

+     * @return the normal vector

+     */

+    public Vector3f getNormal() {

+        if (normal == null) {

+            calculateNormal();

+        }

+        return normal;

+    }

+

+    /**

+     * sets the normal vector of this triangle (to conform, must be unit length)

+     * @param normal the normal vector.

+     */

+    public void setNormal(Vector3f normal) {

+        this.normal = normal;

+    }

+

+    /**

+     * obtains the projection of the vertices relative to the line origin.

+     * @return the projection of the triangle.

+     */

+    public float getProjection() {

+        return this.projection;

+    }

+

+    /**

+     * sets the projection of the vertices relative to the line origin.

+     * @param projection the projection of the triangle.

+     */

+    public void setProjection(float projection) {

+        this.projection = projection;

+    }

+

+    /**

+     * obtains an index that this triangle represents if it is contained in a OBBTree.

+     * @return the index in an OBBtree

+     */

+    public int getIndex() {

+        return index;

+    }

+

+    /**

+     * sets an index that this triangle represents if it is contained in a OBBTree.

+     * @param index the index in an OBBtree

+     */

+    public void setIndex(int index) {

+        this.index = index;

+    }

+

+    public static Vector3f computeTriangleNormal(Vector3f v1, Vector3f v2, Vector3f v3, Vector3f store) {

+        if (store == null) {

+            store = new Vector3f(v2);

+        } else {

+            store.set(v2);

+        }

+

+        store.subtractLocal(v1).crossLocal(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z);

+        return store.normalizeLocal();

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        e.getCapsule(this).write(pointa, "pointa", Vector3f.ZERO);

+        e.getCapsule(this).write(pointb, "pointb", Vector3f.ZERO);

+        e.getCapsule(this).write(pointc, "pointc", Vector3f.ZERO);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        pointa = (Vector3f) e.getCapsule(this).readSavable("pointa", Vector3f.ZERO.clone());

+        pointb = (Vector3f) e.getCapsule(this).readSavable("pointb", Vector3f.ZERO.clone());

+        pointc = (Vector3f) e.getCapsule(this).readSavable("pointc", Vector3f.ZERO.clone());

+    }

+

+    @Override

+    public Triangle clone() {

+        try {

+            Triangle t = (Triangle) super.clone();

+            t.pointa = pointa.clone();

+            t.pointb = pointb.clone();

+            t.pointc = pointc.clone();

+            return t;

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError();

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/math/Vector2f.java b/engine/src/core/com/jme3/math/Vector2f.java
new file mode 100644
index 0000000..c2d8c6f
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector2f.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.export.*;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.logging.Logger;
+
+/**
+ * <code>Vector2f</code> defines a Vector for a two float value vector.
+ * 
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Vector2f implements Savable, Cloneable, java.io.Serializable {
+
+    static final long serialVersionUID = 1;
+    private static final Logger logger = Logger.getLogger(Vector2f.class.getName());
+
+    public static final Vector2f ZERO = new Vector2f(0f, 0f);
+    public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
+    
+    /**
+     * the x value of the vector.
+     */
+    public float x;
+    /**
+     * the y value of the vector.
+     */
+    public float y;
+
+    /**
+     * Creates a Vector2f with the given initial x and y values.
+     * 
+     * @param x
+     *            The x value of this Vector2f.
+     * @param y
+     *            The y value of this Vector2f.
+     */
+    public Vector2f(float x, float y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0).
+     */
+    public Vector2f() {
+        x = y = 0;
+    }
+
+    /**
+     * Creates a new Vector2f that contains the passed vector's information
+     * 
+     * @param vector2f
+     *            The vector to copy
+     */
+    public Vector2f(Vector2f vector2f) {
+        this.x = vector2f.x;
+        this.y = vector2f.y;
+    }
+
+    /**
+     * set the x and y values of the vector
+     * 
+     * @param x
+     *            the x value of the vector.
+     * @param y
+     *            the y value of the vector.
+     * @return this vector
+     */
+    public Vector2f set(float x, float y) {
+        this.x = x;
+        this.y = y;
+        return this;
+    }
+
+    /**
+     * set the x and y values of the vector from another vector
+     * 
+     * @param vec
+     *            the vector to copy from
+     * @return this vector
+     */
+    public Vector2f set(Vector2f vec) {
+        this.x = vec.x;
+        this.y = vec.y;
+        return this;
+    }
+
+    /**
+     * <code>add</code> adds a provided vector to this vector creating a
+     * resultant vector which is returned. If the provided vector is null, null
+     * is returned.
+     * 
+     * @param vec
+     *            the vector to add to this.
+     * @return the resultant vector.
+     */
+    public Vector2f add(Vector2f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        return new Vector2f(x + vec.x, y + vec.y);
+    }
+
+    /**
+     * <code>addLocal</code> adds a provided vector to this vector internally,
+     * and returns a handle to this vector for easy chaining of calls. If the
+     * provided vector is null, null is returned.
+     * 
+     * @param vec
+     *            the vector to add to this vector.
+     * @return this
+     */
+    public Vector2f addLocal(Vector2f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x += vec.x;
+        y += vec.y;
+        return this;
+    }
+
+    /**
+     * <code>addLocal</code> adds the provided values to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     * 
+     * @param addX
+     *            value to add to x
+     * @param addY
+     *            value to add to y
+     * @return this
+     */
+    public Vector2f addLocal(float addX, float addY) {
+        x += addX;
+        y += addY;
+        return this;
+    }
+
+    /**
+     * <code>add</code> adds this vector by <code>vec</code> and stores the
+     * result in <code>result</code>.
+     * 
+     * @param vec
+     *            The vector to add.
+     * @param result
+     *            The vector to store the result in.
+     * @return The result vector, after adding.
+     */
+    public Vector2f add(Vector2f vec, Vector2f result) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        if (result == null)
+            result = new Vector2f();
+        result.x = x + vec.x;
+        result.y = y + vec.y;
+        return result;
+    }
+
+    /**
+     * <code>dot</code> calculates the dot product of this vector with a
+     * provided vector. If the provided vector is null, 0 is returned.
+     * 
+     * @param vec
+     *            the vector to dot with this vector.
+     * @return the resultant dot product of this vector and a given vector.
+     */
+    public float dot(Vector2f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, 0 returned.");
+            return 0;
+        }
+        return x * vec.x + y * vec.y;
+    }
+
+    /**
+     * <code>cross</code> calculates the cross product of this vector with a
+     * parameter vector v.
+     * 
+     * @param v
+     *            the vector to take the cross product of with this.
+     * @return the cross product vector.
+     */
+    public Vector3f cross(Vector2f v) {
+        return new Vector3f(0, 0, determinant(v));
+    }
+
+    public float determinant(Vector2f v) {
+        return (x * v.y) - (y * v.x);
+    }
+    
+    /**
+     * Sets this vector to the interpolation by changeAmnt from this to the
+     * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec
+     * 
+     * @param finalVec
+     *            The final vector to interpolate towards
+     * @param changeAmnt
+     *            An amount between 0.0 - 1.0 representing a percentage change
+     *            from this towards finalVec
+     */
+    public Vector2f interpolate(Vector2f finalVec, float changeAmnt) {
+        this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
+        this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
+        return this;
+    }
+
+    /**
+     * Sets this vector to the interpolation by changeAmnt from beginVec to
+     * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+     * 
+     * @param beginVec
+     *            The begining vector (delta=0)
+     * @param finalVec
+     *            The final vector to interpolate towards (delta=1)
+     * @param changeAmnt
+     *            An amount between 0.0 - 1.0 representing a precentage change
+     *            from beginVec towards finalVec
+     */
+    public Vector2f interpolate(Vector2f beginVec, Vector2f finalVec,
+            float changeAmnt) {
+        this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
+        this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
+        return this;
+    }
+
+    /**
+     * Check a vector... if it is null or its floats are NaN or infinite, return
+     * false. Else return true.
+     * 
+     * @param vector
+     *            the vector to check
+     * @return true or false as stated above.
+     */
+    public static boolean isValidVector(Vector2f vector) {
+      if (vector == null) return false;
+      if (Float.isNaN(vector.x) ||
+          Float.isNaN(vector.y)) return false;
+      if (Float.isInfinite(vector.x) ||
+          Float.isInfinite(vector.y)) return false;
+      return true;
+    }
+
+    /**
+     * <code>length</code> calculates the magnitude of this vector.
+     * 
+     * @return the length or magnitude of the vector.
+     */
+    public float length() {
+        return FastMath.sqrt(lengthSquared());
+    }
+
+    /**
+     * <code>lengthSquared</code> calculates the squared value of the
+     * magnitude of the vector.
+     * 
+     * @return the magnitude squared of the vector.
+     */
+    public float lengthSquared() {
+        return x * x + y * y;
+    }
+
+    /**
+     * <code>distanceSquared</code> calculates the distance squared between
+     * this vector and vector v.
+     *
+     * @param v the second vector to determine the distance squared.
+     * @return the distance squared between the two vectors.
+     */
+    public float distanceSquared(Vector2f v) {
+        double dx = x - v.x;
+        double dy = y - v.y;
+        return (float) (dx * dx + dy * dy);
+    }
+
+    /**
+     * <code>distanceSquared</code> calculates the distance squared between
+     * this vector and vector v.
+     *
+     * @param otherX The X coordinate of the v vector
+     * @param otherY The Y coordinate of the v vector
+     * @return the distance squared between the two vectors.
+     */
+    public float distanceSquared(float otherX, float otherY) {
+        double dx = x - otherX;
+        double dy = y - otherY;
+        return (float) (dx * dx + dy * dy);
+    }
+
+    /**
+     * <code>distance</code> calculates the distance between this vector and
+     * vector v.
+     *
+     * @param v the second vector to determine the distance.
+     * @return the distance between the two vectors.
+     */
+    public float distance(Vector2f v) {
+        return FastMath.sqrt(distanceSquared(v));
+    }
+
+    /**
+     * <code>mult</code> multiplies this vector by a scalar. The resultant
+     * vector is returned.
+     * 
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return the new vector.
+     */
+    public Vector2f mult(float scalar) {
+        return new Vector2f(x * scalar, y * scalar);
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls.
+     * 
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return this
+     */
+    public Vector2f multLocal(float scalar) {
+        x *= scalar;
+        y *= scalar;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     * 
+     * @param vec
+     *            the vector to mult to this vector.
+     * @return this
+     */
+    public Vector2f multLocal(Vector2f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x *= vec.x;
+        y *= vec.y;
+        return this;
+    }
+
+    /**
+     * Multiplies this Vector2f's x and y by the scalar and stores the result in
+     * product. The result is returned for chaining. Similar to
+     * product=this*scalar;
+     * 
+     * @param scalar
+     *            The scalar to multiply by.
+     * @param product
+     *            The vector2f to store the result in.
+     * @return product, after multiplication.
+     */
+    public Vector2f mult(float scalar, Vector2f product) {
+        if (null == product) {
+            product = new Vector2f();
+        }
+
+        product.x = x * scalar;
+        product.y = y * scalar;
+        return product;
+    }
+
+    /**
+     * <code>divide</code> divides the values of this vector by a scalar and
+     * returns the result. The values of this vector remain untouched.
+     * 
+     * @param scalar
+     *            the value to divide this vectors attributes by.
+     * @return the result <code>Vector</code>.
+     */
+    public Vector2f divide(float scalar) {
+        return new Vector2f(x / scalar, y / scalar);
+    }
+
+    /**
+     * <code>divideLocal</code> divides this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls. Dividing
+     * by zero will result in an exception.
+     * 
+     * @param scalar
+     *            the value to divides this vector by.
+     * @return this
+     */
+    public Vector2f divideLocal(float scalar) {
+        x /= scalar;
+        y /= scalar;
+        return this;
+    }
+
+    /**
+     * <code>negate</code> returns the negative of this vector. All values are
+     * negated and set to a new vector.
+     * 
+     * @return the negated vector.
+     */
+    public Vector2f negate() {
+        return new Vector2f(-x, -y);
+    }
+
+    /**
+     * <code>negateLocal</code> negates the internal values of this vector.
+     * 
+     * @return this.
+     */
+    public Vector2f negateLocal() {
+        x = -x;
+        y = -y;
+        return this;
+    }
+
+    /**
+     * <code>subtract</code> subtracts the values of a given vector from those
+     * of this vector creating a new vector object. If the provided vector is
+     * null, an exception is thrown.
+     * 
+     * @param vec
+     *            the vector to subtract from this vector.
+     * @return the result vector.
+     */
+    public Vector2f subtract(Vector2f vec) {
+        return subtract(vec, null);
+    }
+
+    /**
+     * <code>subtract</code> subtracts the values of a given vector from those
+     * of this vector storing the result in the given vector object. If the
+     * provided vector is null, an exception is thrown.
+     * 
+     * @param vec
+     *            the vector to subtract from this vector.
+     * @param store
+     *            the vector to store the result in. It is safe for this to be
+     *            the same as vec. If null, a new vector is created.
+     * @return the result vector.
+     */
+    public Vector2f subtract(Vector2f vec, Vector2f store) {
+        if (store == null)
+            store = new Vector2f();
+        store.x = x - vec.x;
+        store.y = y - vec.y;
+        return store;
+    }
+
+    /**
+     * <code>subtract</code> subtracts the given x,y values from those of this
+     * vector creating a new vector object.
+     * 
+     * @param valX
+     *            value to subtract from x
+     * @param valY
+     *            value to subtract from y
+     * @return this
+     */
+    public Vector2f subtract(float valX, float valY) {
+        return new Vector2f(x - valX, y - valY);
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     * 
+     * @param vec
+     *            the vector to subtract
+     * @return this
+     */
+    public Vector2f subtractLocal(Vector2f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x -= vec.x;
+        y -= vec.y;
+        return this;
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts the provided values from this
+     * vector internally, and returns a handle to this vector for easy chaining
+     * of calls.
+     * 
+     * @param valX
+     *            value to subtract from x
+     * @param valY
+     *            value to subtract from y
+     * @return this
+     */
+    public Vector2f subtractLocal(float valX, float valY) {
+        x -= valX;
+        y -= valY;
+        return this;
+    }
+
+    /**
+     * <code>normalize</code> returns the unit vector of this vector.
+     * 
+     * @return unit vector of this vector.
+     */
+    public Vector2f normalize() {
+        float length = length();
+        if (length != 0) {
+            return divide(length);
+        }
+
+        return divide(1);
+    }
+
+    /**
+     * <code>normalizeLocal</code> makes this vector into a unit vector of
+     * itself.
+     * 
+     * @return this.
+     */
+    public Vector2f normalizeLocal() {
+        float length = length();
+        if (length != 0) {
+            return divideLocal(length);
+        }
+
+        return divideLocal(1);
+    }
+
+    /**
+     * <code>smallestAngleBetween</code> returns (in radians) the minimum
+     * angle between two vectors. It is assumed that both this vector and the
+     * given vector are unit vectors (iow, normalized).
+     * 
+     * @param otherVector
+     *            a unit vector to find the angle against
+     * @return the angle in radians.
+     */
+    public float smallestAngleBetween(Vector2f otherVector) {
+        float dotProduct = dot(otherVector);
+        float angle = FastMath.acos(dotProduct);
+        return angle;
+    }
+
+    /**
+     * <code>angleBetween</code> returns (in radians) the angle required to
+     * rotate a ray represented by this vector to lie colinear to a ray
+     * described by the given vector. It is assumed that both this vector and
+     * the given vector are unit vectors (iow, normalized).
+     * 
+     * @param otherVector
+     *            the "destination" unit vector
+     * @return the angle in radians.
+     */
+    public float angleBetween(Vector2f otherVector) {
+        float angle = FastMath.atan2(otherVector.y, otherVector.x)
+                - FastMath.atan2(y, x);
+        return angle;
+    }
+    
+    public float getX() {
+        return x;
+    }
+
+    public Vector2f setX(float x) {
+        this.x = x;
+        return this;
+    }
+
+    public float getY() {
+        return y;
+    }
+
+    public Vector2f setY(float y) {
+        this.y = y;
+        return this;
+    }
+    /**
+     * <code>getAngle</code> returns (in radians) the angle represented by
+     * this Vector2f as expressed by a conversion from rectangular coordinates (<code>x</code>,&nbsp;<code>y</code>)
+     * to polar coordinates (r,&nbsp;<i>theta</i>).
+     * 
+     * @return the angle in radians. [-pi, pi)
+     */
+    public float getAngle() {
+        return FastMath.atan2(y, x);
+    }
+
+    /**
+     * <code>zero</code> resets this vector's data to zero internally.
+     */
+    public Vector2f zero() {
+        x = y = 0;
+        return this;
+    }
+
+    /**
+     * <code>hashCode</code> returns a unique code for this vector object
+     * based on it's values. If two vectors are logically equivalent, they will
+     * return the same hash code value.
+     * 
+     * @return the hash code value of this vector.
+     */
+    public int hashCode() {
+        int hash = 37;
+        hash += 37 * hash + Float.floatToIntBits(x);
+        hash += 37 * hash + Float.floatToIntBits(y);
+        return hash;
+    }
+
+    @Override
+    public Vector2f clone() {
+        try {
+            return (Vector2f) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(); // can not happen
+        }
+    }
+
+    /**
+     * Saves this Vector2f into the given float[] object.
+     * 
+     * @param floats
+     *            The float[] to take this Vector2f. If null, a new float[2] is
+     *            created.
+     * @return The array, with X, Y float values in that order
+     */
+    public float[] toArray(float[] floats) {
+        if (floats == null) {
+            floats = new float[2];
+        }
+        floats[0] = x;
+        floats[1] = y;
+        return floats;
+    }
+
+    /**
+     * are these two vectors the same? they are is they both have the same x and
+     * y values.
+     * 
+     * @param o
+     *            the object to compare for equality
+     * @return true if they are equal
+     */
+    public boolean equals(Object o) {
+        if (!(o instanceof Vector2f)) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        Vector2f comp = (Vector2f) o;
+        if (Float.compare(x, comp.x) != 0)
+            return false;
+        if (Float.compare(y, comp.y) != 0)
+            return false;
+        return true;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this vector
+     * object. The format of the string is such: com.jme.math.Vector2f
+     * [X=XX.XXXX, Y=YY.YYYY]
+     * 
+     * @return the string representation of this vector.
+     */
+    public String toString() {
+        return "(" + x + ", " + y + ")";
+    }
+
+    /**
+     * Used with serialization. Not to be called manually.
+     * 
+     * @param in
+     *            ObjectInput
+     * @throws IOException
+     * @throws ClassNotFoundException
+     * @see java.io.Externalizable
+     */
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        x = in.readFloat();
+        y = in.readFloat();
+    }
+
+    /**
+     * Used with serialization. Not to be called manually.
+     * 
+     * @param out
+     *            ObjectOutput
+     * @throws IOException
+     * @see java.io.Externalizable
+     */
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeFloat(x);
+        out.writeFloat(y);
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(x, "x", 0);
+        capsule.write(y, "y", 0);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        x = capsule.readFloat("x", 0);
+        y = capsule.readFloat("y", 0);
+    }
+
+    public void rotateAroundOrigin(float angle, boolean cw) {
+        if (cw)
+            angle = -angle;
+        float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y;
+        float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y;
+        x = newX;
+        y = newY;
+    }
+}
diff --git a/engine/src/core/com/jme3/math/Vector3f.java b/engine/src/core/com/jme3/math/Vector3f.java
new file mode 100644
index 0000000..9f6f851
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector3f.java
@@ -0,0 +1,1061 @@
+/*

+ * Copyright (c) 2009-2010 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.math;

+

+import com.jme3.export.*;

+import java.io.IOException;

+import java.util.logging.Logger;

+

+/*

+ * -- Added *Local methods to cut down on object creation - JS

+ */

+

+/**

+ * <code>Vector3f</code> defines a Vector for a three float value tuple.

+ * <code>Vector3f</code> can represent any three dimensional value, such as a

+ * vertex, a normal, etc. Utility methods are also included to aid in

+ * mathematical calculations.

+ *

+ * @author Mark Powell

+ * @author Joshua Slack

+ */

+public final class Vector3f implements Savable, Cloneable, java.io.Serializable {

+

+    static final long serialVersionUID = 1;

+    

+    private static final Logger logger = Logger.getLogger(Vector3f.class.getName());

+

+    public final static Vector3f ZERO = new Vector3f(0, 0, 0);

+    public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN);

+    public final static Vector3f UNIT_X = new Vector3f(1, 0, 0);

+    public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0);

+    public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1);

+    public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1);

+    public final static Vector3f POSITIVE_INFINITY = new Vector3f(

+            Float.POSITIVE_INFINITY,

+            Float.POSITIVE_INFINITY,

+            Float.POSITIVE_INFINITY);

+    public final static Vector3f NEGATIVE_INFINITY = new Vector3f(

+            Float.NEGATIVE_INFINITY,

+            Float.NEGATIVE_INFINITY,

+            Float.NEGATIVE_INFINITY);

+

+    

+	/**

+     * the x value of the vector.

+     */

+    public float x;

+

+    /**

+     * the y value of the vector.

+     */

+    public float y;

+

+    /**

+     * the z value of the vector.

+     */

+    public float z;

+

+    /**

+     * Constructor instantiates a new <code>Vector3f</code> with default

+     * values of (0,0,0).

+     *

+     */

+    public Vector3f() {

+        x = y = z = 0;

+    }

+

+    /**

+     * Constructor instantiates a new <code>Vector3f</code> with provides

+     * values.

+     *

+     * @param x

+     *            the x value of the vector.

+     * @param y

+     *            the y value of the vector.

+     * @param z

+     *            the z value of the vector.

+     */

+    public Vector3f(float x, float y, float z) {

+        this.x = x;

+        this.y = y;

+        this.z = z;

+    }

+

+    /**

+     * Constructor instantiates a new <code>Vector3f</code> that is a copy

+     * of the provided vector

+     * @param copy The Vector3f to copy

+     */

+    public Vector3f(Vector3f copy) {

+        this.set(copy);

+    }

+

+    /**

+     * <code>set</code> sets the x,y,z values of the vector based on passed

+     * parameters.

+     *

+     * @param x

+     *            the x value of the vector.

+     * @param y

+     *            the y value of the vector.

+     * @param z

+     *            the z value of the vector.

+     * @return this vector

+     */

+    public Vector3f set(float x, float y, float z) {

+        this.x = x;

+        this.y = y;

+        this.z = z;

+        return this;

+    }

+

+    /**

+     * <code>set</code> sets the x,y,z values of the vector by copying the

+     * supplied vector.

+     *

+     * @param vect

+     *            the vector to copy.

+     * @return this vector

+     */

+    public Vector3f set(Vector3f vect) {

+        this.x = vect.x;

+        this.y = vect.y;

+        this.z = vect.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>add</code> adds a provided vector to this vector creating a

+     * resultant vector which is returned. If the provided vector is null, null

+     * is returned.

+     *

+     * @param vec

+     *            the vector to add to this.

+     * @return the resultant vector.

+     */

+    public Vector3f add(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        return new Vector3f(x + vec.x, y + vec.y, z + vec.z);

+    }

+

+    /**

+     *

+     * <code>add</code> adds the values of a provided vector storing the

+     * values in the supplied vector.

+     *

+     * @param vec

+     *            the vector to add to this

+     * @param result

+     *            the vector to store the result in

+     * @return result returns the supplied result vector.

+     */

+    public Vector3f add(Vector3f vec, Vector3f result) {

+        result.x = x + vec.x;

+        result.y = y + vec.y;

+        result.z = z + vec.z;

+        return result;

+    }

+

+    /**

+     * <code>addLocal</code> adds a provided vector to this vector internally,

+     * and returns a handle to this vector for easy chaining of calls. If the

+     * provided vector is null, null is returned.

+     *

+     * @param vec

+     *            the vector to add to this vector.

+     * @return this

+     */

+    public Vector3f addLocal(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        x += vec.x;

+        y += vec.y;

+        z += vec.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>add</code> adds the provided values to this vector, creating a

+     * new vector that is then returned.

+     *

+     * @param addX

+     *            the x value to add.

+     * @param addY

+     *            the y value to add.

+     * @param addZ

+     *            the z value to add.

+     * @return the result vector.

+     */

+    public Vector3f add(float addX, float addY, float addZ) {

+        return new Vector3f(x + addX, y + addY, z + addZ);

+    }

+

+    /**

+     * <code>addLocal</code> adds the provided values to this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls.

+     *

+     * @param addX

+     *            value to add to x

+     * @param addY

+     *            value to add to y

+     * @param addZ

+     *            value to add to z

+     * @return this

+     */

+    public Vector3f addLocal(float addX, float addY, float addZ) {

+        x += addX;

+        y += addY;

+        z += addZ;

+        return this;

+    }

+

+    /**

+     *

+     * <code>scaleAdd</code> multiplies this vector by a scalar then adds the

+     * given Vector3f.

+     *

+     * @param scalar

+     *            the value to multiply this vector by.

+     * @param add

+     *            the value to add

+     */

+    public Vector3f scaleAdd(float scalar, Vector3f add) {

+        x = x * scalar + add.x;

+        y = y * scalar + add.y;

+        z = z * scalar + add.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>scaleAdd</code> multiplies the given vector by a scalar then adds

+     * the given vector.

+     *

+     * @param scalar

+     *            the value to multiply this vector by.

+     * @param mult

+     *            the value to multiply the scalar by

+     * @param add

+     *            the value to add

+     */

+    public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) {

+        this.x = mult.x * scalar + add.x;

+        this.y = mult.y * scalar + add.y;

+        this.z = mult.z * scalar + add.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>dot</code> calculates the dot product of this vector with a

+     * provided vector. If the provided vector is null, 0 is returned.

+     *

+     * @param vec

+     *            the vector to dot with this vector.

+     * @return the resultant dot product of this vector and a given vector.

+     */

+    public float dot(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, 0 returned.");

+            return 0;

+        }

+        return x * vec.x + y * vec.y + z * vec.z;

+    }

+

+    /**

+     * <code>cross</code> calculates the cross product of this vector with a

+     * parameter vector v.

+     *

+     * @param v

+     *            the vector to take the cross product of with this.

+     * @return the cross product vector.

+     */

+    public Vector3f cross(Vector3f v) {

+        return cross(v, null);

+    }

+

+    /**

+     * <code>cross</code> calculates the cross product of this vector with a

+     * parameter vector v.  The result is stored in <code>result</code>

+     *

+     * @param v

+     *            the vector to take the cross product of with this.

+     * @param result

+     *            the vector to store the cross product result.

+     * @return result, after recieving the cross product vector.

+     */

+    public Vector3f cross(Vector3f v,Vector3f result) {

+        return cross(v.x, v.y, v.z, result);

+    }

+

+    /**

+     * <code>cross</code> calculates the cross product of this vector with a

+     * parameter vector v.  The result is stored in <code>result</code>

+     *

+     * @param otherX

+     *            x component of the vector to take the cross product of with this.

+     * @param otherY

+     *            y component of the vector to take the cross product of with this.

+     * @param otherZ

+     *            z component of the vector to take the cross product of with this.

+     * @param result

+     *            the vector to store the cross product result.

+     * @return result, after recieving the cross product vector.

+     */

+    public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) {

+        if (result == null) result = new Vector3f();

+        float resX = ((y * otherZ) - (z * otherY)); 

+        float resY = ((z * otherX) - (x * otherZ));

+        float resZ = ((x * otherY) - (y * otherX));

+        result.set(resX, resY, resZ);

+        return result;

+    }

+

+    /**

+     * <code>crossLocal</code> calculates the cross product of this vector

+     * with a parameter vector v.

+     *

+     * @param v

+     *            the vector to take the cross product of with this.

+     * @return this.

+     */

+    public Vector3f crossLocal(Vector3f v) {

+        return crossLocal(v.x, v.y, v.z);

+    }

+

+    /**

+     * <code>crossLocal</code> calculates the cross product of this vector

+     * with a parameter vector v.

+     *

+     * @param otherX

+     *            x component of the vector to take the cross product of with this.

+     * @param otherY

+     *            y component of the vector to take the cross product of with this.

+     * @param otherZ

+     *            z component of the vector to take the cross product of with this.

+     * @return this.

+     */

+    public Vector3f crossLocal(float otherX, float otherY, float otherZ) {

+        float tempx = ( y * otherZ ) - ( z * otherY );

+        float tempy = ( z * otherX ) - ( x * otherZ );

+        z = (x * otherY) - (y * otherX);

+        x = tempx;

+        y = tempy;

+        return this;

+    }

+

+    public Vector3f project(Vector3f other){

+        float n = this.dot(other); // A . B

+        float d = other.lengthSquared(); // |B|^2

+        return new Vector3f(other).normalizeLocal().multLocal(n/d);

+    }

+

+    /**

+     * Returns true if this vector is a unit vector (length() ~= 1),

+     * returns false otherwise.

+     * 

+     * @return true if this vector is a unit vector (length() ~= 1),

+     * or false otherwise.

+     */

+    public boolean isUnitVector(){

+        float len = length();

+        return 0.99f < len && len < 1.01f;

+    }

+

+    /**

+     * <code>length</code> calculates the magnitude of this vector.

+     *

+     * @return the length or magnitude of the vector.

+     */

+    public float length() {

+        return FastMath.sqrt(lengthSquared());

+    }

+

+    /**

+     * <code>lengthSquared</code> calculates the squared value of the

+     * magnitude of the vector.

+     *

+     * @return the magnitude squared of the vector.

+     */

+    public float lengthSquared() {

+        return x * x + y * y + z * z;

+    }

+

+    /**

+     * <code>distanceSquared</code> calculates the distance squared between

+     * this vector and vector v.

+     *

+     * @param v the second vector to determine the distance squared.

+     * @return the distance squared between the two vectors.

+     */

+    public float distanceSquared(Vector3f v) {

+        double dx = x - v.x;

+        double dy = y - v.y;

+        double dz = z - v.z;

+        return (float) (dx * dx + dy * dy + dz * dz);

+    }

+

+    /**

+     * <code>distance</code> calculates the distance between this vector and

+     * vector v.

+     *

+     * @param v the second vector to determine the distance.

+     * @return the distance between the two vectors.

+     */

+    public float distance(Vector3f v) {

+        return FastMath.sqrt(distanceSquared(v));

+    }

+

+    /**

+     *

+     * <code>mult</code> multiplies this vector by a scalar. The resultant

+     * vector is returned.

+     *

+     * @param scalar

+     *            the value to multiply this vector by.

+     * @return the new vector.

+     */

+    public Vector3f mult(float scalar) {

+        return new Vector3f(x * scalar, y * scalar, z * scalar);

+    }

+

+    /**

+     *

+     * <code>mult</code> multiplies this vector by a scalar. The resultant

+     * vector is supplied as the second parameter and returned.

+     *

+     * @param scalar the scalar to multiply this vector by.

+     * @param product the product to store the result in.

+     * @return product

+     */

+    public Vector3f mult(float scalar, Vector3f product) {

+        if (null == product) {

+            product = new Vector3f();

+        }

+

+        product.x = x * scalar;

+        product.y = y * scalar;

+        product.z = z * scalar;

+        return product;

+    }

+

+    /**

+     * <code>multLocal</code> multiplies this vector by a scalar internally,

+     * and returns a handle to this vector for easy chaining of calls.

+     *

+     * @param scalar

+     *            the value to multiply this vector by.

+     * @return this

+     */

+    public Vector3f multLocal(float scalar) {

+        x *= scalar;

+        y *= scalar;

+        z *= scalar;

+        return this;

+    }

+

+    /**

+     * <code>multLocal</code> multiplies a provided vector to this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls. If the provided vector is null, null is returned.

+     *

+     * @param vec

+     *            the vector to mult to this vector.

+     * @return this

+     */

+    public Vector3f multLocal(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        x *= vec.x;

+        y *= vec.y;

+        z *= vec.z;

+        return this;

+    }

+

+    /**

+     * <code>multLocal</code> multiplies this vector by 3 scalars

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls.

+     *

+     * @param x

+     * @param y

+     * @param z

+     * @return this

+     */

+    public Vector3f multLocal(float x, float y, float z) {

+        this.x *= x;

+        this.y *= y;

+        this.z *= z;

+        return this;

+    }

+

+    /**

+     * <code>multLocal</code> multiplies a provided vector to this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls. If the provided vector is null, null is returned.

+     *

+     * @param vec

+     *            the vector to mult to this vector.

+     * @return this

+     */

+    public Vector3f mult(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        return mult(vec, null);

+    }

+

+    /**

+     * <code>multLocal</code> multiplies a provided vector to this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls. If the provided vector is null, null is returned.

+     *

+     * @param vec

+     *            the vector to mult to this vector.

+     * @param store result vector (null to create a new vector)

+     * @return this

+     */

+    public Vector3f mult(Vector3f vec, Vector3f store) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        if (store == null) store = new Vector3f();

+        return store.set(x * vec.x, y * vec.y, z * vec.z);

+    }

+

+

+    /**

+     * <code>divide</code> divides the values of this vector by a scalar and

+     * returns the result. The values of this vector remain untouched.

+     *

+     * @param scalar

+     *            the value to divide this vectors attributes by.

+     * @return the result <code>Vector</code>.

+     */

+    public Vector3f divide(float scalar) {

+        scalar = 1f/scalar;

+        return new Vector3f(x * scalar, y * scalar, z * scalar);

+    }

+

+    /**

+     * <code>divideLocal</code> divides this vector by a scalar internally,

+     * and returns a handle to this vector for easy chaining of calls. Dividing

+     * by zero will result in an exception.

+     *

+     * @param scalar

+     *            the value to divides this vector by.

+     * @return this

+     */

+    public Vector3f divideLocal(float scalar) {

+        scalar = 1f/scalar;

+        x *= scalar;

+        y *= scalar;

+        z *= scalar;

+        return this;

+    }

+

+

+    /**

+     * <code>divide</code> divides the values of this vector by a scalar and

+     * returns the result. The values of this vector remain untouched.

+     *

+     * @param scalar

+     *            the value to divide this vectors attributes by.

+     * @return the result <code>Vector</code>.

+     */

+    public Vector3f divide(Vector3f scalar) {

+        return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z);

+    }

+

+    /**

+     * <code>divideLocal</code> divides this vector by a scalar internally,

+     * and returns a handle to this vector for easy chaining of calls. Dividing

+     * by zero will result in an exception.

+     *

+     * @param scalar

+     *            the value to divides this vector by.

+     * @return this

+     */

+    public Vector3f divideLocal(Vector3f scalar) {

+        x /= scalar.x;

+        y /= scalar.y;

+        z /= scalar.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>negate</code> returns the negative of this vector. All values are

+     * negated and set to a new vector.

+     *

+     * @return the negated vector.

+     */

+    public Vector3f negate() {

+        return new Vector3f(-x, -y, -z);

+    }

+

+    /**

+     *

+     * <code>negateLocal</code> negates the internal values of this vector.

+     *

+     * @return this.

+     */

+    public Vector3f negateLocal() {

+        x = -x;

+        y = -y;

+        z = -z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>subtract</code> subtracts the values of a given vector from those

+     * of this vector creating a new vector object. If the provided vector is

+     * null, null is returned.

+     *

+     * @param vec

+     *            the vector to subtract from this vector.

+     * @return the result vector.

+     */

+    public Vector3f subtract(Vector3f vec) {

+        return new Vector3f(x - vec.x, y - vec.y, z - vec.z);

+    }

+

+    /**

+     * <code>subtractLocal</code> subtracts a provided vector to this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls. If the provided vector is null, null is returned.

+     *

+     * @param vec

+     *            the vector to subtract

+     * @return this

+     */

+    public Vector3f subtractLocal(Vector3f vec) {

+        if (null == vec) {

+            logger.warning("Provided vector is null, null returned.");

+            return null;

+        }

+        x -= vec.x;

+        y -= vec.y;

+        z -= vec.z;

+        return this;

+    }

+

+    /**

+     *

+     * <code>subtract</code>

+     *

+     * @param vec

+     *            the vector to subtract from this

+     * @param result

+     *            the vector to store the result in

+     * @return result

+     */

+    public Vector3f subtract(Vector3f vec, Vector3f result) {

+        if(result == null) {

+            result = new Vector3f();

+        }

+        result.x = x - vec.x;

+        result.y = y - vec.y;

+        result.z = z - vec.z;

+        return result;

+    }

+

+    /**

+     *

+     * <code>subtract</code> subtracts the provided values from this vector,

+     * creating a new vector that is then returned.

+     *

+     * @param subtractX

+     *            the x value to subtract.

+     * @param subtractY

+     *            the y value to subtract.

+     * @param subtractZ

+     *            the z value to subtract.

+     * @return the result vector.

+     */

+    public Vector3f subtract(float subtractX, float subtractY, float subtractZ) {

+        return new Vector3f(x - subtractX, y - subtractY, z - subtractZ);

+    }

+

+    /**

+     * <code>subtractLocal</code> subtracts the provided values from this vector

+     * internally, and returns a handle to this vector for easy chaining of

+     * calls.

+     *

+     * @param subtractX

+     *            the x value to subtract.

+     * @param subtractY

+     *            the y value to subtract.

+     * @param subtractZ

+     *            the z value to subtract.

+     * @return this

+     */

+    public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) {

+        x -= subtractX;

+        y -= subtractY;

+        z -= subtractZ;

+        return this;

+    }

+

+    /**

+     * <code>normalize</code> returns the unit vector of this vector.

+     *

+     * @return unit vector of this vector.

+     */

+    public Vector3f normalize() {

+//        float length = length();

+//        if (length != 0) {

+//            return divide(length);

+//        }

+//

+//        return divide(1);

+        float length = x * x + y * y + z * z;

+        if (length != 1f && length != 0f){

+            length = 1.0f / FastMath.sqrt(length);

+            return new Vector3f(x * length, y * length, z * length);

+        }

+        return clone();

+    }

+

+    /**

+     * <code>normalizeLocal</code> makes this vector into a unit vector of

+     * itself.

+     *

+     * @return this.

+     */

+    public Vector3f normalizeLocal() {

+        // NOTE: this implementation is more optimized

+        // than the old jme normalize as this method

+        // is commonly used.

+        float length = x * x + y * y + z * z;

+        if (length != 1f && length != 0f){

+            length = 1.0f / FastMath.sqrt(length);

+            x *= length;

+            y *= length;

+            z *= length;

+        }

+        return this;

+    }

+

+    /**

+     * <code>maxLocal</code> computes the maximum value for each 

+     * component in this and <code>other</code> vector. The result is stored

+     * in this vector.

+     * @param other 

+     */

+    public void maxLocal(Vector3f other){

+        x = other.x > x ? other.x : x;

+        y = other.y > y ? other.y : y;

+        z = other.z > z ? other.z : z;

+    }

+

+    /**

+     * <code>minLocal</code> computes the minimum value for each

+     * component in this and <code>other</code> vector. The result is stored

+     * in this vector.

+     * @param other

+     */

+    public void minLocal(Vector3f other){

+        x = other.x < x ? other.x : x;

+        y = other.y < y ? other.y : y;

+        z = other.z < z ? other.z : z;

+    }

+

+    /**

+     * <code>zero</code> resets this vector's data to zero internally.

+     */

+    public Vector3f zero() {

+        x = y = z = 0;

+        return this;

+    }

+

+    /**

+     * <code>angleBetween</code> returns (in radians) the angle between two vectors.

+     * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).

+     * 

+     * @param otherVector a unit vector to find the angle against

+     * @return the angle in radians.

+     */

+    public float angleBetween(Vector3f otherVector) {

+        float dotProduct = dot(otherVector);

+        float angle = FastMath.acos(dotProduct);

+        return angle;

+    }

+    

+    /**

+     * Sets this vector to the interpolation by changeAmnt from this to the finalVec

+     * this=(1-changeAmnt)*this + changeAmnt * finalVec

+     * @param finalVec The final vector to interpolate towards

+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage

+     *  change from this towards finalVec

+     */

+    public Vector3f interpolate(Vector3f finalVec, float changeAmnt) {

+        this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x;

+        this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y;

+        this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z;

+        return this;

+    }

+

+    /**

+     * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec

+     * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec

+     * @param beginVec the beging vector (changeAmnt=0)

+     * @param finalVec The final vector to interpolate towards

+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage

+     *  change from beginVec towards finalVec

+     */

+    public Vector3f interpolate(Vector3f beginVec,Vector3f finalVec, float changeAmnt) {

+        this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x;

+        this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y;

+        this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z;

+        return this;

+    }

+

+    /**

+     * Check a vector... if it is null or its floats are NaN or infinite,

+     * return false.  Else return true.

+     * @param vector the vector to check

+     * @return true or false as stated above.

+     */

+    public static boolean isValidVector(Vector3f vector) {

+      if (vector == null) return false;

+      if (Float.isNaN(vector.x) ||

+          Float.isNaN(vector.y) ||

+          Float.isNaN(vector.z)) return false;

+      if (Float.isInfinite(vector.x) ||

+          Float.isInfinite(vector.y) ||

+          Float.isInfinite(vector.z)) return false;

+      return true;

+    }

+

+    public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) {

+        w.normalizeLocal();

+        generateComplementBasis(u, v, w);

+    }

+

+    public static void generateComplementBasis(Vector3f u, Vector3f v,

+            Vector3f w) {

+        float fInvLength;

+

+        if (FastMath.abs(w.x) >= FastMath.abs(w.y)) {

+            // w.x or w.z is the largest magnitude component, swap them

+            fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z);

+            u.x = -w.z * fInvLength;

+            u.y = 0.0f;

+            u.z = +w.x * fInvLength;

+            v.x = w.y * u.z;

+            v.y = w.z * u.x - w.x * u.z;

+            v.z = -w.y * u.x;

+        } else {

+            // w.y or w.z is the largest magnitude component, swap them

+            fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z);

+            u.x = 0.0f;

+            u.y = +w.z * fInvLength;

+            u.z = -w.y * fInvLength;

+            v.x = w.y * u.z - w.z * u.y;

+            v.y = -w.x * u.z;

+            v.z = w.x * u.y;

+        }

+    }

+

+    @Override

+    public Vector3f clone() {

+        try {

+            return (Vector3f) super.clone();

+        } catch (CloneNotSupportedException e) {

+            throw new AssertionError(); // can not happen

+        }

+    }

+

+    /**

+     * Saves this Vector3f into the given float[] object.

+     * 

+     * @param floats

+     *            The float[] to take this Vector3f. If null, a new float[3] is

+     *            created.

+     * @return The array, with X, Y, Z float values in that order

+     */

+    public float[] toArray(float[] floats) {

+        if (floats == null) {

+            floats = new float[3];

+        }

+        floats[0] = x;

+        floats[1] = y;

+        floats[2] = z;

+        return floats;

+    }

+

+    /**

+     * are these two vectors the same? they are is they both have the same x,y,

+     * and z values.

+     *

+     * @param o

+     *            the object to compare for equality

+     * @return true if they are equal

+     */

+    public boolean equals(Object o) {

+        if (!(o instanceof Vector3f)) { return false; }

+

+        if (this == o) { return true; }

+

+        Vector3f comp = (Vector3f) o;

+        if (Float.compare(x,comp.x) != 0) return false;

+        if (Float.compare(y,comp.y) != 0) return false;

+        if (Float.compare(z,comp.z) != 0) return false;

+        return true;

+    }

+

+    /**

+     * <code>hashCode</code> returns a unique code for this vector object based

+     * on it's values. If two vectors are logically equivalent, they will return

+     * the same hash code value.

+     * @return the hash code value of this vector.

+     */

+    public int hashCode() {

+        int hash = 37;

+        hash += 37 * hash + Float.floatToIntBits(x);

+        hash += 37 * hash + Float.floatToIntBits(y);

+        hash += 37 * hash + Float.floatToIntBits(z);

+        return hash;

+    }

+

+    /**

+     * <code>toString</code> returns the string representation of this vector.

+     * The format is:

+     *

+     * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ]

+     *

+     * @return the string representation of this vector.

+     */

+    public String toString() {

+        return "(" + x + ", " + y + ", " + z + ")";

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(x, "x", 0);

+        capsule.write(y, "y", 0);

+        capsule.write(z, "z", 0);

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        InputCapsule capsule = e.getCapsule(this);

+        x = capsule.readFloat("x", 0);

+        y = capsule.readFloat("y", 0);

+        z = capsule.readFloat("z", 0);

+    }

+

+    public float getX() {

+        return x;

+    }

+

+    public Vector3f setX(float x) {

+        this.x = x;

+        return this;

+    }

+

+    public float getY() {

+        return y;

+    }

+

+    public Vector3f setY(float y) {

+        this.y = y;

+        return this;

+    }

+

+    public float getZ() {

+        return z;

+    }

+

+    public Vector3f setZ(float z) {

+        this.z = z;

+        return this;

+    }

+    

+    /**

+     * @param index

+     * @return x value if index == 0, y value if index == 1 or z value if index ==

+     *         2

+     * @throws IllegalArgumentException

+     *             if index is not one of 0, 1, 2.

+     */

+    public float get(int index) {

+        switch (index) {

+            case 0:

+                return x;

+            case 1:

+                return y;

+            case 2:

+                return z;

+        }

+        throw new IllegalArgumentException("index must be either 0, 1 or 2");

+    }

+    

+    /**

+     * @param index

+     *            which field index in this vector to set.

+     * @param value

+     *            to set to one of x, y or z.

+     * @throws IllegalArgumentException

+     *             if index is not one of 0, 1, 2.

+     */

+    public void set(int index, float value) {

+        switch (index) {

+            case 0:

+                x = value;

+                return;

+            case 1:

+                y = value;

+                return;

+            case 2:

+                z = value;

+                return;

+        }

+        throw new IllegalArgumentException("index must be either 0, 1 or 2");

+    }

+

+}

diff --git a/engine/src/core/com/jme3/math/Vector4f.java b/engine/src/core/com/jme3/math/Vector4f.java
new file mode 100644
index 0000000..d6e7ad7
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector4f.java
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * <code>Vector4f</code> defines a Vector for a four float value tuple.
+ * <code>Vector4f</code> can represent any four dimensional value, such as a
+ * vertex, a normal, etc. Utility methods are also included to aid in
+ * mathematical calculations.
+ *
+ * @author Maarten Steur
+ */
+public final class Vector4f implements Savable, Cloneable, java.io.Serializable {
+
+    static final long serialVersionUID = 1;
+
+    private static final Logger logger = Logger.getLogger(Vector4f.class.getName());
+
+    public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0);
+    public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
+    public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0);
+    public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0);
+    public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0);
+    public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1);
+    public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1);
+    public final static Vector4f POSITIVE_INFINITY = new Vector4f(
+            Float.POSITIVE_INFINITY,
+            Float.POSITIVE_INFINITY,
+            Float.POSITIVE_INFINITY,
+            Float.POSITIVE_INFINITY);
+    public final static Vector4f NEGATIVE_INFINITY = new Vector4f(
+            Float.NEGATIVE_INFINITY,
+            Float.NEGATIVE_INFINITY,
+            Float.NEGATIVE_INFINITY,
+            Float.NEGATIVE_INFINITY);
+
+    /**
+     * the x value of the vector.
+     */
+    public float x;
+
+    /**
+     * the y value of the vector.
+     */
+    public float y;
+
+    /**
+     * the z value of the vector.
+     */
+    public float z;
+
+    /**
+     * the w value of the vector.
+     */
+    public float w;
+
+    /**
+     * Constructor instantiates a new <code>Vector3f</code> with default
+     * values of (0,0,0).
+     *
+     */
+    public Vector4f() {
+        x = y = z = w = 0;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Vector4f</code> with provides
+     * values.
+     *
+     * @param x
+     *            the x value of the vector.
+     * @param y
+     *            the y value of the vector.
+     * @param z
+     *            the z value of the vector.
+     * @param w
+     *            the w value of the vector.
+     */
+    public Vector4f(float x, float y, float z, float w) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.w = w;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Vector3f</code> that is a copy
+     * of the provided vector
+     * @param copy The Vector3f to copy
+     */
+    public Vector4f(Vector4f copy) {
+        this.set(copy);
+    }
+
+    /**
+     * <code>set</code> sets the x,y,z,w values of the vector based on passed
+     * parameters.
+     *
+     * @param x
+     *            the x value of the vector.
+     * @param y
+     *            the y value of the vector.
+     * @param z
+     *            the z value of the vector.
+     * @param w
+     *            the w value of the vector.
+     * @return this vector
+     */
+    public Vector4f set(float x, float y, float z, float w) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.w = w;
+        return this;
+    }
+
+    /**
+     * <code>set</code> sets the x,y,z values of the vector by copying the
+     * supplied vector.
+     *
+     * @param vect
+     *            the vector to copy.
+     * @return this vector
+     */
+    public Vector4f set(Vector4f vect) {
+        this.x = vect.x;
+        this.y = vect.y;
+        this.z = vect.z;
+        this.w = vect.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>add</code> adds a provided vector to this vector creating a
+     * resultant vector which is returned. If the provided vector is null, null
+     * is returned.
+     *
+     * @param vec
+     *            the vector to add to this.
+     * @return the resultant vector.
+     */
+    public Vector4f add(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        return new Vector4f(x + vec.x, y + vec.y, z + vec.z, w + vec.w);
+    }
+
+    /**
+     *
+     * <code>add</code> adds the values of a provided vector storing the
+     * values in the supplied vector.
+     *
+     * @param vec
+     *            the vector to add to this
+     * @param result
+     *            the vector to store the result in
+     * @return result returns the supplied result vector.
+     */
+    public Vector4f add(Vector4f vec, Vector4f result) {
+        result.x = x + vec.x;
+        result.y = y + vec.y;
+        result.z = z + vec.z;
+        result.w = w + vec.w;
+        return result;
+    }
+
+    /**
+     * <code>addLocal</code> adds a provided vector to this vector internally,
+     * and returns a handle to this vector for easy chaining of calls. If the
+     * provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to add to this vector.
+     * @return this
+     */
+    public Vector4f addLocal(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x += vec.x;
+        y += vec.y;
+        z += vec.z;
+        w += vec.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>add</code> adds the provided values to this vector, creating a
+     * new vector that is then returned.
+     *
+     * @param addX
+     *            the x value to add.
+     * @param addY
+     *            the y value to add.
+     * @param addZ
+     *            the z value to add.
+     * @return the result vector.
+     */
+    public Vector4f add(float addX, float addY, float addZ, float addW) {
+        return new Vector4f(x + addX, y + addY, z + addZ, w + addW);
+    }
+
+    /**
+     * <code>addLocal</code> adds the provided values to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param addX
+     *            value to add to x
+     * @param addY
+     *            value to add to y
+     * @param addZ
+     *            value to add to z
+     * @return this
+     */
+    public Vector4f addLocal(float addX, float addY, float addZ, float addW) {
+        x += addX;
+        y += addY;
+        z += addZ;
+        w += addW;
+        return this;
+    }
+
+    /**
+     *
+     * <code>scaleAdd</code> multiplies this vector by a scalar then adds the
+     * given Vector3f.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @param add
+     *            the value to add
+     */
+    public Vector4f scaleAdd(float scalar, Vector4f add) {
+        x = x * scalar + add.x;
+        y = y * scalar + add.y;
+        z = z * scalar + add.z;
+        w = w * scalar + add.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>scaleAdd</code> multiplies the given vector by a scalar then adds
+     * the given vector.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @param mult
+     *            the value to multiply the scalar by
+     * @param add
+     *            the value to add
+     */
+    public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) {
+        this.x = mult.x * scalar + add.x;
+        this.y = mult.y * scalar + add.y;
+        this.z = mult.z * scalar + add.z;
+        this.w = mult.w * scalar + add.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>dot</code> calculates the dot product of this vector with a
+     * provided vector. If the provided vector is null, 0 is returned.
+     *
+     * @param vec
+     *            the vector to dot with this vector.
+     * @return the resultant dot product of this vector and a given vector.
+     */
+    public float dot(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, 0 returned.");
+            return 0;
+        }
+        return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
+    }
+
+    public Vector4f project(Vector4f other){
+        float n = this.dot(other); // A . B
+        float d = other.lengthSquared(); // |B|^2
+        return new Vector4f(other).normalizeLocal().multLocal(n/d);
+    }
+
+    /**
+     * Returns true if this vector is a unit vector (length() ~= 1),
+     * returns false otherwise.
+     *
+     * @return true if this vector is a unit vector (length() ~= 1),
+     * or false otherwise.
+     */
+    public boolean isUnitVector(){
+        float len = length();
+        return 0.99f < len && len < 1.01f;
+    }
+
+    /**
+     * <code>length</code> calculates the magnitude of this vector.
+     *
+     * @return the length or magnitude of the vector.
+     */
+    public float length() {
+        return FastMath.sqrt(lengthSquared());
+    }
+
+    /**
+     * <code>lengthSquared</code> calculates the squared value of the
+     * magnitude of the vector.
+     *
+     * @return the magnitude squared of the vector.
+     */
+    public float lengthSquared() {
+        return x * x + y * y + z * z + w * w;
+    }
+
+    /**
+     * <code>distanceSquared</code> calculates the distance squared between
+     * this vector and vector v.
+     *
+     * @param v the second vector to determine the distance squared.
+     * @return the distance squared between the two vectors.
+     */
+    public float distanceSquared(Vector4f v) {
+        double dx = x - v.x;
+        double dy = y - v.y;
+        double dz = z - v.z;
+        double dw = w - v.w;
+        return (float) (dx * dx + dy * dy + dz * dz + dw * dw);
+    }
+
+    /**
+     * <code>distance</code> calculates the distance between this vector and
+     * vector v.
+     *
+     * @param v the second vector to determine the distance.
+     * @return the distance between the two vectors.
+     */
+    public float distance(Vector4f v) {
+        return FastMath.sqrt(distanceSquared(v));
+    }
+
+    /**
+     *
+     * <code>mult</code> multiplies this vector by a scalar. The resultant
+     * vector is returned.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return the new vector.
+     */
+    public Vector4f mult(float scalar) {
+        return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar);
+    }
+
+    /**
+     *
+     * <code>mult</code> multiplies this vector by a scalar. The resultant
+     * vector is supplied as the second parameter and returned.
+     *
+     * @param scalar the scalar to multiply this vector by.
+     * @param product the product to store the result in.
+     * @return product
+     */
+    public Vector4f mult(float scalar, Vector4f product) {
+        if (null == product) {
+            product = new Vector4f();
+        }
+
+        product.x = x * scalar;
+        product.y = y * scalar;
+        product.z = z * scalar;
+        product.w = w * scalar;
+        return product;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return this
+     */
+    public Vector4f multLocal(float scalar) {
+        x *= scalar;
+        y *= scalar;
+        z *= scalar;
+        w *= scalar;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @return this
+     */
+    public Vector4f multLocal(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x *= vec.x;
+        y *= vec.y;
+        z *= vec.z;
+        w *= vec.w;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this vector by 3 scalars
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param x
+     * @param y
+     * @param z
+     * @param w
+     * @return this
+     */
+    public Vector4f multLocal(float x, float y, float z, float w) {
+        this.x *= x;
+        this.y *= y;
+        this.z *= z;
+        this.w *= w;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @return this
+     */
+    public Vector4f mult(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        return mult(vec, null);
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @param store result vector (null to create a new vector)
+     * @return this
+     */
+    public Vector4f mult(Vector4f vec, Vector4f store) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        if (store == null) store = new Vector4f();
+        return store.set(x * vec.x, y * vec.y, z * vec.z, w * vec.w);
+    }
+
+    /**
+     * <code>divide</code> divides the values of this vector by a scalar and
+     * returns the result. The values of this vector remain untouched.
+     *
+     * @param scalar
+     *            the value to divide this vectors attributes by.
+     * @return the result <code>Vector</code>.
+     */
+    public Vector4f divide(float scalar) {
+        scalar = 1f/scalar;
+        return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar);
+    }
+
+    /**
+     * <code>divideLocal</code> divides this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls. Dividing
+     * by zero will result in an exception.
+     *
+     * @param scalar
+     *            the value to divides this vector by.
+     * @return this
+     */
+    public Vector4f divideLocal(float scalar) {
+        scalar = 1f/scalar;
+        x *= scalar;
+        y *= scalar;
+        z *= scalar;
+        w *= scalar;
+        return this;
+    }
+
+    /**
+     * <code>divide</code> divides the values of this vector by a scalar and
+     * returns the result. The values of this vector remain untouched.
+     *
+     * @param scalar
+     *            the value to divide this vectors attributes by.
+     * @return the result <code>Vector</code>.
+     */
+    public Vector4f divide(Vector4f scalar) {
+        return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w);
+    }
+
+    /**
+     * <code>divideLocal</code> divides this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls. Dividing
+     * by zero will result in an exception.
+     *
+     * @param scalar
+     *            the value to divides this vector by.
+     * @return this
+     */
+    public Vector4f divideLocal(Vector4f scalar) {
+        x /= scalar.x;
+        y /= scalar.y;
+        z /= scalar.z;
+        w /= scalar.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>negate</code> returns the negative of this vector. All values are
+     * negated and set to a new vector.
+     *
+     * @return the negated vector.
+     */
+    public Vector4f negate() {
+        return new Vector4f(-x, -y, -z, -w);
+    }
+
+    /**
+     *
+     * <code>negateLocal</code> negates the internal values of this vector.
+     *
+     * @return this.
+     */
+    public Vector4f negateLocal() {
+        x = -x;
+        y = -y;
+        z = -z;
+        w = -w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>subtract</code> subtracts the values of a given vector from those
+     * of this vector creating a new vector object. If the provided vector is
+     * null, null is returned.
+     *
+     * @param vec
+     *            the vector to subtract from this vector.
+     * @return the result vector.
+     */
+    public Vector4f subtract(Vector4f vec) {
+        return new Vector4f(x - vec.x, y - vec.y, z - vec.z, w - vec.w);
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to subtract
+     * @return this
+     */
+    public Vector4f subtractLocal(Vector4f vec) {
+        if (null == vec) {
+            logger.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x -= vec.x;
+        y -= vec.y;
+        z -= vec.z;
+        w -= vec.w;
+        return this;
+    }
+
+    /**
+     *
+     * <code>subtract</code>
+     *
+     * @param vec
+     *            the vector to subtract from this
+     * @param result
+     *            the vector to store the result in
+     * @return result
+     */
+    public Vector4f subtract(Vector4f vec, Vector4f result) {
+        if(result == null) {
+            result = new Vector4f();
+        }
+        result.x = x - vec.x;
+        result.y = y - vec.y;
+        result.z = z - vec.z;
+        result.w = w - vec.w;
+        return result;
+    }
+
+    /**
+     *
+     * <code>subtract</code> subtracts the provided values from this vector,
+     * creating a new vector that is then returned.
+     *
+     * @param subtractX
+     *            the x value to subtract.
+     * @param subtractY
+     *            the y value to subtract.
+     * @param subtractZ
+     *            the z value to subtract.
+     * @param subtractW
+     *            the w value to subtract.
+     * @return the result vector.
+     */
+    public Vector4f subtract(float subtractX, float subtractY, float subtractZ, float subtractW) {
+        return new Vector4f(x - subtractX, y - subtractY, z - subtractZ, w - subtractW);
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts the provided values from this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param subtractX
+     *            the x value to subtract.
+     * @param subtractY
+     *            the y value to subtract.
+     * @param subtractZ
+     *            the z value to subtract.
+     * @param subtractW
+     *            the w value to subtract.
+     * @return this
+     */
+    public Vector4f subtractLocal(float subtractX, float subtractY, float subtractZ, float subtractW) {
+        x -= subtractX;
+        y -= subtractY;
+        z -= subtractZ;
+        w -= subtractW;
+        return this;
+    }
+
+    /**
+     * <code>normalize</code> returns the unit vector of this vector.
+     *
+     * @return unit vector of this vector.
+     */
+    public Vector4f normalize() {
+//        float length = length();
+//        if (length != 0) {
+//            return divide(length);
+//        }
+//
+//        return divide(1);
+        float length = x * x + y * y + z * z + w * w;
+        if (length != 1f && length != 0f){
+            length = 1.0f / FastMath.sqrt(length);
+            return new Vector4f(x * length, y * length, z * length, w * length);
+        }
+        return clone();
+    }
+
+    /**
+     * <code>normalizeLocal</code> makes this vector into a unit vector of
+     * itself.
+     *
+     * @return this.
+     */
+    public Vector4f normalizeLocal() {
+        // NOTE: this implementation is more optimized
+        // than the old jme normalize as this method
+        // is commonly used.
+        float length = x * x + y * y + z * z + w * w;
+        if (length != 1f && length != 0f){
+            length = 1.0f / FastMath.sqrt(length);
+            x *= length;
+            y *= length;
+            z *= length;
+            w *= length;
+        }
+        return this;
+    }
+
+    /**
+     * <code>maxLocal</code> computes the maximum value for each
+     * component in this and <code>other</code> vector. The result is stored
+     * in this vector.
+     * @param other
+     */
+    public void maxLocal(Vector4f other){
+        x = other.x > x ? other.x : x;
+        y = other.y > y ? other.y : y;
+        z = other.z > z ? other.z : z;
+        w = other.w > w ? other.w : w;
+    }
+
+    /**
+     * <code>minLocal</code> computes the minimum value for each
+     * component in this and <code>other</code> vector. The result is stored
+     * in this vector.
+     * @param other
+     */
+    public void minLocal(Vector4f other){
+        x = other.x < x ? other.x : x;
+        y = other.y < y ? other.y : y;
+        z = other.z < z ? other.z : z;
+        w = other.w < w ? other.w : w;
+    }
+
+    /**
+     * <code>zero</code> resets this vector's data to zero internally.
+     */
+    public Vector4f zero() {
+        x = y = z = w = 0;
+        return this;
+    }
+
+    /**
+     * <code>angleBetween</code> returns (in radians) the angle between two vectors.
+     * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
+     *
+     * @param otherVector a unit vector to find the angle against
+     * @return the angle in radians.
+     */
+    public float angleBetween(Vector4f otherVector) {
+        float dotProduct = dot(otherVector);
+        float angle = FastMath.acos(dotProduct);
+        return angle;
+    }
+
+    /**
+     * Sets this vector to the interpolation by changeAmnt from this to the finalVec
+     * this=(1-changeAmnt)*this + changeAmnt * finalVec
+     * @param finalVec The final vector to interpolate towards
+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+     *  change from this towards finalVec
+     */
+    public Vector4f interpolate(Vector4f finalVec, float changeAmnt) {
+        this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x;
+        this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y;
+        this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z;
+        this.w=(1-changeAmnt)*this.w + changeAmnt*finalVec.w;
+        return this;
+    }
+
+    /**
+     * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec
+     * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+     * @param beginVec the beging vector (changeAmnt=0)
+     * @param finalVec The final vector to interpolate towards
+     * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+     *  change from beginVec towards finalVec
+     */
+    public Vector4f interpolate(Vector4f beginVec,Vector4f finalVec, float changeAmnt) {
+        this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x;
+        this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y;
+        this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z;
+        this.w=(1-changeAmnt)*beginVec.w + changeAmnt*finalVec.w;
+        return this;
+    }
+
+    /**
+     * Check a vector... if it is null or its floats are NaN or infinite,
+     * return false.  Else return true.
+     * @param vector the vector to check
+     * @return true or false as stated above.
+     */
+    public static boolean isValidVector(Vector4f vector) {
+      if (vector == null) return false;
+      if (Float.isNaN(vector.x) ||
+          Float.isNaN(vector.y) ||
+          Float.isNaN(vector.z)||
+          Float.isNaN(vector.w)) return false;
+      if (Float.isInfinite(vector.x) ||
+          Float.isInfinite(vector.y) ||
+          Float.isInfinite(vector.z) ||
+          Float.isInfinite(vector.w)) return false;
+      return true;
+    }
+
+    @Override
+    public Vector4f clone() {
+        try {
+            return (Vector4f) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(); // can not happen
+        }
+    }
+
+    /**
+     * Saves this Vector3f into the given float[] object.
+     *
+     * @param floats
+     *            The float[] to take this Vector3f. If null, a new float[3] is
+     *            created.
+     * @return The array, with X, Y, Z float values in that order
+     */
+    public float[] toArray(float[] floats) {
+        if (floats == null) {
+            floats = new float[4];
+        }
+        floats[0] = x;
+        floats[1] = y;
+        floats[2] = z;
+        floats[3] = w;
+        return floats;
+    }
+
+    /**
+     * are these two vectors the same? they are is they both have the same x,y,
+     * and z values.
+     *
+     * @param o
+     *            the object to compare for equality
+     * @return true if they are equal
+     */
+    public boolean equals(Object o) {
+        if (!(o instanceof Vector4f)) { return false; }
+
+        if (this == o) { return true; }
+
+        Vector4f comp = (Vector4f) o;
+        if (Float.compare(x,comp.x) != 0) return false;
+        if (Float.compare(y,comp.y) != 0) return false;
+        if (Float.compare(z,comp.z) != 0) return false;
+        if (Float.compare(w,comp.w) != 0) return false;
+        return true;
+    }
+
+    /**
+     * <code>hashCode</code> returns a unique code for this vector object based
+     * on it's values. If two vectors are logically equivalent, they will return
+     * the same hash code value.
+     * @return the hash code value of this vector.
+     */
+    public int hashCode() {
+        int hash = 37;
+        hash += 37 * hash + Float.floatToIntBits(x);
+        hash += 37 * hash + Float.floatToIntBits(y);
+        hash += 37 * hash + Float.floatToIntBits(z);
+        hash += 37 * hash + Float.floatToIntBits(w);
+        return hash;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this vector.
+     * The format is:
+     *
+     * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ, W=WW.WWWW]
+     *
+     * @return the string representation of this vector.
+     */
+    public String toString() {
+        return "(" + x + ", " + y + ", " + z + ", " + w + ")";
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(x, "x", 0);
+        capsule.write(y, "y", 0);
+        capsule.write(z, "z", 0);
+        capsule.write(w, "w", 0);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        x = capsule.readFloat("x", 0);
+        y = capsule.readFloat("y", 0);
+        z = capsule.readFloat("z", 0);
+        w = capsule.readFloat("w", 0);
+    }
+
+    public float getX() {
+        return x;
+    }
+
+    public Vector4f setX(float x) {
+        this.x = x;
+        return this;
+    }
+
+    public float getY() {
+        return y;
+    }
+
+    public Vector4f setY(float y) {
+        this.y = y;
+        return this;
+    }
+
+    public float getZ() {
+        return z;
+    }
+
+    public Vector4f setZ(float z) {
+        this.z = z;
+        return this;
+    }
+
+    public float getW() {
+        return w;
+    }
+
+    public Vector4f setW(float w) {
+        this.w = w;
+        return this;
+    }
+
+    /**
+     * @param index
+     * @return x value if index == 0, y value if index == 1 or z value if index ==
+     *         2
+     * @throws IllegalArgumentException
+     *             if index is not one of 0, 1, 2.
+     */
+    public float get(int index) {
+        switch (index) {
+            case 0:
+                return x;
+            case 1:
+                return y;
+            case 2:
+                return z;
+            case 3:
+                return w;
+        }
+        throw new IllegalArgumentException("index must be either 0, 1, 2 or 3");
+    }
+
+    /**
+     * @param index
+     *            which field index in this vector to set.
+     * @param value
+     *            to set to one of x, y, z or w.
+     * @throws IllegalArgumentException
+     *             if index is not one of 0, 1, 2, 3.
+     */
+    public void set(int index, float value) {
+        switch (index) {
+            case 0:
+                x = value;
+                return;
+            case 1:
+                y = value;
+                return;
+            case 2:
+                z = value;
+                return;
+            case 3:
+                w = value;
+              return;
+        }
+        throw new IllegalArgumentException("index must be either 0, 1, 2 or 3");
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/math/package.html b/engine/src/core/com/jme3/math/package.html
new file mode 100644
index 0000000..64da8aa
--- /dev/null
+++ b/engine/src/core/com/jme3/math/package.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.math</code> package provides mathematic data structures
+and utilities which are used by the rest of the engine.
+The math package provides the following classes:<br>
+<h3>General purpose vectors</h3>
+<ul>
+    <li>{@link com.jme3.math.Vector2f} - 2D general purpose vector</li>
+    <li>{@link com.jme3.math.Vector3f} - 3D general purpose vector</li>
+    <li>{@link com.jme3.math.Vector4f} - 4D general purpose vector</li>
+</ul>
+<h3>Special purpose vectors</h3>
+<ul>
+    <li>{@link com.jme3.math.ColorRGBA} - Floating-point RGB color with alpha</li>
+    <li>{@link com.jme3.math.Quaternion} - Specialized 4D data structure to represent rotation</li>
+</ul>
+<h3>Matrices</h3>
+<ul>
+    <li>{@link com.jme3.math.Matrix3f} - 3x3 matrix, usually used to represent rotation</li>
+    <li>{@link com.jme3.math.Matrix4f} - 4x4 matrix, used as an efficient transform representation</li>
+</ul>
+<h3>Shapes</h3>
+<ul>
+    <li>{@link com.jme3.math.AbstractTriangle} - Abstract triangle. Data to be provided by implementation</li>
+    <li>{@link com.jme3.math.Triangle} - Concrete implementation of AbstractTriangle with center and normal vectors</li>
+    <li>{@link com.jme3.math.Line} - Infinite 3D line</li>
+    <li>{@link com.jme3.math.LineSegment} - 3D line with start and end point</li>
+    <li>{@link com.jme3.math.Plane} - 3D plane</li>
+    <li>{@link com.jme3.math.Ray} - 3D ray</li>
+    <li>{@link com.jme3.math.Rectangle} - 3D rectangle</li>
+    <li>{@link com.jme3.math.Ring} - 3D ring</li>
+</ul>
+<h3>Curves</h3>
+<ul>
+    <li>{@link com.jme3.math.Spline} - 3D curve defined by control points and a function</li>
+</ul>
+<h3>Utility classes</h3>
+<ul>
+    <li>{@link com.jme3.math.Transform} - Representation of a transform with translation, rotation, and scale</li>
+    <li>{@link com.jme3.math.FastMath} - Contains static methods for floating-point math</li>
+    <li>{@link com.jme3.math.CurveAndSurfaceMath} - Contains static methods specific to curve and surface math</li>
+    <li>{@link com.jme3.math.Eigen3f} - Provides computation of eigenvectors given a matrix</li>
+</ul>
+    
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/post/Filter.java b/engine/src/core/com/jme3/post/Filter.java
new file mode 100644
index 0000000..ef9a6ff
--- /dev/null
+++ b/engine/src/core/com/jme3/post/Filter.java
@@ -0,0 +1,433 @@
+/*

+ * Copyright (c) 2009-2010 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.post;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.export.*;

+import com.jme3.material.Material;

+import com.jme3.renderer.Caps;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.Renderer;

+import com.jme3.renderer.ViewPort;

+import com.jme3.texture.FrameBuffer;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture2D;

+import java.io.IOException;

+import java.util.Collection;

+import java.util.Iterator;

+import java.util.List;

+

+/**

+ * Filters are 2D effects applied to the rendered scene.<br>

+ * The filter is fed with the rendered scene image rendered in an offscreen frame buffer.<br>

+ * This texture is applied on a fullscreen quad, with a special material.<br>

+ * This material uses a shader that aplly the desired effect to the scene texture.<br>

+ * <br>

+ * This class is abstract, any Filter must extend it.<br>

+ * Any filter holds a frameBuffer and a texture<br>

+ * The getMaterial must return a Material that use a GLSL shader immplementing the desired effect<br>

+ *

+ * @author Rémy Bouquet aka Nehon

+ */

+public abstract class Filter implements Savable {

+

+

+    private String name;

+    protected Pass defaultPass;

+    protected List<Pass> postRenderPasses;

+    protected Material material;

+    protected boolean enabled = true;

+    protected FilterPostProcessor processor;

+

+    public Filter(String name) {

+        this.name = name;

+    }

+

+    /**

+     * Inner class Pass

+     * Pass are like filters in filters.

+     * Some filters will need multiple passes before the final render

+     */

+    public class Pass {

+

+        protected FrameBuffer renderFrameBuffer;

+        protected Texture2D renderedTexture;

+        protected Texture2D depthTexture;

+        protected Material passMaterial;

+

+        /**

+         * init the pass called internally

+         * @param renderer

+         * @param width

+         * @param height

+         * @param textureFormat

+         * @param depthBufferFormat

+         * @param numSamples

+         */

+        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples, boolean renderDepth) {

+            Collection<Caps> caps = renderer.getCaps();

+            if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample) && caps.contains(Caps.OpenGL31)) {

+                renderFrameBuffer = new FrameBuffer(width, height, numSamples);

+                renderedTexture = new Texture2D(width, height, numSamples, textureFormat);

+                renderFrameBuffer.setDepthBuffer(depthBufferFormat);

+                if (renderDepth) {

+                    depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat);

+                    renderFrameBuffer.setDepthTexture(depthTexture);

+                }

+            } else {

+                renderFrameBuffer = new FrameBuffer(width, height, 1);

+                renderedTexture = new Texture2D(width, height, textureFormat);

+                renderFrameBuffer.setDepthBuffer(depthBufferFormat);

+                if (renderDepth) {

+                    depthTexture = new Texture2D(width, height, depthBufferFormat);

+                    renderFrameBuffer.setDepthTexture(depthTexture);

+                }

+            }

+

+            renderFrameBuffer.setColorTexture(renderedTexture);

+

+

+        }

+

+        /**

+         *  init the pass called internally

+         * @param renderer

+         * @param width

+         * @param height

+         * @param textureFormat

+         * @param depthBufferFormat

+         */

+        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat) {

+            init(renderer, width, height, textureFormat, depthBufferFormat, 1);

+        }

+

+        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples) {

+            init(renderer, width, height, textureFormat, depthBufferFormat, numSamples, false);

+        }

+

+        /**

+         *  init the pass called internally

+         * @param renderer

+         * @param width

+         * @param height

+         * @param textureFormat

+         * @param depthBufferFormat

+         * @param numSample

+         * @param material

+         */

+        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSample, Material material) {

+            init(renderer, width, height, textureFormat, depthBufferFormat, numSample);

+            passMaterial = material;

+        }

+

+        public boolean requiresSceneAsTexture() {

+            return false;

+        }

+

+        public boolean requiresDepthAsTexture() {

+            return false;

+        }

+

+        public void beforeRender() {

+        }

+

+        public FrameBuffer getRenderFrameBuffer() {

+            return renderFrameBuffer;

+        }

+

+        public void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {

+            this.renderFrameBuffer = renderFrameBuffer;

+        }

+

+        public Texture2D getDepthTexture() {

+            return depthTexture;

+        }

+

+        public Texture2D getRenderedTexture() {

+            return renderedTexture;

+        }

+

+        public void setRenderedTexture(Texture2D renderedTexture) {

+            this.renderedTexture = renderedTexture;

+        }

+

+        public Material getPassMaterial() {

+            return passMaterial;

+        }

+

+        public void setPassMaterial(Material passMaterial) {

+            this.passMaterial = passMaterial;

+        }

+

+        public void cleanup(Renderer r) {

+        }

+    }

+

+    /**

+     * returns the default pass texture format

+     * @return

+     */

+    protected Format getDefaultPassTextureFormat() {

+        return Format.RGBA8;

+    }

+

+    /**

+     * returns the default pass depth format

+     * @return

+     */

+    protected Format getDefaultPassDepthFormat() {

+        return Format.Depth;

+    }

+

+    /**

+     * contruct a Filter

+     */

+    protected Filter() {

+        this("filter");

+    }

+

+    /**

+     *

+     * initialize this filter

+     * use InitFilter for overriding filter initialization

+     * @param manager the assetManager

+     * @param renderManager the renderManager

+     * @param vp the viewport

+     * @param w the width

+     * @param h the height

+     */

+    protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {

+        //  cleanup(renderManager.getRenderer());

+        defaultPass = new Pass();

+        defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat());

+        initFilter(manager, renderManager, vp, w, h);

+    }

+

+    /**

+     * cleanup this filter

+     * @param r

+     */

+    protected final void cleanup(Renderer r) {

+        processor = null;

+        if (defaultPass != null) {

+            defaultPass.cleanup(r);

+        }

+        if (postRenderPasses != null) {

+            for (Iterator<Pass> it = postRenderPasses.iterator(); it.hasNext();) {

+                Pass pass = it.next();

+                pass.cleanup(r);

+            }

+        }

+        cleanUpFilter(r);

+    }

+

+    /**

+     * Initialization of sub classes filters

+     * This method is called once when the filter is added to the FilterPostProcessor

+     * It should contain Material initializations and extra passes initialization

+     * @param manager the assetManager

+     * @param renderManager the renderManager

+     * @param vp the viewPort where this filter is rendered

+     * @param w the width of the filter

+     * @param h the height of the filter

+     */

+    protected abstract void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h);

+

+    /**

+     * override this method if you have some cleanup to do

+     * @param r the renderer

+     */

+    protected void cleanUpFilter(Renderer r) {

+    }

+

+    ;

+

+    /**

+     * Must return the material used for this filter.

+     * this method is called every frame.

+     *

+     * @return the material used for this filter.

+     */

+    protected abstract Material getMaterial();

+

+    /**

+     * Override this method if you want to make a pre pass, before the actual rendering of the frame

+     * @param renderManager

+     * @param viewPort

+     */

+    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {

+    }

+

+    /**

+     * Override this method if you want to modify parameters according to tpf before the rendering of the frame.

+     * This is usefull for animated filters

+     * Also it can be the place to render pre passes

+     * @param tpf the time used to render the previous frame

+     */

+    protected void preFrame(float tpf) {

+    }

+

+    /**

+     * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering

+     * @param renderManager

+     * @param viewPort

+     * @param prevFilterBuffer

+     * @param sceneBuffer

+     */

+    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {

+    }

+

+    /**

+     * Override this method if you want to save extra properties when the filter is saved else only basic properties of the filter will be saved

+     * This method should always begin by super.write(ex);

+     * @param ex

+     * @throws IOException

+     */

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(name, "name", "");

+        oc.write(enabled, "enabled", true);

+    }

+

+    /**

+     * Override this method if you want to load extra properties when the filter

+     * is loaded else only basic properties of the filter will be loaded

+     * This method should always begin by super.read(im);

+     */

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        name = ic.readString("name", "");

+        enabled = ic.readBoolean("enabled", true);

+    }

+

+    /**

+     * returns the name of the filter

+     * @return

+     */

+    public String getName() {

+        return name;

+    }

+

+    /**

+     * Sets the name of the filter

+     * @param name

+     */

+    public void setName(String name) {

+        this.name = name;

+    }

+

+    /**

+     * returns the default pass frame buffer

+     * @return

+     */

+    protected FrameBuffer getRenderFrameBuffer() {

+        return defaultPass.renderFrameBuffer;

+    }

+

+    /**

+     * sets the default pas frame buffer

+     * @param renderFrameBuffer

+     */

+    protected void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {

+        this.defaultPass.renderFrameBuffer = renderFrameBuffer;

+    }

+

+    /**

+     * returns the rendered texture of this filter

+     * @return

+     */

+    protected Texture2D getRenderedTexture() {

+        return defaultPass.renderedTexture;

+    }

+

+    /**

+     * sets the rendered texture of this filter

+     * @param renderedTexture

+     */

+    protected void setRenderedTexture(Texture2D renderedTexture) {

+        this.defaultPass.renderedTexture = renderedTexture;

+    }

+

+    /**

+     * Override this method and return true if your Filter needs the depth texture

+     *

+     * @return true if your Filter need the depth texture

+     */

+    protected boolean isRequiresDepthTexture() {

+        return false;

+    }

+

+    /**

+     * Override this method and return false if your Filter does not need the scene texture

+     *

+     * @return false if your Filter does not need the scene texture

+     */

+    protected boolean isRequiresSceneTexture() {

+        return true;

+    }

+

+    /**

+     * returns the list of the postRender passes

+     * @return

+     */

+    protected List<Pass> getPostRenderPasses() {

+        return postRenderPasses;

+    }

+

+    /**

+     * Enable or disable this filter

+     * @param enabled true to enable

+     */

+    public void setEnabled(boolean enabled) {

+        if (processor != null) {

+            processor.setFilterState(this, enabled);

+        } else {

+            this.enabled = enabled;

+        }

+    }

+

+    /**

+     * returns ttrue if the filter is enabled

+     * @return enabled

+     */

+    public boolean isEnabled() {

+        return enabled;

+    }

+

+    /**

+     * sets a reference to the FilterPostProcessor ti which this filter is attached

+     * @param proc

+     */

+    protected void setProcessor(FilterPostProcessor proc) {

+        processor = proc;

+    }

+}

diff --git a/engine/src/core/com/jme3/post/FilterPostProcessor.java b/engine/src/core/com/jme3/post/FilterPostProcessor.java
new file mode 100644
index 0000000..2e48f0f
--- /dev/null
+++ b/engine/src/core/com/jme3/post/FilterPostProcessor.java
@@ -0,0 +1,500 @@
+/*

+ * Copyright (c) 2009-2010 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.post;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.export.*;

+import com.jme3.material.Material;

+import com.jme3.renderer.*;

+import com.jme3.renderer.queue.RenderQueue;

+import com.jme3.texture.FrameBuffer;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture2D;

+import com.jme3.ui.Picture;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.Iterator;

+import java.util.List;

+

+/**

+ * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene<br>

+ * It manages a list of filters that will be applied in the order in which they've been added to the list

+ * @author Rémy Bouquet aka Nehon

+ */

+public class FilterPostProcessor implements SceneProcessor, Savable {

+

+    private RenderManager renderManager;

+    private Renderer renderer;

+    private ViewPort viewPort;

+    private FrameBuffer renderFrameBufferMS;

+    private int numSamples = 1;

+    private FrameBuffer renderFrameBuffer;

+    private Texture2D filterTexture;

+    private Texture2D depthTexture;

+    private List<Filter> filters = new ArrayList<Filter>();

+    private AssetManager assetManager;

+    private Camera filterCam = new Camera(1, 1);

+    private Picture fsQuad;

+    private boolean computeDepth = false;

+    private FrameBuffer outputBuffer;

+    private int width;

+    private int height;

+    private float bottom;

+    private float left;

+    private float right;

+    private float top;

+    private int originalWidth;

+    private int originalHeight;

+    private int lastFilterIndex = -1;

+    private boolean cameraInit = false;

+

+    /**

+     * Create a FilterProcessor 

+     * @param assetManager the assetManager

+     */

+    public FilterPostProcessor(AssetManager assetManager) {

+        this.assetManager = assetManager;

+    }

+

+    /**

+     * Don't use this constructor use {@link FilterPostProcessor(AssetManager assetManager)}<br>

+     * This constructor is used for serialization only

+     */

+    public FilterPostProcessor() {

+    }

+

+    /**

+     * Adds a filter to the filters list<br>

+     * @param filter the filter to add

+     */

+    public void addFilter(Filter filter) {

+        filters.add(filter);

+        filter.setProcessor(this);

+

+        if (isInitialized()) {

+            initFilter(filter, viewPort);

+        }

+

+        setFilterState(filter, filter.isEnabled());

+

+    }

+

+    /**

+     * removes this filters from the filters list

+     * @param filter 

+     */

+    public void removeFilter(Filter filter) {

+        filters.remove(filter);

+        filter.cleanup(renderer);

+        updateLastFilterIndex();

+    }

+

+    public Iterator<Filter> getFilterIterator() {

+        return filters.iterator();

+    }

+

+    public void initialize(RenderManager rm, ViewPort vp) {

+        renderManager = rm;

+        renderer = rm.getRenderer();

+        viewPort = vp;

+        fsQuad = new Picture("filter full screen quad");

+

+        Camera cam = vp.getCamera();

+

+        //save view port diensions

+        left = cam.getViewPortLeft();

+        right = cam.getViewPortRight();

+        top = cam.getViewPortTop();

+        bottom = cam.getViewPortBottom();

+        originalWidth = cam.getWidth();

+        originalHeight = cam.getHeight();

+        //first call to reshape

+        reshape(vp, cam.getWidth(), cam.getHeight());

+    }

+

+    /**

+     * init the given filter

+     * @param filter

+     * @param vp 

+     */

+    private void initFilter(Filter filter, ViewPort vp) {

+        filter.init(assetManager, renderManager, vp, width, height);

+        if (filter.isRequiresDepthTexture()) {

+            if (!computeDepth && renderFrameBuffer != null) {

+                depthTexture = new Texture2D(width, height, Format.Depth24);

+                renderFrameBuffer.setDepthTexture(depthTexture);

+            }

+            computeDepth = true;

+            filter.getMaterial().setTexture("DepthTexture", depthTexture);

+        }

+    }

+

+    /**

+     * renders a filter on a fullscreen quad

+     * @param r

+     * @param buff

+     * @param mat 

+     */

+    private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) {

+        if (buff == outputBuffer) {

+            fsQuad.setWidth(width);

+            fsQuad.setHeight(height);

+            filterCam.resize(originalWidth, originalHeight, true);

+            fsQuad.setPosition(left * originalWidth, bottom * originalHeight);

+        } else {

+            fsQuad.setWidth(buff.getWidth());

+            fsQuad.setHeight(buff.getHeight());

+            filterCam.resize(buff.getWidth(), buff.getHeight(), true);

+            fsQuad.setPosition(0, 0);

+        }

+

+        if (mat.getAdditionalRenderState().isDepthWrite()) {

+            mat.getAdditionalRenderState().setDepthTest(false);

+            mat.getAdditionalRenderState().setDepthWrite(false);

+        }

+

+        fsQuad.setMaterial(mat);

+        fsQuad.updateGeometricState();

+

+        renderManager.setCamera(filterCam, true);

+        r.setFrameBuffer(buff);

+        r.clearBuffers(false, true, true);

+        renderManager.renderGeometry(fsQuad);

+

+    }

+

+    public boolean isInitialized() {

+        return viewPort != null;

+    }

+

+    public void postQueue(RenderQueue rq) {

+

+        for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {

+            Filter filter = it.next();

+            if (filter.isEnabled()) {

+                filter.postQueue(renderManager, viewPort);

+            }

+        }

+

+    }

+    Picture pic = new Picture("debug");

+

+    /**

+     * iterate through the filter list and renders filters

+     * @param r

+     * @param sceneFb 

+     */

+    private void renderFilterChain(Renderer r, FrameBuffer sceneFb) {

+        Texture2D tex = filterTexture;

+        FrameBuffer buff = sceneFb;

+        boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1;

+        for (int i = 0; i < filters.size(); i++) {

+            Filter filter = filters.get(i);

+            if (filter.isEnabled()) {

+                if (filter.getPostRenderPasses() != null) {

+                    for (Iterator<Filter.Pass> it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) {

+                        Filter.Pass pass = it1.next();

+                        pass.beforeRender();

+                        if (pass.requiresSceneAsTexture()) {

+                            pass.getPassMaterial().setTexture("Texture", tex);

+                            if (tex.getImage().getMultiSamples() > 1) {

+                                pass.getPassMaterial().setInt("NumSamples", tex.getImage().getMultiSamples());

+                            } else {

+                                pass.getPassMaterial().clearParam("NumSamples");

+

+                            }

+                        }

+                        if (pass.requiresDepthAsTexture()) {

+                            pass.getPassMaterial().setTexture("DepthTexture", depthTexture);

+                            if (msDepth) {

+                                pass.getPassMaterial().setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());

+                            } else {

+                                pass.getPassMaterial().clearParam("NumSamplesDepth");

+                            }

+                        }

+                        renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial());

+                    }

+                }

+

+                filter.postFrame(renderManager, viewPort, buff, sceneFb);

+

+                Material mat = filter.getMaterial();

+                if (msDepth && filter.isRequiresDepthTexture()) {

+                    mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());

+                }

+

+                if (filter.isRequiresSceneTexture()) {

+                    mat.setTexture("Texture", tex);

+                    if (tex.getImage().getMultiSamples() > 1) {

+                        mat.setInt("NumSamples", tex.getImage().getMultiSamples());

+                    } else {

+                        mat.clearParam("NumSamples");

+                    }

+                }

+

+                buff = outputBuffer;

+                if (i != lastFilterIndex) {

+                    buff = filter.getRenderFrameBuffer();

+                    tex = filter.getRenderedTexture();

+

+                }

+                renderProcessing(r, buff, mat);

+            }

+        }

+    }

+

+    public void postFrame(FrameBuffer out) {

+

+        FrameBuffer sceneBuffer = renderFrameBuffer;

+        if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) {

+            renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer);

+        } else if (renderFrameBufferMS != null) {

+            sceneBuffer = renderFrameBufferMS;

+        }

+        renderFilterChain(renderer, sceneBuffer);        

+        renderer.setFrameBuffer(outputBuffer);

+        

+        //viewport can be null if no filters are enabled

+        if (viewPort != null) {

+            renderManager.setCamera(viewPort.getCamera(), false);

+        }

+

+    }

+

+    public void preFrame(float tpf) {

+        if (filters.isEmpty() || lastFilterIndex == -1) {

+            //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was

+            if (cameraInit) {

+                viewPort.getCamera().resize(originalWidth, originalHeight, true);

+                viewPort.getCamera().setViewPort(left, right, bottom, top);

+                viewPort.setOutputFrameBuffer(outputBuffer);

+                cameraInit = false;

+            }

+

+        } else {

+            if (renderFrameBufferMS != null) {

+                viewPort.setOutputFrameBuffer(renderFrameBufferMS);

+            } else {

+                viewPort.setOutputFrameBuffer(renderFrameBuffer);

+            }

+            //init of the camera if it wasn't already

+            if (!cameraInit) {

+                viewPort.getCamera().resize(width, height, true);

+                viewPort.getCamera().setViewPort(0, 1, 0, 1);

+            }

+        }

+

+        for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {

+            Filter filter = it.next();

+            if (filter.isEnabled()) {

+                filter.preFrame(tpf);

+            }

+        }

+

+    }

+

+    /**

+     * sets the filter to enabled or disabled

+     * @param filter

+     * @param enabled 

+     */

+    protected void setFilterState(Filter filter, boolean enabled) {

+        if (filters.contains(filter)) {

+            filter.enabled = enabled;

+            updateLastFilterIndex();

+        }

+    }

+

+    /**

+     * compute the index of the last filter to render

+     */

+    private void updateLastFilterIndex() {

+        lastFilterIndex = -1;

+        for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) {

+            if (filters.get(i).isEnabled()) {

+                lastFilterIndex = i;

+                return;

+            }

+        }

+        if (lastFilterIndex == -1) {

+            cleanup();

+        }

+    }

+

+    public void cleanup() {

+        if (viewPort != null) {

+            //reseting the viewport camera viewport to its initial value

+            viewPort.getCamera().resize(originalWidth, originalHeight, true);

+            viewPort.getCamera().setViewPort(left, right, bottom, top);

+            viewPort.setOutputFrameBuffer(outputBuffer);          

+            viewPort = null;

+        }

+

+    }

+

+    public void reshape(ViewPort vp, int w, int h) {

+        //this has no effect at first init but is useful when resizing the canvas with multi views

+        Camera cam = vp.getCamera();

+        cam.setViewPort(left, right, bottom, top);

+        //resizing the camera to fit the new viewport and saving original dimensions

+        cam.resize(w, h, false);

+        left = cam.getViewPortLeft();

+        right = cam.getViewPortRight();

+        top = cam.getViewPortTop();

+        bottom = cam.getViewPortBottom();

+        originalWidth = w;

+        originalHeight = h;

+        cam.setViewPort(0, 1, 0, 1);

+

+        //computing real dimension of the viewport and resizing he camera 

+        width = (int) (w * (Math.abs(right - left)));

+        height = (int) (h * (Math.abs(bottom - top)));

+        width = Math.max(1, width);

+        height = Math.max(1, height);

+        cam.resize(width, height, false);

+        cameraInit = true;

+        computeDepth = false;

+

+        if (renderFrameBuffer == null) {

+            outputBuffer = viewPort.getOutputFrameBuffer();

+        }

+

+        Collection<Caps> caps = renderer.getCaps();

+

+        //antialiasing on filters only supported in opengl 3 due to depth read problem

+        if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) {

+            renderFrameBufferMS = new FrameBuffer(width, height, numSamples);

+            if (caps.contains(Caps.OpenGL31)) {

+                Texture2D msColor = new Texture2D(width, height, numSamples, Format.RGBA8);

+                Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth);

+                renderFrameBufferMS.setDepthTexture(msDepth);

+                renderFrameBufferMS.setColorTexture(msColor);

+                filterTexture = msColor;

+                depthTexture = msDepth;

+            } else {

+                renderFrameBufferMS.setDepthBuffer(Format.Depth);

+                renderFrameBufferMS.setColorBuffer(Format.RGBA8);

+            }

+        }

+

+        if (numSamples <= 1 || !caps.contains(Caps.OpenGL31)) {

+            renderFrameBuffer = new FrameBuffer(width, height, 1);

+            renderFrameBuffer.setDepthBuffer(Format.Depth);

+            filterTexture = new Texture2D(width, height, Format.RGBA8);

+            renderFrameBuffer.setColorTexture(filterTexture);

+        }

+

+        for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {

+            Filter filter = it.next();

+            initFilter(filter, vp);

+        }

+

+        if (renderFrameBufferMS != null) {

+            viewPort.setOutputFrameBuffer(renderFrameBufferMS);

+        } else {

+            viewPort.setOutputFrameBuffer(renderFrameBuffer);

+        }

+    }

+

+    /**

+     * return the number of samples for antialiasing

+     * @return numSamples

+     */

+    public int getNumSamples() {

+        return numSamples;

+    }

+

+    /**

+     *

+     * Removes all the filters from this processor

+     */

+    public void removeAllFilters() {

+        filters.clear();

+        updateLastFilterIndex();

+    }

+

+    /**

+     * Sets the number of samples for antialiasing

+     * @param numSamples the number of Samples

+     */

+    public void setNumSamples(int numSamples) {

+        if (numSamples <= 0) {

+            throw new IllegalArgumentException("numSamples must be > 0");

+        }

+

+        this.numSamples = numSamples;

+    }

+

+    /**

+     * Sets the asset manager for this processor

+     * @param assetManager

+     */

+    public void setAssetManager(AssetManager assetManager) {

+        this.assetManager = assetManager;

+    }

+

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(numSamples, "numSamples", 0);

+        oc.writeSavableArrayList((ArrayList) filters, "filters", null);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        numSamples = ic.readInt("numSamples", 0);

+        filters = ic.readSavableArrayList("filters", null);

+        for (Filter filter : filters) {

+            filter.setProcessor(this);

+            setFilterState(filter, filter.isEnabled());

+        }

+        assetManager = im.getAssetManager();

+    }

+

+    /**

+     * For internal use only<br>

+     * returns the depth texture of the scene

+     * @return 

+     */

+    public Texture2D getDepthTexture() {

+        return depthTexture;

+    }

+

+    /**

+     * For internal use only<br>

+     * returns the rendered texture of the scene

+     * @return 

+     */

+    public Texture2D getFilterTexture() {

+        return filterTexture;

+    }

+}

diff --git a/engine/src/core/com/jme3/post/HDRRenderer.java b/engine/src/core/com/jme3/post/HDRRenderer.java
new file mode 100644
index 0000000..bac8334
--- /dev/null
+++ b/engine/src/core/com/jme3/post/HDRRenderer.java
@@ -0,0 +1,419 @@
+/*

+ * Copyright (c) 2009-2010 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.post;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.material.Material;

+import com.jme3.math.Vector2f;

+import com.jme3.renderer.*;

+import com.jme3.renderer.queue.RenderQueue;

+import com.jme3.texture.FrameBuffer;

+import com.jme3.texture.Image;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.MagFilter;

+import com.jme3.texture.Texture.MinFilter;

+import com.jme3.texture.Texture2D;

+import com.jme3.ui.Picture;

+import java.util.Collection;

+import java.util.logging.Logger;

+

+public class HDRRenderer implements SceneProcessor {

+

+    private static final int LUMMODE_NONE = 0x1,

+                             LUMMODE_ENCODE_LUM = 0x2,

+                             LUMMODE_DECODE_LUM = 0x3;

+

+    private Renderer renderer;

+    private RenderManager renderManager;

+    private ViewPort viewPort;

+    private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName());

+

+    private Camera fbCam = new Camera(1, 1);

+

+    private FrameBuffer msFB;

+

+    private FrameBuffer mainSceneFB;

+    private Texture2D mainScene;

+    private FrameBuffer scene64FB;

+    private Texture2D scene64;

+    private FrameBuffer scene8FB;

+    private Texture2D scene8;

+    private FrameBuffer scene1FB[] = new FrameBuffer[2];

+    private Texture2D scene1[] = new Texture2D[2];

+

+    private Material hdr64;

+    private Material hdr8;

+    private Material hdr1;

+    private Material tone;

+

+    private Picture fsQuad;

+    private float time = 0;

+    private int curSrc = -1;

+    private int oppSrc = -1;

+    private float blendFactor = 0;

+

+    private int numSamples = 0;

+    private float exposure = 0.18f;

+    private float whiteLevel = 100f;

+    private float throttle = -1;

+    private int maxIterations = -1;

+    private Image.Format bufFormat = Format.RGB16F;

+

+    private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps;

+    private MagFilter fbMagFilter = MagFilter.Bilinear;

+    private AssetManager manager;

+

+    private boolean enabled = true;

+

+    public HDRRenderer(AssetManager manager, Renderer renderer){

+        this.manager = manager;

+        this.renderer = renderer;

+        

+        Collection<Caps> caps = renderer.getCaps();

+        if (caps.contains(Caps.PackedFloatColorBuffer))

+            bufFormat = Format.RGB111110F;

+        else if (caps.contains(Caps.FloatColorBuffer))

+            bufFormat = Format.RGB16F;

+        else{

+            enabled = false;

+            return;

+        }

+    }

+

+    public boolean isEnabled() {

+        return enabled;

+    }

+

+    public void setSamples(int samples){

+        this.numSamples = samples;

+    }

+

+    public void setExposure(float exp){

+        this.exposure = exp;

+    }

+

+    public void setWhiteLevel(float whiteLevel){

+        this.whiteLevel = whiteLevel;

+    }

+

+    public void setMaxIterations(int maxIterations){

+        this.maxIterations = maxIterations;

+

+        // regenerate shaders if needed

+        if (hdr64 != null)

+            createLumShaders();

+    }

+

+    public void setThrottle(float throttle){

+        this.throttle = throttle;

+    }

+

+    public void setUseFastFilter(boolean fastFilter){

+        if (fastFilter){

+            fbMagFilter = MagFilter.Nearest;

+            fbMinFilter = MinFilter.NearestNoMipMaps;

+        }else{

+            fbMagFilter = MagFilter.Bilinear;

+            fbMinFilter = MinFilter.BilinearNoMipMaps;

+        }

+    }

+

+    public Picture createDisplayQuad(/*int mode, Texture tex*/){

+        if (scene64 == null)

+            return null;

+

+        Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");

+//        if (mode == LUMMODE_ENCODE_LUM)

+//            mat.setBoolean("EncodeLum", true);

+//        else if (mode == LUMMODE_DECODE_LUM)

+            mat.setBoolean("DecodeLum", true);

+            mat.setTexture("Texture", scene64);

+//        mat.setTexture("Texture", tex);

+        

+        Picture dispQuad = new Picture("Luminance Display");

+        dispQuad.setMaterial(mat);

+        return dispQuad;

+    }

+

+    private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mode,

+                                int iters, Texture tex){

+        Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");

+        

+        Vector2f blockSize = new Vector2f(1f / bufW, 1f / bufH);

+        Vector2f pixelSize = new Vector2f(1f / srcW, 1f / srcH);

+        Vector2f blocks = new Vector2f();

+        float numPixels = Float.POSITIVE_INFINITY;

+        if (iters != -1){

+            do {

+                pixelSize.multLocal(2);

+                blocks.set(blockSize.x / pixelSize.x,

+                           blockSize.y / pixelSize.y);

+                numPixels = blocks.x * blocks.y;

+            } while (numPixels > iters);

+        }else{

+            blocks.set(blockSize.x / pixelSize.x,

+                       blockSize.y / pixelSize.y);

+            numPixels = blocks.x * blocks.y;

+        }

+        System.out.println(numPixels);

+

+        mat.setBoolean("Blocks", true);

+        if (mode == LUMMODE_ENCODE_LUM)

+            mat.setBoolean("EncodeLum", true);

+        else if (mode == LUMMODE_DECODE_LUM)

+            mat.setBoolean("DecodeLum", true);

+

+        mat.setTexture("Texture", tex);

+        mat.setVector2("BlockSize", blockSize);

+        mat.setVector2("PixelSize", pixelSize);

+        mat.setFloat("NumPixels", numPixels);

+

+        return mat;

+    }

+

+    private void createLumShaders(){

+        int w = mainSceneFB.getWidth();

+        int h = mainSceneFB.getHeight();

+        hdr64 = createLumShader(w,  h,  64, 64, LUMMODE_ENCODE_LUM, maxIterations, mainScene);

+        hdr8  = createLumShader(64, 64, 8,  8,  LUMMODE_NONE,       maxIterations, scene64);

+        hdr1  = createLumShader(8,  8,  1,  1,  LUMMODE_NONE,       maxIterations, scene8);

+    }

+

+    private int opposite(int i){

+        return i == 1 ? 0 : 1;

+    }

+

+    private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){

+        if (dst == null){

+            fsQuad.setWidth(mainSceneFB.getWidth());

+            fsQuad.setHeight(mainSceneFB.getHeight());

+            fbCam.resize(mainSceneFB.getWidth(), mainSceneFB.getHeight(), true);

+        }else{

+            fsQuad.setWidth(dst.getWidth());

+            fsQuad.setHeight(dst.getHeight());

+            fbCam.resize(dst.getWidth(), dst.getHeight(), true);

+        }

+        fsQuad.setMaterial(mat);

+        fsQuad.updateGeometricState();

+        renderManager.setCamera(fbCam, true);

+

+        r.setFrameBuffer(dst);

+        r.clearBuffers(true, true, true);

+        renderManager.renderGeometry(fsQuad);

+    }

+

+    private void renderToneMap(Renderer r, FrameBuffer out){

+        tone.setFloat("A", exposure);

+        tone.setFloat("White", whiteLevel);

+        tone.setTexture("Lum", scene1[oppSrc]);

+        tone.setTexture("Lum2", scene1[curSrc]);

+        tone.setFloat("BlendFactor", blendFactor);

+        renderProcessing(r, out, tone);

+    }

+

+    private void updateAverageLuminance(Renderer r){

+        renderProcessing(r, scene64FB, hdr64);

+        renderProcessing(r, scene8FB, hdr8);

+        renderProcessing(r, scene1FB[curSrc], hdr1);

+    }

+

+    public boolean isInitialized(){

+        return viewPort != null;

+    }

+

+    public void reshape(ViewPort vp, int w, int h){

+        if (mainSceneFB != null){

+            renderer.deleteFrameBuffer(mainSceneFB);

+        }

+

+        mainSceneFB = new FrameBuffer(w, h, 1);

+        mainScene = new Texture2D(w, h, bufFormat);

+        mainSceneFB.setDepthBuffer(Format.Depth);

+        mainSceneFB.setColorTexture(mainScene);

+        mainScene.setMagFilter(fbMagFilter);

+        mainScene.setMinFilter(fbMinFilter);

+

+        if (msFB != null){

+            renderer.deleteFrameBuffer(msFB);

+        }

+

+        tone.setTexture("Texture", mainScene);

+        

+        Collection<Caps> caps = renderer.getCaps();

+        if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)){

+            msFB = new FrameBuffer(w, h, numSamples);

+            msFB.setDepthBuffer(Format.Depth);

+            msFB.setColorBuffer(bufFormat);

+            vp.setOutputFrameBuffer(msFB);

+        }else{

+            if (numSamples > 1)

+                logger.warning("FBO multisampling not supported on this GPU, request ignored.");

+

+            vp.setOutputFrameBuffer(mainSceneFB);

+        }

+

+        createLumShaders();

+    }

+

+    public void initialize(RenderManager rm, ViewPort vp){

+        if (!enabled)

+            return;

+

+        renderer = rm.getRenderer();

+        renderManager = rm;

+        viewPort = vp;

+

+        // loadInitial()

+        fsQuad = new Picture("HDR Fullscreen Quad");

+

+        Format lumFmt = Format.RGB8;

+        scene64FB = new FrameBuffer(64, 64, 1);

+        scene64 = new Texture2D(64, 64, lumFmt);

+        scene64FB.setColorTexture(scene64);

+        scene64.setMagFilter(fbMagFilter);

+        scene64.setMinFilter(fbMinFilter);

+

+        scene8FB = new FrameBuffer(8, 8, 1);

+        scene8 = new Texture2D(8, 8, lumFmt);

+        scene8FB.setColorTexture(scene8);

+        scene8.setMagFilter(fbMagFilter);

+        scene8.setMinFilter(fbMinFilter);

+

+        scene1FB[0] = new FrameBuffer(1, 1, 1);

+        scene1[0] = new Texture2D(1, 1, lumFmt);

+        scene1FB[0].setColorTexture(scene1[0]);

+

+        scene1FB[1] = new FrameBuffer(1, 1, 1);

+        scene1[1] = new Texture2D(1, 1, lumFmt);

+        scene1FB[1].setColorTexture(scene1[1]);

+

+        // prepare tonemap shader

+        tone = new Material(manager, "Common/MatDefs/Hdr/ToneMap.j3md");

+        tone.setFloat("A", 0.18f);

+        tone.setFloat("White", 100);

+

+        // load();

+        int w = vp.getCamera().getWidth();

+        int h = vp.getCamera().getHeight();

+        reshape(vp, w, h);

+

+        

+    }

+

+    public void preFrame(float tpf) {

+        if (!enabled)

+            return;

+

+        time += tpf;

+        blendFactor = (time / throttle);

+    }

+

+    public void postQueue(RenderQueue rq) {

+    }

+

+    public void postFrame(FrameBuffer out) {

+        if (!enabled)

+            return;

+

+        if (msFB != null){

+            // first render to multisampled FB

+//            renderer.setFrameBuffer(msFB);

+//            renderer.clearBuffers(true,true,true);

+//

+//            renderManager.renderViewPortRaw(viewPort);

+

+            // render back to non-multisampled FB

+            renderer.copyFrameBuffer(msFB, mainSceneFB);

+        }else{

+//            renderer.setFrameBuffer(mainSceneFB);

+//            renderer.clearBuffers(true,true,false);

+//

+//            renderManager.renderViewPortRaw(viewPort);

+        }

+

+        // should we update avg lum?

+        if (throttle == -1){

+            // update every frame

+            curSrc = 0;

+            oppSrc = 0;

+            blendFactor = 0;

+            time = 0;

+            updateAverageLuminance(renderer);

+        }else{

+            if (curSrc == -1){

+                curSrc = 0;

+                oppSrc = 0;

+

+                // initial update

+                updateAverageLuminance(renderer);

+

+                blendFactor = 0;

+                time = 0;

+            }else if (time > throttle){

+

+                // time to switch

+                oppSrc = curSrc;

+                curSrc = opposite(curSrc);

+

+                updateAverageLuminance(renderer);

+

+                blendFactor = 0;

+                time = 0;

+            }

+        }

+

+        // since out == mainSceneFB, tonemap into the main screen instead

+        //renderToneMap(renderer, out);

+        renderToneMap(renderer, null);

+

+        renderManager.setCamera(viewPort.getCamera(), false);

+    }

+

+    public void cleanup() {

+        if (!enabled)

+            return;

+

+        if (msFB != null)

+            renderer.deleteFrameBuffer(msFB);

+        if (mainSceneFB != null)

+            renderer.deleteFrameBuffer(mainSceneFB);

+        if (scene64FB != null){

+            renderer.deleteFrameBuffer(scene64FB);

+            renderer.deleteFrameBuffer(scene8FB);

+            renderer.deleteFrameBuffer(scene1FB[0]);

+            renderer.deleteFrameBuffer(scene1FB[1]);

+        }

+    }

+

+}

diff --git a/engine/src/core/com/jme3/post/PreDepthProcessor.java b/engine/src/core/com/jme3/post/PreDepthProcessor.java
new file mode 100644
index 0000000..2544aab
--- /dev/null
+++ b/engine/src/core/com/jme3/post/PreDepthProcessor.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Processor that lays depth first, this can improve performance in complex
+ * scenes.
+ */
+public class PreDepthProcessor implements SceneProcessor {
+
+    private RenderManager rm;
+    private ViewPort vp;
+    private AssetManager assetManager;
+    private Material preDepth;
+    private RenderState forcedRS;
+
+    public PreDepthProcessor(AssetManager assetManager){
+        this.assetManager = assetManager;
+        preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md");
+        preDepth.getAdditionalRenderState().setPolyOffset(0, 0);
+        preDepth.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Back);
+
+        forcedRS = new RenderState();
+        forcedRS.setDepthTest(true);
+        forcedRS.setDepthWrite(false);
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        this.rm = rm;
+        this.vp = vp;
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+        this.vp = vp;
+    }
+
+    public boolean isInitialized() {
+        return vp != null;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+        // lay depth first
+        rm.setForcedMaterial(preDepth);
+        rq.renderQueue(RenderQueue.Bucket.Opaque, rm, vp.getCamera(), false);
+        rm.setForcedMaterial(null);
+
+        rm.setForcedRenderState(forcedRS);
+    }
+
+    public void postFrame(FrameBuffer out) {
+        rm.setForcedRenderState(null);
+    }
+
+    public void cleanup() {
+        vp = null;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/post/SceneProcessor.java b/engine/src/core/com/jme3/post/SceneProcessor.java
new file mode 100644
index 0000000..7b73706
--- /dev/null
+++ b/engine/src/core/com/jme3/post/SceneProcessor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Scene processors are used to compute/render things before and after the classic render of the scene.
+ * They have to be added to a viewport and are rendered in the order they've been added
+ *
+ * @author Kirill Vainer
+ */
+public interface SceneProcessor {
+
+    /**
+     * Called in the render thread to initialize the scene processor.
+     *
+     * @param rm The render manager to which the SP was added to
+     * @param vp The viewport to which the SP is assigned
+     */
+    public void initialize(RenderManager rm, ViewPort vp);
+
+    /**
+     * Called when the resolution of the viewport has been changed.
+     * @param vp
+     */
+    public void reshape(ViewPort vp, int w, int h);
+
+    /**
+     * @return True if initialize() has been called on this SceneProcessor,
+     * false if otherwise.
+     */
+    public boolean isInitialized();
+
+    /**
+     * Called before a frame
+     *
+     * @param tpf Time per frame
+     */
+    public void preFrame(float tpf);
+
+    /**
+     * Called after the scene graph has been queued, but before it is flushed.
+     *
+     * @param rq The render queue
+     */
+    public void postQueue(RenderQueue rq);
+
+    /**
+     * Called after a frame has been rendered and the queue flushed.
+     *
+     * @param out The FB to which the scene was rendered.
+     */
+    public void postFrame(FrameBuffer out);
+
+    /**
+     * Called when the SP is removed from the RM.
+     */
+    public void cleanup();
+
+}
diff --git a/engine/src/core/com/jme3/post/package.html b/engine/src/core/com/jme3/post/package.html
new file mode 100644
index 0000000..632e715
--- /dev/null
+++ b/engine/src/core/com/jme3/post/package.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.post</code> package provides utilities for 
+render processing.
+<p>
+The {@link com.jme3.post.SceneProcessor} interface is used as the base interface
+for all render processing. The SceneProcessor contains hooks for various rendering
+events. 
+<p>
+One use of render processing is post-processing, which is applying effects
+on an already-rendered scene. The engine's post-processing system is implemented
+in the {@link com.jme3.post.FilterPostProcessor} class, which contains a list
+of {@link com.jme3.post.Filter filters}. Each are invoked in order to apply
+various effects on the rendered scene.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/renderer/Camera.java b/engine/src/core/com/jme3/renderer/Camera.java
new file mode 100644
index 0000000..74ba6cf
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Camera.java
@@ -0,0 +1,1436 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Camera</code> is a standalone, purely mathematical class for doing
+ * camera-related computations.
+ *
+ * <p>
+ * Given input data such as location, orientation (direction, left, up),
+ * and viewport settings, it can compute data necessary to render objects
+ * with the graphics library. Two matrices are generated, the view matrix
+ * transforms objects from world space into eye space, while the projection
+ * matrix transforms objects from eye space into clip space.
+ * </p>
+ * <p>Another purpose of the camera class is to do frustum culling operations,
+ * defined by six planes which define a 3D frustum shape, it is possible to
+ * test if an object bounded by a mathematically defined volume is inside
+ * the camera frustum, and thus to avoid rendering objects that are outside
+ * the frustum
+ * </p>
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class Camera implements Savable, Cloneable {
+
+    private static final Logger logger = Logger.getLogger(Camera.class.getName());
+
+    /**
+     * The <code>FrustumIntersect</code> enum is returned as a result
+     * of a culling check operation, 
+     * see {@link #contains(com.jme3.bounding.BoundingVolume) }
+     */
+    public enum FrustumIntersect {
+
+        /**
+         * defines a constant assigned to spatials that are completely outside
+         * of this camera's view frustum.
+         */
+        Outside,
+        /**
+         * defines a constant assigned to spatials that are completely inside
+         * the camera's view frustum.
+         */
+        Inside,
+        /**
+         * defines a constant assigned to spatials that are intersecting one of
+         * the six planes that define the view frustum.
+         */
+        Intersects;
+    }
+    /**
+     * LEFT_PLANE represents the left plane of the camera frustum.
+     */
+    private static final int LEFT_PLANE = 0;
+    /**
+     * RIGHT_PLANE represents the right plane of the camera frustum.
+     */
+    private static final int RIGHT_PLANE = 1;
+    /**
+     * BOTTOM_PLANE represents the bottom plane of the camera frustum.
+     */
+    private static final int BOTTOM_PLANE = 2;
+    /**
+     * TOP_PLANE represents the top plane of the camera frustum.
+     */
+    private static final int TOP_PLANE = 3;
+    /**
+     * FAR_PLANE represents the far plane of the camera frustum.
+     */
+    private static final int FAR_PLANE = 4;
+    /**
+     * NEAR_PLANE represents the near plane of the camera frustum.
+     */
+    private static final int NEAR_PLANE = 5;
+    /**
+     * FRUSTUM_PLANES represents the number of planes of the camera frustum.
+     */
+    private static final int FRUSTUM_PLANES = 6;
+    /**
+     * MAX_WORLD_PLANES holds the maximum planes allowed by the system.
+     */
+    private static final int MAX_WORLD_PLANES = 6;
+    /**
+     * Camera's location
+     */
+    protected Vector3f location;
+    /**
+     * The orientation of the camera.
+     */
+    protected Quaternion rotation;
+    /**
+     * Distance from camera to near frustum plane.
+     */
+    protected float frustumNear;
+    /**
+     * Distance from camera to far frustum plane.
+     */
+    protected float frustumFar;
+    /**
+     * Distance from camera to left frustum plane.
+     */
+    protected float frustumLeft;
+    /**
+     * Distance from camera to right frustum plane.
+     */
+    protected float frustumRight;
+    /**
+     * Distance from camera to top frustum plane.
+     */
+    protected float frustumTop;
+    /**
+     * Distance from camera to bottom frustum plane.
+     */
+    protected float frustumBottom;
+    //Temporary values computed in onFrustumChange that are needed if a
+    //call is made to onFrameChange.
+    protected float[] coeffLeft;
+    protected float[] coeffRight;
+    protected float[] coeffBottom;
+    protected float[] coeffTop;
+    //view port coordinates
+    /**
+     * Percent value on display where horizontal viewing starts for this camera.
+     * Default is 0.
+     */
+    protected float viewPortLeft;
+    /**
+     * Percent value on display where horizontal viewing ends for this camera.
+     * Default is 1.
+     */
+    protected float viewPortRight;
+    /**
+     * Percent value on display where vertical viewing ends for this camera.
+     * Default is 1.
+     */
+    protected float viewPortTop;
+    /**
+     * Percent value on display where vertical viewing begins for this camera.
+     * Default is 0.
+     */
+    protected float viewPortBottom;
+    /**
+     * Array holding the planes that this camera will check for culling.
+     */
+    protected Plane[] worldPlane;
+    /**
+     * A mask value set during contains() that allows fast culling of a Node's
+     * children.
+     */
+    private int planeState;
+    protected int width;
+    protected int height;
+    protected boolean viewportChanged = true;
+    /**
+     * store the value for field parallelProjection
+     */
+    private boolean parallelProjection;
+    protected Matrix4f projectionMatrixOverride;
+    protected Matrix4f viewMatrix = new Matrix4f();
+    protected Matrix4f projectionMatrix = new Matrix4f();
+    protected Matrix4f viewProjectionMatrix = new Matrix4f();
+    private BoundingBox guiBounding = new BoundingBox();
+    /** The camera's name. */
+    protected String name;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Camera() {
+        worldPlane = new Plane[MAX_WORLD_PLANES];
+        for (int i = 0; i < MAX_WORLD_PLANES; i++) {
+            worldPlane[i] = new Plane();
+        }
+    }
+
+    /**
+     * Constructor instantiates a new <code>Camera</code> object. All
+     * values of the camera are set to default.
+     */
+    public Camera(int width, int height) {
+        this();
+        location = new Vector3f();
+        rotation = new Quaternion();
+
+        frustumNear = 1.0f;
+        frustumFar = 2.0f;
+        frustumLeft = -0.5f;
+        frustumRight = 0.5f;
+        frustumTop = 0.5f;
+        frustumBottom = -0.5f;
+
+        coeffLeft = new float[2];
+        coeffRight = new float[2];
+        coeffBottom = new float[2];
+        coeffTop = new float[2];
+
+        viewPortLeft = 0.0f;
+        viewPortRight = 1.0f;
+        viewPortTop = 1.0f;
+        viewPortBottom = 0.0f;
+
+        this.width = width;
+        this.height = height;
+
+        onFrustumChange();
+        onViewPortChange();
+        onFrameChange();
+
+        logger.log(Level.INFO, "Camera created (W: {0}, H: {1})", new Object[]{width, height});
+    }
+
+    @Override
+    public Camera clone() {
+        try {
+            Camera cam = (Camera) super.clone();
+            cam.viewportChanged = true;
+            cam.planeState = 0;
+
+            cam.worldPlane = new Plane[MAX_WORLD_PLANES];
+            for (int i = 0; i < worldPlane.length; i++) {
+                cam.worldPlane[i] = worldPlane[i].clone();
+            }
+
+            cam.coeffLeft = new float[2];
+            cam.coeffRight = new float[2];
+            cam.coeffBottom = new float[2];
+            cam.coeffTop = new float[2];
+
+            cam.location = location.clone();
+            cam.rotation = rotation.clone();
+
+            if (projectionMatrixOverride != null) {
+                cam.projectionMatrixOverride = projectionMatrixOverride.clone();
+            }
+
+            cam.viewMatrix = viewMatrix.clone();
+            cam.projectionMatrix = projectionMatrix.clone();
+            cam.viewProjectionMatrix = viewProjectionMatrix.clone();
+            cam.guiBounding = (BoundingBox) guiBounding.clone();
+
+            cam.update();
+
+            return cam;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+    
+	/**
+	 * This method copise the settings of the given camera.
+	 * 
+	 * @param cam
+	 *            the camera we copy the settings from
+	 */
+    public void copyFrom(Camera cam) {
+    	location.set(cam.location);
+        rotation.set(cam.rotation);
+
+        frustumNear = cam.frustumNear;
+        frustumFar = cam.frustumFar;
+        frustumLeft = cam.frustumLeft;
+        frustumRight = cam.frustumRight;
+        frustumTop = cam.frustumTop;
+        frustumBottom = cam.frustumBottom;
+
+        coeffLeft[0] = cam.coeffLeft[0];
+        coeffLeft[1] = cam.coeffLeft[1];
+        coeffRight[0] = cam.coeffRight[0];
+        coeffRight[1] = cam.coeffRight[1];
+        coeffBottom[0] = cam.coeffBottom[0];
+        coeffBottom[1] = cam.coeffBottom[1];
+        coeffTop[0] = cam.coeffTop[0];
+        coeffTop[1] = cam.coeffTop[1];
+
+        viewPortLeft = cam.viewPortLeft;
+        viewPortRight = cam.viewPortRight;
+        viewPortTop = cam.viewPortTop;
+        viewPortBottom = cam.viewPortBottom;
+
+        this.width = cam.width;
+        this.height = cam.height;
+        
+        this.planeState = cam.planeState;
+        this.viewportChanged = cam.viewportChanged;
+        for (int i = 0; i < MAX_WORLD_PLANES; ++i) {
+            worldPlane[i].setNormal(cam.worldPlane[i].getNormal());
+            worldPlane[i].setConstant(cam.worldPlane[i].getConstant());
+        }
+        
+        this.parallelProjection = cam.parallelProjection;
+        if(cam.projectionMatrixOverride != null) {
+        	if(this.projectionMatrixOverride == null) {
+        		this.projectionMatrixOverride = cam.projectionMatrixOverride.clone();
+        	} else {
+        		this.projectionMatrixOverride.set(cam.projectionMatrixOverride);
+        	}
+        } else {
+        	this.projectionMatrixOverride = null;
+        }
+        this.viewMatrix.set(cam.viewMatrix);
+        this.projectionMatrix.set(cam.projectionMatrix);
+        this.viewProjectionMatrix.set(cam.viewProjectionMatrix);
+        
+        this.guiBounding.setXExtent(cam.guiBounding.getXExtent());
+        this.guiBounding.setYExtent(cam.guiBounding.getYExtent());
+        this.guiBounding.setZExtent(cam.guiBounding.getZExtent());
+        this.guiBounding.setCenter(cam.guiBounding.getCenter());
+        this.guiBounding.setCheckPlane(cam.guiBounding.getCheckPlane());
+        
+        this.name = cam.name;
+    }
+
+    /**
+     * This method sets the cameras name.
+     * @param name the cameras name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * This method returns the cameras name.
+     * @return the cameras name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets a clipPlane for this camera.
+     * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
+     * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
+     * more info here
+     * <ul>
+     * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a>
+     * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a>
+     * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a>
+     * </ul>
+     *
+     * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
+     * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
+     * @param clipPlane the plane
+     * @param side the side the camera stands from the plane
+     */
+    public void setClipPlane(Plane clipPlane, Plane.Side side) {
+        float sideFactor = 1;
+        if (side == Plane.Side.Negative) {
+            sideFactor = -1;
+        }
+        //we are on the other side of the plane no need to clip anymore.
+        if (clipPlane.whichSide(location) == side) {
+            return;
+        }
+        Matrix4f p = projectionMatrix.clone();
+
+        Matrix4f ivm = viewMatrix.clone();
+
+        Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant());
+        Vector3f pp = ivm.mult(point);
+        Vector3f pn = ivm.multNormal(clipPlane.getNormal(), null);
+        Vector4f clipPlaneV = new Vector4f(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, -(pp.dot(pn)) * sideFactor);
+
+        Vector4f v = new Vector4f(0, 0, 0, 0);
+
+        v.x = (Math.signum(clipPlaneV.x) + p.m02) / p.m00;
+        v.y = (Math.signum(clipPlaneV.y) + p.m12) / p.m11;
+        v.z = -1.0f;
+        v.w = (1.0f + p.m22) / p.m23;
+
+        float dot = clipPlaneV.dot(v);//clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w;
+        Vector4f c = clipPlaneV.mult(2.0f / dot);
+
+        p.m20 = c.x - p.m30;
+        p.m21 = c.y - p.m31;
+        p.m22 = c.z - p.m32;
+        p.m23 = c.w - p.m33;
+        setProjectionMatrix(p);
+    }
+
+    /**
+     * Sets a clipPlane for this camera.
+     * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
+     * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
+     * more info here
+     * <ul>
+     * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a></li>
+     * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a></li>
+     * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">
+     * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a></li>
+     * </ul>
+     *
+     * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
+     * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
+     * @param clipPlane the plane
+     */
+    public void setClipPlane(Plane clipPlane) {
+        setClipPlane(clipPlane, clipPlane.whichSide(location));
+    }
+
+    /**
+     * Resizes this camera's view with the given width and height. This is
+     * similar to constructing a new camera, but reusing the same Object. This
+     * method is called by an associated {@link RenderManager} to notify the camera of
+     * changes in the display dimensions.
+     *
+     * @param width the view width
+     * @param height the view height
+     * @param fixAspect If true, the camera's aspect ratio will be recomputed.
+     * Recomputing the aspect ratio requires changing the frustum values.
+     */
+    public void resize(int width, int height, boolean fixAspect) {
+        this.width = width;
+        this.height = height;
+        onViewPortChange();
+
+        if (fixAspect /*&& !parallelProjection*/) {
+            frustumRight = frustumTop * ((float) width / height);
+            frustumLeft = -frustumRight;
+            onFrustumChange();
+        }
+    }
+
+    /**
+     * <code>getFrustumBottom</code> returns the value of the bottom frustum
+     * plane.
+     *
+     * @return the value of the bottom frustum plane.
+     */
+    public float getFrustumBottom() {
+        return frustumBottom;
+    }
+
+    /**
+     * <code>setFrustumBottom</code> sets the value of the bottom frustum
+     * plane.
+     *
+     * @param frustumBottom the value of the bottom frustum plane.
+     */
+    public void setFrustumBottom(float frustumBottom) {
+        this.frustumBottom = frustumBottom;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getFrustumFar</code> gets the value of the far frustum plane.
+     *
+     * @return the value of the far frustum plane.
+     */
+    public float getFrustumFar() {
+        return frustumFar;
+    }
+
+    /**
+     * <code>setFrustumFar</code> sets the value of the far frustum plane.
+     *
+     * @param frustumFar the value of the far frustum plane.
+     */
+    public void setFrustumFar(float frustumFar) {
+        this.frustumFar = frustumFar;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getFrustumLeft</code> gets the value of the left frustum plane.
+     *
+     * @return the value of the left frustum plane.
+     */
+    public float getFrustumLeft() {
+        return frustumLeft;
+    }
+
+    /**
+     * <code>setFrustumLeft</code> sets the value of the left frustum plane.
+     *
+     * @param frustumLeft the value of the left frustum plane.
+     */
+    public void setFrustumLeft(float frustumLeft) {
+        this.frustumLeft = frustumLeft;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getFrustumNear</code> gets the value of the near frustum plane.
+     *
+     * @return the value of the near frustum plane.
+     */
+    public float getFrustumNear() {
+        return frustumNear;
+    }
+
+    /**
+     * <code>setFrustumNear</code> sets the value of the near frustum plane.
+     *
+     * @param frustumNear the value of the near frustum plane.
+     */
+    public void setFrustumNear(float frustumNear) {
+        this.frustumNear = frustumNear;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getFrustumRight</code> gets the value of the right frustum plane.
+     *
+     * @return frustumRight the value of the right frustum plane.
+     */
+    public float getFrustumRight() {
+        return frustumRight;
+    }
+
+    /**
+     * <code>setFrustumRight</code> sets the value of the right frustum plane.
+     *
+     * @param frustumRight the value of the right frustum plane.
+     */
+    public void setFrustumRight(float frustumRight) {
+        this.frustumRight = frustumRight;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getFrustumTop</code> gets the value of the top frustum plane.
+     *
+     * @return the value of the top frustum plane.
+     */
+    public float getFrustumTop() {
+        return frustumTop;
+    }
+
+    /**
+     * <code>setFrustumTop</code> sets the value of the top frustum plane.
+     *
+     * @param frustumTop the value of the top frustum plane.
+     */
+    public void setFrustumTop(float frustumTop) {
+        this.frustumTop = frustumTop;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>getLocation</code> retrieves the location vector of the camera.
+     *
+     * @return the position of the camera.
+     * @see Camera#getLocation()
+     */
+    public Vector3f getLocation() {
+        return location;
+    }
+
+    /**
+     * <code>getRotation</code> retrieves the rotation quaternion of the camera.
+     *
+     * @return the rotation of the camera.
+     */
+    public Quaternion getRotation() {
+        return rotation;
+    }
+
+    /**
+     * <code>getDirection</code> retrieves the direction vector the camera is
+     * facing.
+     *
+     * @return the direction the camera is facing.
+     * @see Camera#getDirection()
+     */
+    public Vector3f getDirection() {
+        return rotation.getRotationColumn(2);
+    }
+
+    /**
+     * <code>getLeft</code> retrieves the left axis of the camera.
+     *
+     * @return the left axis of the camera.
+     * @see Camera#getLeft()
+     */
+    public Vector3f getLeft() {
+        return rotation.getRotationColumn(0);
+    }
+
+    /**
+     * <code>getUp</code> retrieves the up axis of the camera.
+     *
+     * @return the up axis of the camera.
+     * @see Camera#getUp()
+     */
+    public Vector3f getUp() {
+        return rotation.getRotationColumn(1);
+    }
+
+    /**
+     * <code>getDirection</code> retrieves the direction vector the camera is
+     * facing.
+     *
+     * @return the direction the camera is facing.
+     * @see Camera#getDirection()
+     */
+    public Vector3f getDirection(Vector3f store) {
+        return rotation.getRotationColumn(2, store);
+    }
+
+    /**
+     * <code>getLeft</code> retrieves the left axis of the camera.
+     *
+     * @return the left axis of the camera.
+     * @see Camera#getLeft()
+     */
+    public Vector3f getLeft(Vector3f store) {
+        return rotation.getRotationColumn(0, store);
+    }
+
+    /**
+     * <code>getUp</code> retrieves the up axis of the camera.
+     *
+     * @return the up axis of the camera.
+     * @see Camera#getUp()
+     */
+    public Vector3f getUp(Vector3f store) {
+        return rotation.getRotationColumn(1, store);
+    }
+
+    /**
+     * <code>setLocation</code> sets the position of the camera.
+     *
+     * @param location the position of the camera.
+     */
+    public void setLocation(Vector3f location) {
+        this.location.set(location);
+        onFrameChange();
+    }
+
+    /**
+     * <code>setRotation</code> sets the orientation of this camera. 
+     * This will be equivelant to setting each of the axes:
+     * <code><br>
+     * cam.setLeft(rotation.getRotationColumn(0));<br>
+     * cam.setUp(rotation.getRotationColumn(1));<br>
+     * cam.setDirection(rotation.getRotationColumn(2));<br>
+     * </code>
+     *
+     * @param rotation the rotation of this camera
+     */
+    public void setRotation(Quaternion rotation) {
+        this.rotation.set(rotation);
+        onFrameChange();
+    }
+
+    /**
+     * <code>lookAtDirection</code> sets the direction the camera is facing
+     * given a direction and an up vector.
+     *
+     * @param direction the direction this camera is facing.
+     */
+    public void lookAtDirection(Vector3f direction, Vector3f up) {
+        this.rotation.lookAt(direction, up);
+        onFrameChange();
+    }
+
+    /**
+     * <code>setAxes</code> sets the axes (left, up and direction) for this
+     * camera.
+     *
+     * @param left      the left axis of the camera.
+     * @param up        the up axis of the camera.
+     * @param direction the direction the camera is facing.
+     * 
+     * @see Camera#setAxes(com.jme3.math.Quaternion) 
+     */
+    public void setAxes(Vector3f left, Vector3f up, Vector3f direction) {
+        this.rotation.fromAxes(left, up, direction);
+        onFrameChange();
+    }
+
+    /**
+     * <code>setAxes</code> uses a rotational matrix to set the axes of the
+     * camera.
+     *
+     * @param axes the matrix that defines the orientation of the camera.
+     */
+    public void setAxes(Quaternion axes) {
+        this.rotation.set(axes);
+        onFrameChange();
+    }
+
+    /**
+     * normalize normalizes the camera vectors.
+     */
+    public void normalize() {
+        this.rotation.normalizeLocal();
+        onFrameChange();
+    }
+
+    /**
+     * <code>setFrustum</code> sets the frustum of this camera object.
+     *
+     * @param near   the near plane.
+     * @param far    the far plane.
+     * @param left   the left plane.
+     * @param right  the right plane.
+     * @param top    the top plane.
+     * @param bottom the bottom plane.
+     * @see Camera#setFrustum(float, float, float, float,
+     *      float, float)
+     */
+    public void setFrustum(float near, float far, float left, float right,
+            float top, float bottom) {
+
+        frustumNear = near;
+        frustumFar = far;
+        frustumLeft = left;
+        frustumRight = right;
+        frustumTop = top;
+        frustumBottom = bottom;
+        onFrustumChange();
+    }
+
+    /**
+     * <code>setFrustumPerspective</code> defines the frustum for the camera.  This
+     * frustum is defined by a viewing angle, aspect ratio, and near/far planes
+     *
+     * @param fovY   Frame of view angle along the Y in degrees.
+     * @param aspect Width:Height ratio
+     * @param near   Near view plane distance
+     * @param far    Far view plane distance
+     */
+    public void setFrustumPerspective(float fovY, float aspect, float near,
+            float far) {
+        if (Float.isNaN(aspect) || Float.isInfinite(aspect)) {
+            // ignore.
+            logger.log(Level.WARNING, "Invalid aspect given to setFrustumPerspective: {0}", aspect);
+            return;
+        }
+
+        float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * near;
+        float w = h * aspect;
+        frustumLeft = -w;
+        frustumRight = w;
+        frustumBottom = -h;
+        frustumTop = h;
+        frustumNear = near;
+        frustumFar = far;
+
+        onFrustumChange();
+    }
+
+    /**
+     * <code>setFrame</code> sets the orientation and location of the camera.
+     *
+     * @param location  the point position of the camera.
+     * @param left      the left axis of the camera.
+     * @param up        the up axis of the camera.
+     * @param direction the facing of the camera.
+     * @see Camera#setFrame(com.jme3.math.Vector3f,
+     *      com.jme3.math.Vector3f, com.jme3.math.Vector3f, com.jme3.math.Vector3f)
+     */
+    public void setFrame(Vector3f location, Vector3f left, Vector3f up,
+            Vector3f direction) {
+
+        this.location = location;
+        this.rotation.fromAxes(left, up, direction);
+        onFrameChange();
+    }
+
+    /**
+     * <code>lookAt</code> is a convienence method for auto-setting the frame
+     * based on a world position the user desires the camera to look at. It
+     * repoints the camera towards the given position using the difference
+     * between the position and the current camera location as a direction
+     * vector and the worldUpVector to compute up and left camera vectors.
+     *
+     * @param pos           where to look at in terms of world coordinates
+     * @param worldUpVector a normalized vector indicating the up direction of the world.
+     *                      (typically {0, 1, 0} in jME.)
+     */
+    public void lookAt(Vector3f pos, Vector3f worldUpVector) {
+        TempVars vars = TempVars.get();
+        Vector3f newDirection = vars.vect1;
+        Vector3f newUp = vars.vect2;
+        Vector3f newLeft = vars.vect3;
+
+        newDirection.set(pos).subtractLocal(location).normalizeLocal();
+
+        newUp.set(worldUpVector).normalizeLocal();
+        if (newUp.equals(Vector3f.ZERO)) {
+            newUp.set(Vector3f.UNIT_Y);
+        }
+
+        newLeft.set(newUp).crossLocal(newDirection).normalizeLocal();
+        if (newLeft.equals(Vector3f.ZERO)) {
+            if (newDirection.x != 0) {
+                newLeft.set(newDirection.y, -newDirection.x, 0f);
+            } else {
+                newLeft.set(0f, newDirection.z, -newDirection.y);
+            }
+        }
+
+        newUp.set(newDirection).crossLocal(newLeft).normalizeLocal();
+
+        this.rotation.fromAxes(newLeft, newUp, newDirection);
+        this.rotation.normalizeLocal();
+        vars.release();
+
+        onFrameChange();
+    }
+
+    /**
+     * <code>setFrame</code> sets the orientation and location of the camera.
+     * 
+     * @param location
+     *            the point position of the camera.
+     * @param axes
+     *            the orientation of the camera.
+     */
+    public void setFrame(Vector3f location, Quaternion axes) {
+        this.location = location;
+        this.rotation.set(axes);
+        onFrameChange();
+    }
+
+    /**
+     * <code>update</code> updates the camera parameters by calling
+     * <code>onFrustumChange</code>,<code>onViewPortChange</code> and
+     * <code>onFrameChange</code>.
+     *
+     * @see Camera#update()
+     */
+    public void update() {
+        onFrustumChange();
+        onViewPortChange();
+        onFrameChange();
+    }
+
+    /**
+     * <code>getPlaneState</code> returns the state of the frustum planes. So
+     * checks can be made as to which frustum plane has been examined for
+     * culling thus far.
+     *
+     * @return the current plane state int.
+     */
+    public int getPlaneState() {
+        return planeState;
+    }
+
+    /**
+     * <code>setPlaneState</code> sets the state to keep track of tested
+     * planes for culling.
+     *
+     * @param planeState the updated state.
+     */
+    public void setPlaneState(int planeState) {
+        this.planeState = planeState;
+    }
+
+    /**
+     * <code>getViewPortLeft</code> gets the left boundary of the viewport
+     *
+     * @return the left boundary of the viewport
+     */
+    public float getViewPortLeft() {
+        return viewPortLeft;
+    }
+
+    /**
+     * <code>setViewPortLeft</code> sets the left boundary of the viewport
+     *
+     * @param left the left boundary of the viewport
+     */
+    public void setViewPortLeft(float left) {
+        viewPortLeft = left;
+        onViewPortChange();
+    }
+
+    /**
+     * <code>getViewPortRight</code> gets the right boundary of the viewport
+     *
+     * @return the right boundary of the viewport
+     */
+    public float getViewPortRight() {
+        return viewPortRight;
+    }
+
+    /**
+     * <code>setViewPortRight</code> sets the right boundary of the viewport
+     *
+     * @param right the right boundary of the viewport
+     */
+    public void setViewPortRight(float right) {
+        viewPortRight = right;
+        onViewPortChange();
+    }
+
+    /**
+     * <code>getViewPortTop</code> gets the top boundary of the viewport
+     *
+     * @return the top boundary of the viewport
+     */
+    public float getViewPortTop() {
+        return viewPortTop;
+    }
+
+    /**
+     * <code>setViewPortTop</code> sets the top boundary of the viewport
+     *
+     * @param top the top boundary of the viewport
+     */
+    public void setViewPortTop(float top) {
+        viewPortTop = top;
+        onViewPortChange();
+    }
+
+    /**
+     * <code>getViewPortBottom</code> gets the bottom boundary of the viewport
+     *
+     * @return the bottom boundary of the viewport
+     */
+    public float getViewPortBottom() {
+        return viewPortBottom;
+    }
+
+    /**
+     * <code>setViewPortBottom</code> sets the bottom boundary of the viewport
+     *
+     * @param bottom the bottom boundary of the viewport
+     */
+    public void setViewPortBottom(float bottom) {
+        viewPortBottom = bottom;
+        onViewPortChange();
+    }
+
+    /**
+     * <code>setViewPort</code> sets the boundaries of the viewport
+     *
+     * @param left   the left boundary of the viewport (default: 0)
+     * @param right  the right boundary of the viewport (default: 1)
+     * @param bottom the bottom boundary of the viewport (default: 0)
+     * @param top    the top boundary of the viewport (default: 1)
+     */
+    public void setViewPort(float left, float right, float bottom, float top) {
+        this.viewPortLeft = left;
+        this.viewPortRight = right;
+        this.viewPortBottom = bottom;
+        this.viewPortTop = top;
+        onViewPortChange();
+    }
+
+    /**
+     * Returns the pseudo distance from the given position to the near
+     * plane of the camera. This is used for render queue sorting.
+     * @param pos The position to compute a distance to.
+     * @return Distance from the far plane to the point.
+     */
+    public float distanceToNearPlane(Vector3f pos) {
+        return worldPlane[NEAR_PLANE].pseudoDistance(pos);
+    }
+
+    /**
+     * <code>contains</code> tests a bounding volume against the planes of the
+     * camera's frustum. The frustums planes are set such that the normals all
+     * face in towards the viewable scene. Therefore, if the bounding volume is
+     * on the negative side of the plane is can be culled out.
+     *
+     * NOTE: This method is used internally for culling, for public usage,
+     * the plane state of the bounding volume must be saved and restored, e.g:
+     * <code>BoundingVolume bv;<br/>
+     * Camera c;<br/>
+     * int planeState = bv.getPlaneState();<br/>
+     * bv.setPlaneState(0);<br/>
+     * c.contains(bv);<br/>
+     * bv.setPlaneState(plateState);<br/>
+     * </code>
+     *
+     * @param bound the bound to check for culling
+     * @return See enums in <code>FrustumIntersect</code>
+     */
+    public FrustumIntersect contains(BoundingVolume bound) {
+        if (bound == null) {
+            return FrustumIntersect.Inside;
+        }
+
+        int mask;
+        FrustumIntersect rVal = FrustumIntersect.Inside;
+
+        for (int planeCounter = FRUSTUM_PLANES; planeCounter >= 0; planeCounter--) {
+            if (planeCounter == bound.getCheckPlane()) {
+                continue; // we have already checked this plane at first iteration
+            }
+            int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
+//            int planeId = planeCounter;
+
+            mask = 1 << (planeId);
+            if ((planeState & mask) == 0) {
+                Plane.Side side = bound.whichSide(worldPlane[planeId]);
+
+                if (side == Plane.Side.Negative) {
+                    //object is outside of frustum
+                    bound.setCheckPlane(planeId);
+                    return FrustumIntersect.Outside;
+                } else if (side == Plane.Side.Positive) {
+                    //object is visible on *this* plane, so mark this plane
+                    //so that we don't check it for sub nodes.
+                    planeState |= mask;
+                } else {
+                    rVal = FrustumIntersect.Intersects;
+                }
+            }
+        }
+
+        return rVal;
+    }
+
+    /**
+     * <code>containsGui</code> tests a bounding volume against the ortho
+     * bounding box of the camera. A bounding box spanning from
+     * 0, 0 to Width, Height. Constrained by the viewport settings on the
+     * camera.
+     *
+     * @param bound the bound to check for culling
+     * @return True if the camera contains the gui element bounding volume.
+     */
+    public boolean containsGui(BoundingVolume bound) {
+        return guiBounding.intersects(bound);
+    }
+
+    /**
+     * @return the view matrix of the camera.
+     * The view matrix transforms world space into eye space.
+     * This matrix is usually defined by the position and
+     * orientation of the camera.
+     */
+    public Matrix4f getViewMatrix() {
+        return viewMatrix;
+    }
+
+    /**
+     * Overrides the projection matrix used by the camera. Will
+     * use the matrix for computing the view projection matrix as well.
+     * Use null argument to return to normal functionality.
+     *
+     * @param projMatrix
+     */
+    public void setProjectionMatrix(Matrix4f projMatrix) {
+        projectionMatrixOverride = projMatrix;
+        updateViewProjection();
+    }
+
+    /**
+     * @return the projection matrix of the camera.
+     * The view projection matrix  transforms eye space into clip space.
+     * This matrix is usually defined by the viewport and perspective settings
+     * of the camera.
+     */
+    public Matrix4f getProjectionMatrix() {
+        if (projectionMatrixOverride != null) {
+            return projectionMatrixOverride;
+        }
+
+        return projectionMatrix;
+    }
+
+    /**
+     * Updates the view projection matrix.
+     */
+    public void updateViewProjection() {
+        if (projectionMatrixOverride != null) {
+            viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix);
+        } else {
+            //viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix);
+            viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix);
+        }
+    }
+
+    /**
+     * @return The result of multiplying the projection matrix by the view
+     * matrix. This matrix is required for rendering an object. It is
+     * precomputed so as to not compute it every time an object is rendered.
+     */
+    public Matrix4f getViewProjectionMatrix() {
+        return viewProjectionMatrix;
+    }
+
+    /**
+     * @return True if the viewport (width, height, left, right, bottom, up)
+     * has been changed. This is needed in the renderer so that the proper
+     * viewport can be set-up.
+     */
+    public boolean isViewportChanged() {
+        return viewportChanged;
+    }
+
+    /**
+     * Clears the viewport changed flag once it has been updated inside
+     * the renderer.
+     */
+    public void clearViewportChanged() {
+        viewportChanged = false;
+    }
+
+    /**
+     * Called when the viewport has been changed.
+     */
+    public void onViewPortChange() {
+        viewportChanged = true;
+        setGuiBounding();
+    }
+
+    private void setGuiBounding() {
+        float sx = width * viewPortLeft;
+        float ex = width * viewPortRight;
+        float sy = height * viewPortBottom;
+        float ey = height * viewPortTop;
+        float xExtent = Math.max(0f, (ex - sx) / 2f);
+        float yExtent = Math.max(0f, (ey - sy) / 2f);
+        guiBounding.setCenter(new Vector3f(sx + xExtent, sy + yExtent, 0));
+        guiBounding.setXExtent(xExtent);
+        guiBounding.setYExtent(yExtent);
+        guiBounding.setZExtent(Float.MAX_VALUE);
+    }
+
+    /**
+     * <code>onFrustumChange</code> updates the frustum to reflect any changes
+     * made to the planes. The new frustum values are kept in a temporary
+     * location for use when calculating the new frame. The projection
+     * matrix is updated to reflect the current values of the frustum.
+     */
+    public void onFrustumChange() {
+        if (!isParallelProjection()) {
+            float nearSquared = frustumNear * frustumNear;
+            float leftSquared = frustumLeft * frustumLeft;
+            float rightSquared = frustumRight * frustumRight;
+            float bottomSquared = frustumBottom * frustumBottom;
+            float topSquared = frustumTop * frustumTop;
+
+            float inverseLength = FastMath.invSqrt(nearSquared + leftSquared);
+            coeffLeft[0] = frustumNear * inverseLength;
+            coeffLeft[1] = -frustumLeft * inverseLength;
+
+            inverseLength = FastMath.invSqrt(nearSquared + rightSquared);
+            coeffRight[0] = -frustumNear * inverseLength;
+            coeffRight[1] = frustumRight * inverseLength;
+
+            inverseLength = FastMath.invSqrt(nearSquared + bottomSquared);
+            coeffBottom[0] = frustumNear * inverseLength;
+            coeffBottom[1] = -frustumBottom * inverseLength;
+
+            inverseLength = FastMath.invSqrt(nearSquared + topSquared);
+            coeffTop[0] = -frustumNear * inverseLength;
+            coeffTop[1] = frustumTop * inverseLength;
+        } else {
+            coeffLeft[0] = 1;
+            coeffLeft[1] = 0;
+
+            coeffRight[0] = -1;
+            coeffRight[1] = 0;
+
+            coeffBottom[0] = 1;
+            coeffBottom[1] = 0;
+
+            coeffTop[0] = -1;
+            coeffTop[1] = 0;
+        }
+
+        projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight, frustumTop, frustumBottom, parallelProjection);
+//        projectionMatrix.transposeLocal();
+
+        // The frame is effected by the frustum values
+        // update it as well
+        onFrameChange();
+    }
+
+    /**
+     * <code>onFrameChange</code> updates the view frame of the camera.
+     */
+    public void onFrameChange() {
+        TempVars vars = TempVars.get();
+        
+        Vector3f left = getLeft(vars.vect1);
+        Vector3f direction = getDirection(vars.vect2);
+        Vector3f up = getUp(vars.vect3);
+
+        float dirDotLocation = direction.dot(location);
+
+        // left plane
+        Vector3f leftPlaneNormal = worldPlane[LEFT_PLANE].getNormal();
+        leftPlaneNormal.x = left.x * coeffLeft[0];
+        leftPlaneNormal.y = left.y * coeffLeft[0];
+        leftPlaneNormal.z = left.z * coeffLeft[0];
+        leftPlaneNormal.addLocal(direction.x * coeffLeft[1], direction.y
+                * coeffLeft[1], direction.z * coeffLeft[1]);
+        worldPlane[LEFT_PLANE].setConstant(location.dot(leftPlaneNormal));
+
+        // right plane
+        Vector3f rightPlaneNormal = worldPlane[RIGHT_PLANE].getNormal();
+        rightPlaneNormal.x = left.x * coeffRight[0];
+        rightPlaneNormal.y = left.y * coeffRight[0];
+        rightPlaneNormal.z = left.z * coeffRight[0];
+        rightPlaneNormal.addLocal(direction.x * coeffRight[1], direction.y
+                * coeffRight[1], direction.z * coeffRight[1]);
+        worldPlane[RIGHT_PLANE].setConstant(location.dot(rightPlaneNormal));
+
+        // bottom plane
+        Vector3f bottomPlaneNormal = worldPlane[BOTTOM_PLANE].getNormal();
+        bottomPlaneNormal.x = up.x * coeffBottom[0];
+        bottomPlaneNormal.y = up.y * coeffBottom[0];
+        bottomPlaneNormal.z = up.z * coeffBottom[0];
+        bottomPlaneNormal.addLocal(direction.x * coeffBottom[1], direction.y
+                * coeffBottom[1], direction.z * coeffBottom[1]);
+        worldPlane[BOTTOM_PLANE].setConstant(location.dot(bottomPlaneNormal));
+
+        // top plane
+        Vector3f topPlaneNormal = worldPlane[TOP_PLANE].getNormal();
+        topPlaneNormal.x = up.x * coeffTop[0];
+        topPlaneNormal.y = up.y * coeffTop[0];
+        topPlaneNormal.z = up.z * coeffTop[0];
+        topPlaneNormal.addLocal(direction.x * coeffTop[1], direction.y
+                * coeffTop[1], direction.z * coeffTop[1]);
+        worldPlane[TOP_PLANE].setConstant(location.dot(topPlaneNormal));
+
+        if (isParallelProjection()) {
+            worldPlane[LEFT_PLANE].setConstant(worldPlane[LEFT_PLANE].getConstant() + frustumLeft);
+            worldPlane[RIGHT_PLANE].setConstant(worldPlane[RIGHT_PLANE].getConstant() - frustumRight);
+            worldPlane[TOP_PLANE].setConstant(worldPlane[TOP_PLANE].getConstant() - frustumTop);
+            worldPlane[BOTTOM_PLANE].setConstant(worldPlane[BOTTOM_PLANE].getConstant() + frustumBottom);
+        }
+
+        // far plane
+        worldPlane[FAR_PLANE].setNormal(left);
+        worldPlane[FAR_PLANE].setNormal(-direction.x, -direction.y, -direction.z);
+        worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + frustumFar));
+
+        // near plane
+        worldPlane[NEAR_PLANE].setNormal(direction.x, direction.y, direction.z);
+        worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear);
+
+        viewMatrix.fromFrame(location, direction, up, left);
+        
+        vars.release();
+        
+//        viewMatrix.transposeLocal();
+        updateViewProjection();
+    }
+
+    /**
+     * @return true if parallel projection is enable, false if in normal perspective mode
+     * @see #setParallelProjection(boolean)
+     */
+    public boolean isParallelProjection() {
+        return this.parallelProjection;
+    }
+
+    /**
+     * Enable/disable parallel projection.
+     *
+     * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode
+     */
+    public void setParallelProjection(final boolean value) {
+        this.parallelProjection = value;
+        onFrustumChange();
+    }
+
+    /**
+     * @see Camera#getWorldCoordinates
+     */
+    public Vector3f getWorldCoordinates(Vector2f screenPos, float zPos) {
+        return getWorldCoordinates(screenPos, zPos, null);
+    }
+
+    /**
+     * @see Camera#getWorldCoordinates
+     */
+    public Vector3f getWorldCoordinates(Vector2f screenPosition,
+            float zPos, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        Matrix4f inverseMat = new Matrix4f(viewProjectionMatrix);
+        inverseMat.invertLocal();
+
+        store.set(
+                (screenPosition.x / getWidth() - viewPortLeft) / (viewPortRight - viewPortLeft) * 2 - 1,
+                (screenPosition.y / getHeight() - viewPortBottom) / (viewPortTop - viewPortBottom) * 2 - 1,
+                zPos * 2 - 1);
+
+        float w = inverseMat.multProj(store, store);
+        store.multLocal(1f / w);
+
+        return store;
+    }
+
+    /**
+     * Converts the given position from world space to screen space.
+     * 
+     * @see Camera#getScreenCoordinates
+     */
+    public Vector3f getScreenCoordinates(Vector3f worldPos) {
+        return getScreenCoordinates(worldPos, null);
+    }
+
+    /**
+     * Converts the given position from world space to screen space.
+     *
+     * @see Camera#getScreenCoordinates(Vector3f, Vector3f)
+     */
+    public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+//        TempVars vars = vars.lock();
+//        Quaternion tmp_quat = vars.quat1;
+//        tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 );
+//        viewProjectionMatrix.mult(tmp_quat, tmp_quat);
+//        tmp_quat.multLocal( 1.0f / tmp_quat.getW() );
+//        store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) * getWidth();
+//        store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) * getHeight();
+//        store.z = ( tmp_quat.getZ() + 1 ) / 2;
+//        vars.release();
+
+        float w = viewProjectionMatrix.multProj(worldPosition, store);
+        store.divideLocal(w);
+
+        store.x = ((store.x + 1f) * (viewPortRight - viewPortLeft) / 2f + viewPortLeft) * getWidth();
+        store.y = ((store.y + 1f) * (viewPortTop - viewPortBottom) / 2f + viewPortBottom) * getHeight();
+        store.z = (store.z + 1f) / 2f;
+
+        return store;
+    }
+
+    /**
+     * @return the width/resolution of the display.
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * @return the height/resolution of the display.
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    @Override
+    public String toString() {
+        return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n"
+                + "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n"
+                + "near=" + frustumNear + ", far=" + frustumFar + "]";
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(location, "location", Vector3f.ZERO);
+        capsule.write(rotation, "rotation", Quaternion.DIRECTION_Z);
+        capsule.write(frustumNear, "frustumNear", 1);
+        capsule.write(frustumFar, "frustumFar", 2);
+        capsule.write(frustumLeft, "frustumLeft", -0.5f);
+        capsule.write(frustumRight, "frustumRight", 0.5f);
+        capsule.write(frustumTop, "frustumTop", 0.5f);
+        capsule.write(frustumBottom, "frustumBottom", -0.5f);
+        capsule.write(coeffLeft, "coeffLeft", new float[2]);
+        capsule.write(coeffRight, "coeffRight", new float[2]);
+        capsule.write(coeffBottom, "coeffBottom", new float[2]);
+        capsule.write(coeffTop, "coeffTop", new float[2]);
+        capsule.write(viewPortLeft, "viewPortLeft", 0);
+        capsule.write(viewPortRight, "viewPortRight", 1);
+        capsule.write(viewPortTop, "viewPortTop", 1);
+        capsule.write(viewPortBottom, "viewPortBottom", 0);
+        capsule.write(width, "width", 0);
+        capsule.write(height, "height", 0);
+        capsule.write(name, "name", null);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone());
+        rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone());
+        frustumNear = capsule.readFloat("frustumNear", 1);
+        frustumFar = capsule.readFloat("frustumFar", 2);
+        frustumLeft = capsule.readFloat("frustumLeft", -0.5f);
+        frustumRight = capsule.readFloat("frustumRight", 0.5f);
+        frustumTop = capsule.readFloat("frustumTop", 0.5f);
+        frustumBottom = capsule.readFloat("frustumBottom", -0.5f);
+        coeffLeft = capsule.readFloatArray("coeffLeft", new float[2]);
+        coeffRight = capsule.readFloatArray("coeffRight", new float[2]);
+        coeffBottom = capsule.readFloatArray("coeffBottom", new float[2]);
+        coeffTop = capsule.readFloatArray("coeffTop", new float[2]);
+        viewPortLeft = capsule.readFloat("viewPortLeft", 0);
+        viewPortRight = capsule.readFloat("viewPortRight", 1);
+        viewPortTop = capsule.readFloat("viewPortTop", 1);
+        viewPortBottom = capsule.readFloat("viewPortBottom", 0);
+        width = capsule.readInt("width", 1);
+        height = capsule.readInt("height", 1);
+        name = capsule.readString("name", null);
+        onFrustumChange();
+        onViewPortChange();
+        onFrameChange();
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/Caps.java b/engine/src/core/com/jme3/renderer/Caps.java
new file mode 100644
index 0000000..4dd1ed8
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Caps.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.shader.Shader;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.FrameBuffer.RenderBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import java.util.Collection;
+
+/**
+ * <code>Caps</code> is an enum specifying a capability that the {@link Renderer}
+ * supports.
+ * 
+ * @author Kirill Vainer
+ */
+public enum Caps {
+
+    /**
+     * Supports {@link FrameBuffer FrameBuffers}.
+     * <p>
+     * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
+     * OpenGL ES: Renderer supports OpenGL ES 2.0.
+     */
+    FrameBuffer,
+
+    /**
+     * Supports framebuffer Multiple Render Targets (MRT)
+     * <p>
+     * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension
+     */
+    FrameBufferMRT,
+
+    /**
+     * Supports framebuffer multi-sampling
+     * <p>
+     * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension<br>
+     * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or
+     * GL_ANGLE_framebuffer_multisample.
+     */
+    FrameBufferMultisample,
+
+    /**
+     * Supports texture multi-sampling
+     * <p>
+     * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension<br>
+     * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture
+     * extension.
+     */
+    TextureMultisample,
+
+    /**
+     * Supports OpenGL 2.0 or OpenGL ES 2.0.
+     */
+    OpenGL20,
+    
+    /**
+     * Supports OpenGL 2.1
+     */
+    OpenGL21,
+    
+    /**
+     * Supports OpenGL 3.0
+     */
+    OpenGL30,
+    
+    /**
+     * Supports OpenGL 3.1
+     */
+    OpenGL31,
+    
+    /**
+     * Supports OpenGL 3.2
+     */
+    OpenGL32,
+
+    /**
+     * Supports OpenGL ARB program.
+     * <p>
+     * OpenGL: Renderer exposes ARB_vertex_program and ARB_fragment_program
+     * extensions.
+     */
+    ARBprogram,
+    
+    /**
+     * Supports GLSL 1.0
+     */
+    GLSL100,
+    
+    /**
+     * Supports GLSL 1.1
+     */
+    GLSL110,
+    
+    /**
+     * Supports GLSL 1.2
+     */
+    GLSL120,
+    
+    /**
+     * Supports GLSL 1.3
+     */
+    GLSL130,
+    
+    /**
+     * Supports GLSL 1.4
+     */
+    GLSL140,
+    
+    /**
+     * Supports GLSL 1.5
+     */
+    GLSL150,
+    
+    /**
+     * Supports GLSL 3.3
+     */
+    GLSL330,
+
+    /**
+     * Supports reading from textures inside the vertex shader.
+     */
+    VertexTextureFetch,
+
+    /**
+     * Supports geometry shader.
+     */
+    GeometryShader,
+
+    /**
+     * Supports texture arrays
+     */
+    TextureArray,
+
+    /**
+     * Supports texture buffers
+     */
+    TextureBuffer,
+
+    /**
+     * Supports floating point textures (Format.RGB16F)
+     */
+    FloatTexture,
+
+    /**
+     * Supports floating point FBO color buffers (Format.RGB16F)
+     */
+    FloatColorBuffer,
+
+    /**
+     * Supports floating point depth buffer
+     */
+    FloatDepthBuffer,
+
+    /**
+     * Supports Format.RGB111110F for textures
+     */
+    PackedFloatTexture,
+
+    /**
+     * Supports Format.RGB9E5 for textures
+     */
+    SharedExponentTexture,
+
+    /**
+     * Supports Format.RGB111110F for FBO color buffers
+     */
+    PackedFloatColorBuffer,
+
+    /**
+     * Supports Format.RGB9E5 for FBO color buffers
+     */
+    SharedExponentColorBuffer,
+
+    /**
+     * Supports Format.LATC for textures, this includes
+     * support for ATI's 3Dc texture compression.
+     */
+    TextureCompressionLATC,
+
+    /**
+     * Supports Non-Power-Of-Two (NPOT) textures and framebuffers
+     */
+    NonPowerOfTwoTextures,
+
+    /// Vertex Buffer features
+    MeshInstancing,
+
+    /**
+     * Supports VAO, or vertex buffer arrays
+     */
+    VertexBufferArray,
+
+    /**
+     * Supports multisampling on the screen
+     */
+    Multisample;
+
+    /**
+     * Returns true if given the renderer capabilities, the texture
+     * can be supported by the renderer.
+     * <p>
+     * This only checks the format of the texture, non-power-of-2
+     * textures are scaled automatically inside the renderer 
+     * if are not supported natively.
+     * 
+     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+     * @param tex The texture to check
+     * @return True if it is supported, false otherwise.
+     */
+    public static boolean supports(Collection<Caps> caps, Texture tex){
+        if (tex.getType() == Texture.Type.TwoDimensionalArray
+         && !caps.contains(Caps.TextureArray))
+            return false;
+
+        Image img = tex.getImage();
+        if (img == null)
+            return true;
+
+        Format fmt = img.getFormat();
+        switch (fmt){
+            case Depth32F:
+                return caps.contains(Caps.FloatDepthBuffer);
+            case LATC:
+                return caps.contains(Caps.TextureCompressionLATC);
+            case RGB16F_to_RGB111110F:
+            case RGB111110F:
+                return caps.contains(Caps.PackedFloatTexture);
+            case RGB16F_to_RGB9E5:
+            case RGB9E5:
+                return caps.contains(Caps.SharedExponentTexture);
+            default:
+                if (fmt.isFloatingPont())
+                    return caps.contains(Caps.FloatTexture);
+                        
+                return true;
+        }
+    }
+
+    /**
+     * Returns true if given the renderer capabilities, the framebuffer
+     * can be supported by the renderer.
+     * 
+     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+     * @param fb The framebuffer to check
+     * @return True if it is supported, false otherwise.
+     */
+    public static boolean supports(Collection<Caps> caps, FrameBuffer fb){
+        if (!caps.contains(Caps.FrameBuffer))
+            return false;
+
+        if (fb.getSamples() > 1
+         && !caps.contains(Caps.FrameBufferMultisample))
+            return false;
+
+        RenderBuffer colorBuf = fb.getColorBuffer();
+        RenderBuffer depthBuf = fb.getDepthBuffer();
+
+        if (depthBuf != null){
+            Format depthFmt = depthBuf.getFormat();
+            if (!depthFmt.isDepthFormat()){
+                return false;
+            }else{
+                if (depthFmt == Format.Depth32F
+                 && !caps.contains(Caps.FloatDepthBuffer))
+                    return false;
+            }
+        }
+        if (colorBuf != null){
+            Format colorFmt = colorBuf.getFormat();
+            if (colorFmt.isDepthFormat())
+                return false;
+
+            if (colorFmt.isCompressed())
+                return false;
+
+            switch (colorFmt){
+                case RGB111110F:
+                    return caps.contains(Caps.PackedFloatColorBuffer);
+                case RGB16F_to_RGB111110F:
+                case RGB16F_to_RGB9E5:
+                case RGB9E5:
+                    return false;
+                default:
+                    if (colorFmt.isFloatingPont())
+                        return caps.contains(Caps.FloatColorBuffer);
+
+                    return true;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if given the renderer capabilities, the shader
+     * can be supported by the renderer.
+     * 
+     * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+     * @param shader The shader to check
+     * @return True if it is supported, false otherwise.
+     */
+    public static boolean supports(Collection<Caps> caps, Shader shader){
+        String lang = shader.getLanguage();
+        if (lang.startsWith("GLSL")){
+            int ver = Integer.parseInt(lang.substring(4));
+            switch (ver){
+                case 100:
+                    return caps.contains(Caps.GLSL100);
+                case 110:
+                    return caps.contains(Caps.GLSL110);
+                case 120:
+                    return caps.contains(Caps.GLSL120);
+                case 130:
+                    return caps.contains(Caps.GLSL130);
+                case 140:
+                    return caps.contains(Caps.GLSL140);
+                case 150:
+                    return caps.contains(Caps.GLSL150);
+                case 330:
+                    return caps.contains(Caps.GLSL330);
+                default:
+                    return false;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/GL1Renderer.java b/engine/src/core/com/jme3/renderer/GL1Renderer.java
new file mode 100644
index 0000000..78df0c6
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/GL1Renderer.java
@@ -0,0 +1,26 @@
+package com.jme3.renderer;
+
+import com.jme3.material.FixedFuncBinding;
+
+/**
+ * Renderer sub-interface that is used for non-shader based renderers.
+ * <p>
+ * The <code>GL1Renderer</code> provides a single call, 
+ * {@link #setFixedFuncBinding(com.jme3.material.FixedFuncBinding, java.lang.Object) }
+ * which allows to set fixed functionality state.
+ * 
+ * @author Kirill Vainer
+ */
+public interface GL1Renderer extends Renderer {
+    
+    /**
+     * Set the fixed functionality state.
+     * <p>
+     * See {@link FixedFuncBinding} for various values that
+     * can be set.
+     * 
+     * @param ffBinding The binding to set
+     * @param val The value
+     */
+    public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val);
+}
diff --git a/engine/src/core/com/jme3/renderer/IDList.java b/engine/src/core/com/jme3/renderer/IDList.java
new file mode 100644
index 0000000..2db7294
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/IDList.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import java.util.Arrays;
+
+/**
+ * A specialized data-structure used to optimize state changes of "slot"
+ * based state. 
+ */
+public class IDList {
+
+    public int[] newList = new int[16];
+    public int[] oldList = new int[16];
+    public int newLen = 0;
+    public int oldLen = 0;
+
+    /**
+     * Reset all states to zero
+     */
+    public void reset(){
+        newLen = 0;
+        oldLen = 0;
+        Arrays.fill(newList, 0);
+        Arrays.fill(oldList, 0);
+    }
+
+    /**
+     * Adds an index to the new list.
+     * If the index was not in the old list, false is returned,
+     * if the index was in the old list, it is removed from the old
+     * list and true is returned.
+     * 
+     * @param idx The index to move
+     * @return True if it existed in old list and was removed
+     * from there, false otherwise.
+     */
+    public boolean moveToNew(int idx){
+        if (newLen == 0 || newList[newLen-1] != idx)
+            // add item to newList first
+            newList[newLen++] = idx;
+
+        // find idx in oldList, if removed successfuly, return true.
+        for (int i = 0; i < oldLen; i++){
+            if (oldList[i] == idx){
+                // found index in slot i
+                // delete index from old list
+                oldLen --;
+                for (int j = i; j < oldLen; j++){
+                    oldList[j] = oldList[j+1];
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Copies the new list to the old list, and clears the new list.
+     */
+    public void copyNewToOld(){
+        System.arraycopy(newList, 0, oldList, 0, newLen);
+        oldLen = newLen;
+        newLen = 0;
+    }
+
+    /**
+     * Prints the contents of the lists
+     */
+    public void print(){
+        if (newLen > 0){
+            System.out.print("New List: ");
+            for (int i = 0; i < newLen; i++){
+                if (i == newLen -1)
+                    System.out.println(newList[i]);
+                else
+                    System.out.print(newList[i]+", ");
+            }
+        }
+        if (oldLen > 0){
+            System.out.print("Old List: ");
+            for (int i = 0; i < oldLen; i++){
+                if (i == oldLen -1)
+                    System.out.println(oldList[i]);
+                else
+                    System.out.print(oldList[i]+", ");
+            }
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/RenderContext.java b/engine/src/core/com/jme3/renderer/RenderContext.java
new file mode 100644
index 0000000..ee9ff9c
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RenderContext.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+
+/**
+ * Represents the current state of the graphics library. This class is used
+ * internally to reduce state changes. NOTE: This class is specific to OpenGL.
+ */
+public class RenderContext {
+
+    /**
+     * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+     */
+    public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off;
+
+    /**
+     * @see RenderState#setDepthTest(boolean) 
+     */
+    public boolean depthTestEnabled = false;
+
+    /**
+     * @see RenderState#setAlphaTest(boolean) 
+     */
+    public boolean alphaTestEnabled = false;
+
+    /**
+     * @see RenderState#setDepthWrite(boolean) 
+     */
+    public boolean depthWriteEnabled = true;
+
+    /**
+     * @see RenderState#setColorWrite(boolean) 
+     */
+    public boolean colorWriteEnabled = true;
+
+    /**
+     * @see Renderer#setClipRect(int, int, int, int) 
+     */
+    public boolean clipRectEnabled = false;
+
+    /**
+     * @see RenderState#setPolyOffset(float, float) 
+     */
+    public boolean polyOffsetEnabled = false;
+    
+    /**
+     * @see RenderState#setPolyOffset(float, float) 
+     */
+    public float polyOffsetFactor = 0;
+    
+    /**
+     * @see RenderState#setPolyOffset(float, float) 
+     */
+    public float polyOffsetUnits = 0;
+
+    /**
+     * For normals only. Uses GL_NORMALIZE.
+     * 
+     * @see VertexBuffer#setNormalized(boolean) 
+     */
+    public boolean normalizeEnabled = false;
+
+    /**
+     * For glMatrixMode.
+     * 
+     * @see Renderer#setWorldMatrix(com.jme3.math.Matrix4f) 
+     * @see Renderer#setViewProjectionMatrices(com.jme3.math.Matrix4f, com.jme3.math.Matrix4f) 
+     */
+    public int matrixMode = -1;
+
+    /**
+     * @see Mesh#setPointSize(float) 
+     */
+    public float pointSize = 1;
+    
+    /**
+     * @see Mesh#setLineWidth(float) 
+     */
+    public float lineWidth = 1;
+
+    /**
+     * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) 
+     */
+    public RenderState.BlendMode blendMode = RenderState.BlendMode.Off;
+
+    /**
+     * @see RenderState#setWireframe(boolean) 
+     */
+    public boolean wireframe = false;
+
+    /**
+     * @see RenderState#setPointSprite(boolean) 
+     */
+    public boolean pointSprite = false;
+
+    /**
+     * @see Renderer#setShader(com.jme3.shader.Shader) 
+     */
+    public int boundShaderProgram;
+
+    /**
+     * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 
+     */
+    public int boundFBO = 0;
+
+    /**
+     * Currently bound Renderbuffer
+     * 
+     * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 
+     */
+    public int boundRB = 0;
+
+    /**
+     * Currently bound draw buffer
+     * -2 = GL_NONE
+     * -1 = GL_BACK
+     *  0 = GL_COLOR_ATTACHMENT0
+     *  n = GL_COLOR_ATTACHMENTn
+     *  where n is an integer greater than 1
+     * 
+     * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 
+     * @see FrameBuffer#setTargetIndex(int) 
+     */
+    public int boundDrawBuf = -1;
+
+    /**
+     * Currently bound read buffer
+     *
+     * @see RenderContext#boundDrawBuf
+     * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 
+     * @see FrameBuffer#setTargetIndex(int) 
+     */
+    public int boundReadBuf = -1;
+
+    /**
+     * Currently bound element array vertex buffer.
+     * 
+     * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) 
+     */
+    public int boundElementArrayVBO;
+
+    /**
+     * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) 
+     */
+    public int boundVertexArray;
+
+    /**
+     * Currently bound array vertex buffer.
+     * 
+     * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) 
+     */
+    public int boundArrayVBO;
+
+    public int numTexturesSet = 0;
+
+    /**
+     * Current bound texture IDs for each texture unit.
+     * 
+     * @see Renderer#setTexture(int, com.jme3.texture.Texture) 
+     */
+    public Image[] boundTextures = new Image[16];
+
+    /**
+     * IDList for texture units
+     * 
+     * @see Renderer#setTexture(int, com.jme3.texture.Texture) 
+     */
+    public IDList textureIndexList = new IDList();
+
+    /**
+     * Currently bound texture unit
+     * 
+     * @see Renderer#setTexture(int, com.jme3.texture.Texture) 
+     */
+    public int boundTextureUnit = 0;
+
+    /**
+     * Stencil Buffer state
+     */
+    public boolean stencilTest = false;
+    public RenderState.StencilOperation frontStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+    public RenderState.StencilOperation frontStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+    public RenderState.StencilOperation frontStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+    public RenderState.StencilOperation backStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+    public RenderState.StencilOperation backStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+    public RenderState.StencilOperation backStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+    public RenderState.TestFunction frontStencilFunction = RenderState.TestFunction.Always;
+    public RenderState.TestFunction backStencilFunction = RenderState.TestFunction.Always;
+
+    /**
+     * Vertex attribs currently bound and enabled. If a slot is null, then
+     * it is disabled.
+     */
+    public VertexBuffer[] boundAttribs = new VertexBuffer[16];
+
+    /**
+     * IDList for vertex attributes
+     */
+    public IDList attribIndexList = new IDList();
+    
+    /**
+     * Ambient color (GL1 only)
+     */
+    public ColorRGBA ambient;
+    
+    /**
+     * Diffuse color (GL1 only)
+     */
+    public ColorRGBA diffuse;
+    
+    /**
+     * Specular color (GL1 only)
+     */
+    public ColorRGBA specular;
+    
+    /**
+     * Material color (GL1 only)
+     */
+    public ColorRGBA color;
+    
+    /**
+     * Shininess (GL1 only)
+     */
+    public float shininess;
+    
+    /**
+     * Use vertex color (GL1 only)
+     */
+    public boolean useVertexColor;
+
+    /**
+     * Reset the RenderContext to default GL state
+     */
+    public void reset(){
+        cullMode = RenderState.FaceCullMode.Off;
+        depthTestEnabled = false;
+        alphaTestEnabled = false;
+        depthWriteEnabled = false;
+        colorWriteEnabled = false;
+        clipRectEnabled = false;
+        polyOffsetEnabled = false;
+        polyOffsetFactor = 0;
+        polyOffsetUnits = 0;
+        normalizeEnabled = false;
+        matrixMode = -1;
+        pointSize = 1;
+        blendMode = RenderState.BlendMode.Off;
+        wireframe = false;
+        boundShaderProgram = 0;
+        boundFBO = 0;
+        boundRB = 0;
+        boundDrawBuf = -1; 
+        boundReadBuf = -1;
+        boundElementArrayVBO = 0;
+        boundVertexArray = 0;
+        boundArrayVBO = 0;
+        numTexturesSet = 0;
+        for (int i = 0; i < boundTextures.length; i++)
+            boundTextures[i] = null;
+
+        textureIndexList.reset();
+        boundTextureUnit = 0;
+        for (int i = 0; i < boundAttribs.length; i++)
+            boundAttribs[i] = null;
+
+        attribIndexList.reset();
+        
+        stencilTest = false;
+        frontStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+        frontStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+        frontStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+        backStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+        backStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+        backStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+        frontStencilFunction = RenderState.TestFunction.Always;
+        backStencilFunction = RenderState.TestFunction.Always;
+        
+        ambient = diffuse = specular = color = null;
+        shininess = 0;
+        useVertexColor = false;
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/RenderManager.java b/engine/src/core/com/jme3/renderer/RenderManager.java
new file mode 100644
index 0000000..1d58d22
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RenderManager.java
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.material.RenderState;
+import com.jme3.material.Technique;
+import com.jme3.math.*;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.*;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
+import com.jme3.system.NullRenderer;
+import com.jme3.system.Timer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.TempVars;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * <code>RenderManager</code> is a high-level rendering interface that is
+ * above the Renderer implementation. RenderManager takes care
+ * of rendering the scene graphs attached to each viewport and
+ * handling SceneProcessors.
+ *
+ * @see SceneProcessor
+ * @see ViewPort
+ * @see Spatial
+ */
+public class RenderManager {
+
+    private static final Logger logger = Logger.getLogger(RenderManager.class.getName());
+    
+    private Renderer renderer;
+    private Timer timer;
+    private ArrayList<ViewPort> preViewPorts = new ArrayList<ViewPort>();
+    private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
+    private ArrayList<ViewPort> postViewPorts = new ArrayList<ViewPort>();
+    private Camera prevCam = null;
+    private Material forcedMaterial = null;
+    private String forcedTechnique = null;
+    private RenderState forcedRenderState = null;
+    private boolean shader;
+    private int viewX, viewY, viewWidth, viewHeight;
+    private float near, far;
+    private Matrix4f orthoMatrix = new Matrix4f();
+    private Matrix4f viewMatrix = new Matrix4f();
+    private Matrix4f projMatrix = new Matrix4f();
+    private Matrix4f viewProjMatrix = new Matrix4f();
+    private Matrix4f worldMatrix = new Matrix4f();
+    private Vector3f camUp = new Vector3f(),
+            camLeft = new Vector3f(),
+            camDir = new Vector3f(),
+            camLoc = new Vector3f();
+    //temp technique
+    private String tmpTech;
+    private boolean handleTranlucentBucket = true;
+
+    /**
+     * Create a high-level rendering interface over the
+     * low-level rendering interface.
+     * @param renderer
+     */
+    public RenderManager(Renderer renderer) {
+        this.renderer = renderer;
+        //this.shader = renderer.getCaps().contains(Caps.GLSL100);
+    }
+
+    /**
+     * Returns the pre ViewPort with the given name.
+     * 
+     * @param viewName The name of the pre ViewPort to look up
+     * @return The ViewPort, or null if not found.
+     * 
+     * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public ViewPort getPreView(String viewName) {
+        for (int i = 0; i < preViewPorts.size(); i++) {
+            if (preViewPorts.get(i).getName().equals(viewName)) {
+                return preViewPorts.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes the specified pre ViewPort.
+     * 
+     * @param view The pre ViewPort to remove
+     * @return True if the ViewPort was removed successfully.
+     * 
+     * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public boolean removePreView(ViewPort view) {
+        return preViewPorts.remove(view);
+    }
+
+    /**
+     * Returns the main ViewPort with the given name.
+     * 
+     * @param viewName The name of the main ViewPort to look up
+     * @return The ViewPort, or null if not found.
+     * 
+     * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public ViewPort getMainView(String viewName) {
+        for (int i = 0; i < viewPorts.size(); i++) {
+            if (viewPorts.get(i).getName().equals(viewName)) {
+                return viewPorts.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes the main ViewPort with the specified name.
+     * 
+     * @param viewName The main ViewPort name to remove
+     * @return True if the ViewPort was removed successfully.
+     * 
+     * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public boolean removeMainView(String viewName) {
+        for (int i = 0; i < viewPorts.size(); i++) {
+            if (viewPorts.get(i).getName().equals(viewName)) {
+                viewPorts.remove(i);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes the specified main ViewPort.
+     * 
+     * @param view The main ViewPort to remove
+     * @return True if the ViewPort was removed successfully.
+     * 
+     * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public boolean removeMainView(ViewPort view) {
+        return viewPorts.remove(view);
+    }
+
+    /**
+     * Returns the post ViewPort with the given name.
+     * 
+     * @param viewName The name of the post ViewPort to look up
+     * @return The ViewPort, or null if not found.
+     * 
+     * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public ViewPort getPostView(String viewName) {
+        for (int i = 0; i < postViewPorts.size(); i++) {
+            if (postViewPorts.get(i).getName().equals(viewName)) {
+                return postViewPorts.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes the post ViewPort with the specified name.
+     * 
+     * @param viewName The post ViewPort name to remove
+     * @return True if the ViewPort was removed successfully.
+     * 
+     * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public boolean removePostView(String viewName) {
+        for (int i = 0; i < postViewPorts.size(); i++) {
+            if (postViewPorts.get(i).getName().equals(viewName)) {
+                postViewPorts.remove(i);
+
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes the specified post ViewPort.
+     * 
+     * @param view The post ViewPort to remove
+     * @return True if the ViewPort was removed successfully.
+     * 
+     * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public boolean removePostView(ViewPort view) {
+        return postViewPorts.remove(view);
+    }
+
+    /**
+     * Returns a read-only list of all pre ViewPorts
+     * @return a read-only list of all pre ViewPorts
+     * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public List<ViewPort> getPreViews() {
+        return Collections.unmodifiableList(preViewPorts);
+    }
+
+    /**
+     * Returns a read-only list of all main ViewPorts
+     * @return a read-only list of all main ViewPorts
+     * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public List<ViewPort> getMainViews() {
+        return Collections.unmodifiableList(viewPorts);
+    }
+
+    /**
+     * Returns a read-only list of all post ViewPorts
+     * @return a read-only list of all post ViewPorts
+     * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public List<ViewPort> getPostViews() {
+        return Collections.unmodifiableList(postViewPorts);
+    }
+
+    /**
+     * Creates a new pre ViewPort, to display the given camera's content.
+     * <p>
+     * The view will be processed before the main and post viewports.
+     */
+    public ViewPort createPreView(String viewName, Camera cam) {
+        ViewPort vp = new ViewPort(viewName, cam);
+        preViewPorts.add(vp);
+        return vp;
+    }
+
+    /**
+     * Creates a new main ViewPort, to display the given camera's content.
+     * <p>
+     * The view will be processed before the post viewports but after
+     * the pre viewports.
+     */
+    public ViewPort createMainView(String viewName, Camera cam) {
+        ViewPort vp = new ViewPort(viewName, cam);
+        viewPorts.add(vp);
+        return vp;
+    }
+
+    /**
+     * Creates a new post ViewPort, to display the given camera's content.
+     * <p>
+     * The view will be processed after the pre and main viewports.
+     */
+    public ViewPort createPostView(String viewName, Camera cam) {
+        ViewPort vp = new ViewPort(viewName, cam);
+        postViewPorts.add(vp);
+        return vp;
+    }
+
+    private void notifyReshape(ViewPort vp, int w, int h) {
+        List<SceneProcessor> processors = vp.getProcessors();
+        for (SceneProcessor proc : processors) {
+            if (!proc.isInitialized()) {
+                proc.initialize(this, vp);
+            } else {
+                proc.reshape(vp, w, h);
+            }
+        }
+    }
+
+    /**
+     * Internal use only.
+     * Updates the resolution of all on-screen cameras to match
+     * the given width and height.
+     */
+    public void notifyReshape(int w, int h) {
+        for (ViewPort vp : preViewPorts) {
+            if (vp.getOutputFrameBuffer() == null) {
+                Camera cam = vp.getCamera();
+                cam.resize(w, h, true);
+            }
+            notifyReshape(vp, w, h);
+        }
+        for (ViewPort vp : viewPorts) {
+            if (vp.getOutputFrameBuffer() == null) {
+                Camera cam = vp.getCamera();
+                cam.resize(w, h, true);
+            }
+            notifyReshape(vp, w, h);
+        }
+        for (ViewPort vp : postViewPorts) {
+            if (vp.getOutputFrameBuffer() == null) {
+                Camera cam = vp.getCamera();
+                cam.resize(w, h, true);
+            }
+            notifyReshape(vp, w, h);
+        }
+    }
+
+    /**
+     * Internal use only.
+     * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
+     * based on the current world state.
+     */
+    public void updateUniformBindings(List<Uniform> params) {
+        // assums worldMatrix is properly set.
+        TempVars vars = TempVars.get();
+
+        Matrix4f tempMat4 = vars.tempMat4;
+        Matrix3f tempMat3 = vars.tempMat3;
+        Vector2f tempVec2 = vars.vect2d;
+        Quaternion tempVec4 = vars.quat1;
+
+        for (int i = 0; i < params.size(); i++) {
+            Uniform u = params.get(i);
+            switch (u.getBinding()) {
+                case WorldMatrix:
+                    u.setValue(VarType.Matrix4, worldMatrix);
+                    break;
+                case ViewMatrix:
+                    u.setValue(VarType.Matrix4, viewMatrix);
+                    break;
+                case ProjectionMatrix:
+                    u.setValue(VarType.Matrix4, projMatrix);
+                    break;
+                case ViewProjectionMatrix:
+                    u.setValue(VarType.Matrix4, viewProjMatrix);
+                    break;
+                case WorldViewMatrix:
+                    tempMat4.set(viewMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case NormalMatrix:
+                    tempMat4.set(viewMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    tempMat4.toRotationMatrix(tempMat3);
+                    tempMat3.invertLocal();
+                    tempMat3.transposeLocal();
+                    u.setValue(VarType.Matrix3, tempMat3);
+                    break;
+                case WorldViewProjectionMatrix:
+                    tempMat4.set(viewProjMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case WorldMatrixInverse:
+                    tempMat4.multLocal(worldMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case ViewMatrixInverse:
+                    tempMat4.set(viewMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case ProjectionMatrixInverse:
+                    tempMat4.set(projMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case ViewProjectionMatrixInverse:
+                    tempMat4.set(viewProjMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case WorldViewMatrixInverse:
+                    tempMat4.set(viewMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case NormalMatrixInverse:
+                    tempMat4.set(viewMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    tempMat4.toRotationMatrix(tempMat3);
+                    tempMat3.invertLocal();
+                    tempMat3.transposeLocal();
+                    tempMat3.invertLocal();
+                    u.setValue(VarType.Matrix3, tempMat3);
+                    break;
+                case WorldViewProjectionMatrixInverse:
+                    tempMat4.set(viewProjMatrix);
+                    tempMat4.multLocal(worldMatrix);
+                    tempMat4.invertLocal();
+                    u.setValue(VarType.Matrix4, tempMat4);
+                    break;
+                case ViewPort:
+                    tempVec4.set(viewX, viewY, viewWidth, viewHeight);
+                    u.setValue(VarType.Vector4, tempVec4);
+                    break;
+                case Resolution:
+                    tempVec2.set(viewWidth, viewHeight);
+                    u.setValue(VarType.Vector2, tempVec2);
+                    break;
+                case Aspect:
+                    float aspect = ((float) viewWidth) / viewHeight;
+                    u.setValue(VarType.Float, aspect);
+                    break;
+                case FrustumNearFar:
+                    tempVec2.set(near, far);
+                    u.setValue(VarType.Vector2, tempVec2);
+                    break;
+                case CameraPosition:
+                    u.setValue(VarType.Vector3, camLoc);
+                    break;
+                case CameraDirection:
+                    u.setValue(VarType.Vector3, camDir);
+                    break;
+                case CameraLeft:
+                    u.setValue(VarType.Vector3, camLeft);
+                    break;
+                case CameraUp:
+                    u.setValue(VarType.Vector3, camUp);
+                    break;
+                case Time:
+                    u.setValue(VarType.Float, timer.getTimeInSeconds());
+                    break;
+                case Tpf:
+                    u.setValue(VarType.Float, timer.getTimePerFrame());
+                    break;
+                case FrameRate:
+                    u.setValue(VarType.Float, timer.getFrameRate());
+                    break;
+            }
+        }
+
+        vars.release();
+    }
+
+    /**
+     * Set the material to use to render all future objects.
+     * This overrides the material set on the geometry and renders
+     * with the provided material instead.
+     * Use null to clear the material and return renderer to normal
+     * functionality.
+     * @param mat The forced material to set, or null to return to normal
+     */
+    public void setForcedMaterial(Material mat) {
+        forcedMaterial = mat;
+    }
+
+    /**
+     * Returns the forced render state previously set with 
+     * {@link #setForcedRenderState(com.jme3.material.RenderState) }.
+     * @return the forced render state
+     */
+    public RenderState getForcedRenderState() {
+        return forcedRenderState;
+    }
+
+    /**
+     * Set the render state to use for all future objects.
+     * This overrides the render state set on the material and instead
+     * forces this render state to be applied for all future materials
+     * rendered. Set to null to return to normal functionality.
+     * 
+     * @param forcedRenderState The forced render state to set, or null
+     * to return to normal
+     */
+    public void setForcedRenderState(RenderState forcedRenderState) {
+        this.forcedRenderState = forcedRenderState;
+    }
+
+    /**
+     * Set the timer that should be used to query the time based
+     * {@link UniformBinding}s for material world parameters.
+     * 
+     * @param timer The timer to query time world parameters
+     */
+    public void setTimer(Timer timer) {
+        this.timer = timer;
+    }
+
+    /**
+     * Returns the forced technique name set.
+     * 
+     * @return the forced technique name set.
+     * 
+     * @see #setForcedTechnique(java.lang.String) 
+     */
+    public String getForcedTechnique() {
+        return forcedTechnique;
+    }
+
+    /**
+     * Sets the forced technique to use when rendering geometries.
+     * <p>
+     * If the specified technique name is available on the geometry's
+     * material, then it is used, otherwise, the 
+     * {@link #setForcedMaterial(com.jme3.material.Material) forced material} is used.
+     * If a forced material is not set and the forced technique name cannot
+     * be found on the material, the geometry will <em>not</em> be rendered.
+     * 
+     * @param forcedTechnique The forced technique name to use, set to null
+     * to return to normal functionality.
+     * 
+     * @see #renderGeometry(com.jme3.scene.Geometry) 
+     */
+    public void setForcedTechnique(String forcedTechnique) {
+        this.forcedTechnique = forcedTechnique;
+    }
+
+    /**
+     * Enable or disable alpha-to-coverage. 
+     * <p>
+     * When alpha to coverage is enabled and the renderer implementation
+     * supports it, then alpha blending will be replaced with alpha dissolve
+     * if multi-sampling is also set on the renderer.
+     * This feature allows avoiding of alpha blending artifacts due to
+     * lack of triangle-level back-to-front sorting.
+     * 
+     * @param value True to enable alpha-to-coverage, false otherwise.
+     */
+    public void setAlphaToCoverage(boolean value) {
+        renderer.setAlphaToCoverage(value);
+    }
+
+    /**
+     * True if the translucent bucket should automatically be rendered
+     * by the RenderManager.
+     * 
+     * @return Whether or not the translucent bucket is rendered.
+     * 
+     * @see #setHandleTranslucentBucket(boolean) 
+     */
+    public boolean isHandleTranslucentBucket() {
+        return handleTranlucentBucket;
+    }
+
+    /**
+     * Enable or disable rendering of the 
+     * {@link Bucket#Translucent translucent bucket}
+     * by the RenderManager. The default is enabled.
+     * 
+     * @param handleTranslucentBucket Whether or not the translucent bucket should
+     * be rendered.
+     */
+    public void setHandleTranslucentBucket(boolean handleTranslucentBucket) {
+        this.handleTranlucentBucket = handleTranslucentBucket;
+    }
+
+    /**
+     * Internal use only. Sets the world matrix to use for future
+     * rendering. This has no effect unless objects are rendered manually
+     * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }.
+     * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will 
+     * override this value.
+     * 
+     * @param mat The world matrix to set
+     */
+    public void setWorldMatrix(Matrix4f mat) {
+        if (shader) {
+            worldMatrix.set(mat);
+        } else {
+            renderer.setWorldMatrix(mat);
+        }
+    }
+
+    /**
+     * Renders the given geometry.
+     * <p>
+     * First the proper world matrix is set, if 
+     * the geometry's {@link Geometry#setIgnoreTransform(boolean) ignore transform}
+     * feature is enabled, the identity world matrix is used, otherwise, the 
+     * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used. 
+     * <p>
+     * Once the world matrix is applied, the proper material is chosen for rendering.
+     * If a {@link #setForcedMaterial(com.jme3.material.Material) forced material} is
+     * set on this RenderManager, then it is used for rendering the geometry,
+     * otherwise, the {@link Geometry#getMaterial() geometry's material} is used.
+     * <p>
+     * If a {@link #setForcedTechnique(java.lang.String) forced technique} is
+     * set on this RenderManager, then it is selected automatically
+     * on the geometry's material and is used for rendering. Otherwise, one
+     * of the {@link MaterialDef#getDefaultTechniques() default techniques} is
+     * used.
+     * <p>
+     * If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced
+     * render state} is set on this RenderManager, then it is used
+     * for rendering the material, and the material's own render state is ignored.
+     * Otherwise, the material's render state is used as intended.
+     * 
+     * @param g The geometry to render
+     * 
+     * @see Technique
+     * @see RenderState
+     * @see Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) 
+     * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) 
+     */
+    public void renderGeometry(Geometry g) {
+        if (g.isIgnoreTransform()) {
+            setWorldMatrix(Matrix4f.IDENTITY);
+        } else {
+            setWorldMatrix(g.getWorldMatrix());
+        }
+
+        //if forcedTechnique we try to force it for render,
+        //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null
+        //else the geom is not rendered
+        if (forcedTechnique != null) {
+            if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) {
+                tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default";
+                g.getMaterial().selectTechnique(forcedTechnique, this);
+                // use geometry's material
+                g.getMaterial().render(g, this);
+                g.getMaterial().selectTechnique(tmpTech, this);
+                //Reverted this part from revision 6197
+                //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered
+            } else if (forcedMaterial != null) {
+                // use forced material
+                forcedMaterial.render(g, this);
+            }
+        } else if (forcedMaterial != null) {
+            // use forced material
+            forcedMaterial.render(g, this);
+        } else {
+            g.getMaterial().render(g, this);
+        }
+    }
+
+    /**
+     * Renders the given GeometryList.
+     * <p>
+     * For every geometry in the list, the 
+     * {@link #renderGeometry(com.jme3.scene.Geometry) } method is called.
+     * 
+     * @param gl The geometry list to render.
+     * 
+     * @see GeometryList
+     * @see #renderGeometry(com.jme3.scene.Geometry) 
+     */
+    public void renderGeometryList(GeometryList gl) {
+        for (int i = 0; i < gl.size(); i++) {
+            renderGeometry(gl.get(i));
+        }
+    }
+
+    /**
+     * If a spatial is not inside the eye frustum, it
+     * is still rendered in the shadow frustum (shadow casting queue)
+     * through this recursive method.
+     */
+    private void renderShadow(Spatial s, RenderQueue rq) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            List<Spatial> children = n.getChildren();
+            for (int i = 0; i < children.size(); i++) {
+                renderShadow(children.get(i), rq);
+            }
+        } else if (s instanceof Geometry) {
+            Geometry gm = (Geometry) s;
+
+            RenderQueue.ShadowMode shadowMode = s.getShadowMode();
+            if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) {
+                //forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue
+                rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast);
+            }
+        }
+    }
+
+    /**
+     * Preloads a scene for rendering.
+     * <p>
+     * After invocation of this method, the underlying
+     * renderer would have uploaded any textures, shaders and meshes
+     * used by the given scene to the video driver. 
+     * Using this method is useful when wishing to avoid the initial pause
+     * when rendering a scene for the first time. Note that it is not 
+     * guaranteed that the underlying renderer will actually choose to upload
+     * the data to the GPU so some pause is still to be expected.
+     * 
+     * @param scene The scene to preload
+     */
+    public void preloadScene(Spatial scene) {
+        if (scene instanceof Node) {
+            // recurse for all children
+            Node n = (Node) scene;
+            List<Spatial> children = n.getChildren();
+            for (int i = 0; i < children.size(); i++) {
+                preloadScene(children.get(i));
+            }
+        } else if (scene instanceof Geometry) {
+            // add to the render queue
+            Geometry gm = (Geometry) scene;
+            if (gm.getMaterial() == null) {
+                throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
+            }
+
+            gm.getMaterial().preload(this);
+            Mesh mesh = gm.getMesh();
+            if (mesh != null) {
+                for (Entry<VertexBuffer> entry : mesh.getBuffers()) {
+                    VertexBuffer buf = entry.getValue();
+                    if (buf.getData() != null) {
+                        renderer.updateBufferData(buf);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Flattens the given scene graph into the ViewPort's RenderQueue,
+     * checking for culling as the call goes down the graph recursively.
+     * <p>
+     * First, the scene is checked for culling based on the <code>Spatial</code>s
+     * {@link Spatial#setCullHint(com.jme3.scene.Spatial.CullHint) cull hint},
+     * if the camera frustum contains the scene, then this method is recursively
+     * called on its children.
+     * <p>
+     * When the scene's leaves or {@link Geometry geometries} are reached,
+     * they are each enqueued into the 
+     * {@link ViewPort#getQueue() ViewPort's render queue}.
+     * <p>
+     * In addition to enqueuing the visible geometries, this method
+     * also scenes which cast or receive shadows, by putting them into the
+     * RenderQueue's 
+     * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) 
+     * shadow queue}. Each Spatial which has its 
+     * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
+     * set to not off, will be put into the appropriate shadow queue, note that
+     * this process does not check for frustum culling on any 
+     * {@link ShadowMode#Cast shadow casters}, as they don't have to be
+     * in the eye camera frustum to cast shadows on objects that are inside it.
+     * 
+     * @param scene The scene to flatten into the queue
+     * @param vp The ViewPort provides the {@link ViewPort#getCamera() camera}
+     * used for culling and the {@link ViewPort#getQueue() queue} used to 
+     * contain the flattened scene graph.
+     */
+    public void renderScene(Spatial scene, ViewPort vp) {
+        if (scene.getParent() == null) {
+            vp.getCamera().setPlaneState(0);
+        }
+        // check culling first.
+        if (!scene.checkCulling(vp.getCamera())) {
+            // move on to shadow-only render
+            if ((scene.getShadowMode() != RenderQueue.ShadowMode.Off || scene instanceof Node) && scene.getCullHint()!=Spatial.CullHint.Always) {
+                renderShadow(scene, vp.getQueue());
+            }
+            return;
+        }
+
+        scene.runControlRender(this, vp);
+        if (scene instanceof Node) {
+            // recurse for all children
+            Node n = (Node) scene;
+            List<Spatial> children = n.getChildren();
+            //saving cam state for culling
+            int camState = vp.getCamera().getPlaneState();
+            for (int i = 0; i < children.size(); i++) {
+                //restoring cam state before proceeding children recusively
+                vp.getCamera().setPlaneState(camState);
+                renderScene(children.get(i), vp);
+
+            }
+        } else if (scene instanceof Geometry) {
+
+            // add to the render queue
+            Geometry gm = (Geometry) scene;
+            if (gm.getMaterial() == null) {
+                throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
+            }
+
+            vp.getQueue().addToQueue(gm, scene.getQueueBucket());
+
+            // add to shadow queue if needed
+            RenderQueue.ShadowMode shadowMode = scene.getShadowMode();
+            if (shadowMode != RenderQueue.ShadowMode.Off) {
+                vp.getQueue().addToShadowQueue(gm, shadowMode);
+            }
+        }
+    }
+
+    /**
+     * Returns the camera currently used for rendering.
+     * <p>
+     * The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }.
+     * 
+     * @return the camera currently used for rendering.
+     */
+    public Camera getCurrentCamera() {
+        return prevCam;
+    }
+
+    /**
+     * The renderer implementation used for rendering operations.
+     * 
+     * @return The renderer implementation
+     * 
+     * @see #RenderManager(com.jme3.renderer.Renderer) 
+     * @see Renderer
+     */
+    public Renderer getRenderer() {
+        return renderer;
+    }
+
+    /**
+     * Flushes the ViewPort's {@link ViewPort#getQueue() render queue}
+     * by rendering each of its visible buckets.
+     * By default the queues will automatically be cleared after rendering,
+     * so there's no need to clear them manually.
+     * 
+     * @param vp The ViewPort of which the queue will be flushed
+     * 
+     * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) 
+     * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList) 
+     */
+    public void flushQueue(ViewPort vp) {
+        renderViewPortQueues(vp, true);
+    }
+
+    /**
+     * Clears the queue of the given ViewPort.
+     * Simply calls {@link RenderQueue#clear() } on the ViewPort's 
+     * {@link ViewPort#getQueue() render queue}.
+     * 
+     * @param vp The ViewPort of which the queue will be cleared.
+     * 
+     * @see RenderQueue#clear()
+     * @see ViewPort#getQueue()
+     */
+    public void clearQueue(ViewPort vp) {
+        vp.getQueue().clear();
+    }
+
+    /**
+     * Render the given viewport queues.
+     * <p>
+     * Changes the {@link Renderer#setDepthRange(float, float) depth range}
+     * appropriately as expected by each queue and then calls 
+     * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) }
+     * on the queue. Makes sure to restore the depth range to [0, 1] 
+     * at the end of the call.
+     * Note that the {@link Bucket#Translucent translucent bucket} is NOT
+     * rendered by this method. Instead the user should call 
+     * {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) }
+     * after this call.
+     * 
+     * @param vp the viewport of which queue should be rendered
+     * @param flush If true, the queues will be cleared after
+     * rendering.
+     * 
+     * @see RenderQueue
+     * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort) 
+     */
+    public void renderViewPortQueues(ViewPort vp, boolean flush) {
+        RenderQueue rq = vp.getQueue();
+        Camera cam = vp.getCamera();
+        boolean depthRangeChanged = false;
+
+        // render opaque objects with default depth range
+        // opaque objects are sorted front-to-back, reducing overdraw
+        rq.renderQueue(Bucket.Opaque, this, cam, flush);
+
+        // render the sky, with depth range set to the farthest
+        if (!rq.isQueueEmpty(Bucket.Sky)) {
+            renderer.setDepthRange(1, 1);
+            rq.renderQueue(Bucket.Sky, this, cam, flush);
+            depthRangeChanged = true;
+        }
+
+
+        // transparent objects are last because they require blending with the
+        // rest of the scene's objects. Consequently, they are sorted
+        // back-to-front.
+        if (!rq.isQueueEmpty(Bucket.Transparent)) {
+            if (depthRangeChanged) {
+                renderer.setDepthRange(0, 1);
+                depthRangeChanged = false;
+            }
+
+            rq.renderQueue(Bucket.Transparent, this, cam, flush);
+        }
+
+        if (!rq.isQueueEmpty(Bucket.Gui)) {
+            renderer.setDepthRange(0, 0);
+            setCamera(cam, true);
+            rq.renderQueue(Bucket.Gui, this, cam, flush);
+            setCamera(cam, false);
+            depthRangeChanged = true;
+        }
+
+        // restore range to default
+        if (depthRangeChanged) {
+            renderer.setDepthRange(0, 1);
+        }
+    }
+
+    /**
+     * Renders the {@link Bucket#Translucent translucent queue} on the viewPort.
+     * <p>
+     * This call does nothing unless {@link #setHandleTranslucentBucket(boolean) }
+     * is set to true. This method clears the translucent queue after rendering
+     * it.
+     * 
+     * @param vp The viewport of which the translucent queue should be rendered.
+     * 
+     * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) 
+     * @see #setHandleTranslucentBucket(boolean) 
+     */
+    public void renderTranslucentQueue(ViewPort vp) {
+        RenderQueue rq = vp.getQueue();
+        if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranlucentBucket) {
+            rq.renderQueue(Bucket.Translucent, this, vp.getCamera(), true);
+        }
+    }
+
+    private void setViewPort(Camera cam) {
+        // this will make sure to update viewport only if needed
+        if (cam != prevCam || cam.isViewportChanged()) {
+            viewX = (int) (cam.getViewPortLeft() * cam.getWidth());
+            viewY = (int) (cam.getViewPortBottom() * cam.getHeight());
+            viewWidth = (int) ((cam.getViewPortRight() - cam.getViewPortLeft()) * cam.getWidth());
+            viewHeight = (int) ((cam.getViewPortTop() - cam.getViewPortBottom()) * cam.getHeight());
+            renderer.setViewPort(viewX, viewY, viewWidth, viewHeight);
+            renderer.setClipRect(viewX, viewY, viewWidth, viewHeight);
+            cam.clearViewportChanged();
+            prevCam = cam;
+
+//            float translateX = viewWidth == viewX ? 0 : -(viewWidth + viewX) / (viewWidth - viewX);
+//            float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY);
+//            float scaleX = viewWidth == viewX ? 1f : 2f / (viewWidth - viewX);
+//            float scaleY = viewHeight == viewY ? 1f : 2f / (viewHeight - viewY);
+//            
+//            orthoMatrix.loadIdentity();
+//            orthoMatrix.setTranslation(translateX, translateY, 0);
+//            orthoMatrix.setScale(scaleX, scaleY, 0); 
+
+            orthoMatrix.loadIdentity();
+            orthoMatrix.setTranslation(-1f, -1f, 0f);
+            orthoMatrix.setScale(2f / cam.getWidth(), 2f / cam.getHeight(), 0f);
+        }
+    }
+
+    private void setViewProjection(Camera cam, boolean ortho) {
+        if (shader) {
+            if (ortho) {
+                viewMatrix.set(Matrix4f.IDENTITY);
+                projMatrix.set(orthoMatrix);
+                viewProjMatrix.set(orthoMatrix);
+            } else {
+                viewMatrix.set(cam.getViewMatrix());
+                projMatrix.set(cam.getProjectionMatrix());
+                viewProjMatrix.set(cam.getViewProjectionMatrix());
+            }
+
+            camLoc.set(cam.getLocation());
+            cam.getLeft(camLeft);
+            cam.getUp(camUp);
+            cam.getDirection(camDir);
+
+            near = cam.getFrustumNear();
+            far = cam.getFrustumFar();
+        } else {
+            if (ortho) {
+                renderer.setViewProjectionMatrices(Matrix4f.IDENTITY, orthoMatrix);
+            } else {
+                renderer.setViewProjectionMatrices(cam.getViewMatrix(),
+                        cam.getProjectionMatrix());
+            }
+
+        }
+    }
+
+    /**
+     * Set the camera to use for rendering.
+     * <p>
+     * First, the camera's 
+     * {@link Camera#setViewPort(float, float, float, float) view port parameters}
+     * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and 
+     * {@link Camera#getProjectionMatrix() projection} matrices are set
+     * on the renderer. If <code>ortho</code> is <code>true</code>, then
+     * instead of using the camera's view and projection matrices, an ortho
+     * matrix is computed and used instead of the view projection matrix. 
+     * The ortho matrix converts from the range (0 ~ Width, 0 ~ Height, -1 ~ +1)
+     * to the clip range (-1 ~ +1, -1 ~ +1, -1 ~ +1).
+     * 
+     * @param cam The camera to set
+     * @param ortho True if to use orthographic projection (for GUI rendering),
+     * false if to use the camera's view and projection matrices.
+     */
+    public void setCamera(Camera cam, boolean ortho) {
+        setViewPort(cam);
+        setViewProjection(cam, ortho);
+    }
+
+    /**
+     * Draws the viewport but without notifying {@link SceneProcessor scene
+     * processors} of any rendering events.
+     * 
+     * @param vp The ViewPort to render
+     * 
+     * @see #renderViewPort(com.jme3.renderer.ViewPort, float) 
+     */
+    public void renderViewPortRaw(ViewPort vp) {
+        setCamera(vp.getCamera(), false);
+        List<Spatial> scenes = vp.getScenes();
+        for (int i = scenes.size() - 1; i >= 0; i--) {
+            renderScene(scenes.get(i), vp);
+        }
+        flushQueue(vp);
+    }
+
+    /**
+     * Renders the {@link ViewPort}.
+     * <p>
+     * If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method
+     * returns immediately. Otherwise, the ViewPort is rendered by 
+     * the following process:<br>
+     * <ul>
+     * <li>All {@link SceneProcessor scene processors} that are attached
+     * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, com.jme3.renderer.ViewPort) initialized}.
+     * </li>
+     * <li>The SceneProcessors' {@link SceneProcessor#preFrame(float) } method 
+     * is called.</li>
+     * <li>The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer}
+     * is set on the Renderer</li>
+     * <li>The camera is set on the renderer, including its view port parameters.
+     * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })</li>
+     * <li>Any buffers that the ViewPort requests to be cleared are cleared
+     * and the {@link ViewPort#getBackgroundColor() background color} is set</li>
+     * <li>Every scene that is attached to the ViewPort is flattened into 
+     * the ViewPort's render queue 
+     * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) })
+     * </li>
+     * <li>The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) }
+     * method is called.</li>
+     * <li>The render queue is sorted and then flushed, sending
+     * rendering commands to the underlying Renderer implementation. 
+     * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })</li>
+     * <li>The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) }
+     * method is called.</li>
+     * <li>The translucent queue of the ViewPort is sorted and then flushed
+     * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })</li>
+     * <li>If any objects remained in the render queue, they are removed
+     * from the queue. This is generally objects added to the 
+     * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) 
+     * shadow queue}
+     * which were not rendered because of a missing shadow renderer.</li>
+     * </ul>
+     * 
+     * @param vp
+     * @param tpf 
+     */
+    public void renderViewPort(ViewPort vp, float tpf) {
+        if (!vp.isEnabled()) {
+            return;
+        }
+        List<SceneProcessor> processors = vp.getProcessors();
+        if (processors.isEmpty()) {
+            processors = null;
+        }
+
+        if (processors != null) {
+            for (SceneProcessor proc : processors) {
+                if (!proc.isInitialized()) {
+                    proc.initialize(this, vp);
+                }
+                proc.preFrame(tpf);
+            }
+        }
+
+        renderer.setFrameBuffer(vp.getOutputFrameBuffer());
+        setCamera(vp.getCamera(), false);
+        if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
+            if (vp.isClearColor()) {
+                renderer.setBackgroundColor(vp.getBackgroundColor());
+            }
+            renderer.clearBuffers(vp.isClearColor(),
+                    vp.isClearDepth(),
+                    vp.isClearStencil());
+        }
+
+        List<Spatial> scenes = vp.getScenes();
+        for (int i = scenes.size() - 1; i >= 0; i--) {
+            renderScene(scenes.get(i), vp);
+        }
+
+        if (processors != null) {
+            for (SceneProcessor proc : processors) {
+                proc.postQueue(vp.getQueue());
+            }
+        }
+
+        flushQueue(vp);
+
+        if (processors != null) {
+            for (SceneProcessor proc : processors) {
+                proc.postFrame(vp.getOutputFrameBuffer());
+            }
+        }
+        //renders the translucent objects queue after processors have been rendered
+        renderTranslucentQueue(vp);
+        // clear any remaining spatials that were not rendered.
+        clearQueue(vp);
+    }
+
+    /**
+     * Called by the application to render any ViewPorts
+     * added to this RenderManager.
+     * <p>
+     * Renders any viewports that were added using the following methods:
+     * <ul>
+     * <li>{@link #createPreView(java.lang.String, com.jme3.renderer.Camera) }</li>
+     * <li>{@link #createMainView(java.lang.String, com.jme3.renderer.Camera) }</li>
+     * <li>{@link #createPostView(java.lang.String, com.jme3.renderer.Camera) }</li>
+     * </ul>
+     * 
+     * @param tpf Time per frame value
+     */
+    public void render(float tpf, boolean mainFrameBufferActive) {
+        if (renderer instanceof NullRenderer) {
+            return;
+        }
+
+        this.shader = renderer.getCaps().contains(Caps.GLSL100);
+
+        for (int i = 0; i < preViewPorts.size(); i++) {
+            ViewPort vp = preViewPorts.get(i);
+            if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+                renderViewPort(vp, tpf);
+            }
+        }
+        for (int i = 0; i < viewPorts.size(); i++) {
+            ViewPort vp = viewPorts.get(i);
+            if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+                renderViewPort(vp, tpf);
+            }
+        }
+        for (int i = 0; i < postViewPorts.size(); i++) {
+            ViewPort vp = postViewPorts.get(i);
+            if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+                renderViewPort(vp, tpf);
+            }
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/Renderer.java b/engine/src/core/com/jme3/renderer/Renderer.java
new file mode 100644
index 0000000..6e366ab
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Renderer.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.light.LightList;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+/**
+ * The <code>Renderer</code> is responsible for taking rendering commands and
+ * executing them on the underlying video hardware.
+ * 
+ * @author Kirill Vainer
+ */
+public interface Renderer {
+
+    /**
+     * Get the capabilities of the renderer.
+     * @return The capabilities of the renderer.
+     */
+    public EnumSet<Caps> getCaps();
+
+    /**
+     * The statistics allow tracking of how data
+     * per frame, such as number of objects rendered, number of triangles, etc.
+     * These are updated when the Renderer's methods are used, make sure
+     * to call {@link Statistics#clearFrame() } at the appropriate time
+     * to get accurate info per frame.
+     */
+    public Statistics getStatistics();
+
+    /**
+     * Invalidates the current rendering state. Should be called after
+     * the GL state was changed manually or through an external library.
+     */
+    public void invalidateState();
+
+    /**
+     * Clears certain channels of the currently bound framebuffer.
+     *
+     * @param color True if to clear colors (RGBA)
+     * @param depth True if to clear depth/z
+     * @param stencil True if to clear stencil buffer (if available, otherwise
+     * ignored)
+     */
+    public void clearBuffers(boolean color, boolean depth, boolean stencil);
+
+    /**
+     * Sets the background (aka clear) color.
+     * 
+     * @param color The background color to set
+     */
+    public void setBackgroundColor(ColorRGBA color);
+
+    /**
+     * Applies the given {@link RenderState}, making the necessary
+     * GL calls so that the state is applied.
+     */
+    public void applyRenderState(RenderState state);
+
+    /**
+     * Set the range of the depth values for objects. All rendered
+     * objects will have their depth clamped to this range.
+     * 
+     * @param start The range start
+     * @param end The range end
+     */
+    public void setDepthRange(float start, float end);
+
+    /**
+     * Called when a new frame has been rendered.
+     */
+    public void onFrame();
+
+    /**
+     * Set the world matrix to use. Does nothing if the Renderer is 
+     * shader based.
+     * 
+     * @param worldMatrix World matrix to use.
+     */
+    public void setWorldMatrix(Matrix4f worldMatrix);
+
+    /**
+     * Sets the view and projection matrices to use. Does nothing if the Renderer 
+     * is shader based.
+     * 
+     * @param viewMatrix The view matrix to use.
+     * @param projMatrix The projection matrix to use.
+     */
+    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix);
+
+    /**
+     * Set the viewport location and resolution on the screen.
+     * 
+     * @param x The x coordinate of the viewport
+     * @param y The y coordinate of the viewport
+     * @param width Width of the viewport
+     * @param height Height of the viewport
+     */
+    public void setViewPort(int x, int y, int width, int height);
+
+    /**
+     * Specifies a clipping rectangle.
+     * For all future rendering commands, no pixels will be allowed
+     * to be rendered outside of the clip rectangle.
+     * 
+     * @param x The x coordinate of the clip rect
+     * @param y The y coordinate of the clip rect
+     * @param width Width of the clip rect
+     * @param height Height of the clip rect
+     */
+    public void setClipRect(int x, int y, int width, int height);
+
+    /**
+     * Clears the clipping rectangle set with 
+     * {@link #setClipRect(int, int, int, int) }.
+     */
+    public void clearClipRect();
+
+    /**
+     * Set lighting state.
+     * Does nothing if the renderer is shader based.
+     * The lights should be provided in world space. 
+     * Specify <code>null</code> to disable lighting.
+     * 
+     * @param lights The light list to set.
+     */
+    public void setLighting(LightList lights);
+
+    /**
+     * Sets the shader to use for rendering.
+     * If the shader has not been uploaded yet, it is compiled
+     * and linked. If it has been uploaded, then the 
+     * uniform data is updated and the shader is set.
+     * 
+     * @param shader The shader to use for rendering.
+     */
+    public void setShader(Shader shader);
+
+    /**
+     * Deletes a shader. This method also deletes
+     * the attached shader sources.
+     * 
+     * @param shader Shader to delete.
+     */
+    public void deleteShader(Shader shader);
+
+    /**
+     * Deletes the provided shader source.
+     * 
+     * @param source The ShaderSource to delete.
+     */
+    public void deleteShaderSource(ShaderSource source);
+
+    /**
+     * Copies contents from src to dst, scaling if necessary.
+     */
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst);
+
+    /**
+     * Copies contents from src to dst, scaling if necessary.
+     * set copyDepth to false to only copy the color buffers.
+     */
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth);
+
+    /**
+     * Sets the framebuffer that will be drawn to.
+     */
+    public void setFrameBuffer(FrameBuffer fb);
+    
+    /**
+     * Set the framebuffer that will be set instead of the main framebuffer
+     * when a call to setFrameBuffer(null) is made.
+     * 
+     * @param fb 
+     */
+    public void setMainFrameBufferOverride(FrameBuffer fb);
+
+    /**
+     * Reads the pixels currently stored in the specified framebuffer
+     * into the given ByteBuffer object. 
+     * Only color pixels are transferred, the format is BGRA with 8 bits 
+     * per component. The given byte buffer should have at least
+     * fb.getWidth() * fb.getHeight() * 4 bytes remaining.
+     * 
+     * @param fb The framebuffer to read from
+     * @param byteBuf The bytebuffer to transfer color data to
+     */
+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf);
+
+    /**
+     * Deletes a framebuffer and all attached renderbuffers
+     */
+    public void deleteFrameBuffer(FrameBuffer fb);
+
+    /**
+     * Sets the texture to use for the given texture unit.
+     */
+    public void setTexture(int unit, Texture tex);
+
+    /**
+     * Deletes a texture from the GPU.
+     */
+    public void deleteImage(Image image);
+
+    /**
+     * Uploads a vertex buffer to the GPU.
+     * 
+     * @param vb The vertex buffer to upload
+     */
+    public void updateBufferData(VertexBuffer vb);
+
+    /**
+     * Deletes a vertex buffer from the GPU.
+     * @param vb The vertex buffer to delete
+     */
+    public void deleteBuffer(VertexBuffer vb);
+
+    /**
+     * Renders <code>count</code> meshes, with the geometry data supplied.
+     * The shader which is currently set with <code>setShader</code> is
+     * responsible for transforming the input verticies into clip space
+     * and shading it based on the given vertex attributes.
+     * The int variable gl_InstanceID can be used to access the current
+     * instance of the mesh being rendered inside the vertex shader.
+     *
+     * @param mesh The mesh to render
+     * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
+     * @param count Number of mesh instances to render
+     */
+    public void renderMesh(Mesh mesh, int lod, int count);
+
+    /**
+     * Resets all previously used {@link GLObject}s on this Renderer.
+     * The state of the GLObjects is reset in such way, that using
+     * them again will cause the renderer to reupload them.
+     * Call this method when you know the GL context is going to shutdown.
+     * 
+     * @see GLObject#resetObject() 
+     */
+    public void resetGLObjects();
+
+    /**
+     * Deletes all previously used {@link GLObject}s on this Renderer, and
+     * then resets the GLObjects.
+     * 
+     * @see #resetGLObjects() 
+     * @see GLObject#deleteObject(com.jme3.renderer.Renderer) 
+     */
+    public void cleanup();
+
+    /**
+     * Sets the alpha to coverage state.
+     * <p>
+     * When alpha coverage and multi-sampling is enabled, 
+     * each pixel will contain alpha coverage in all
+     * of its subsamples, which is then combined when
+     * other future alpha-blended objects are rendered.
+     * </p>
+     * <p>
+     * Alpha-to-coverage is useful for rendering transparent objects
+     * without having to worry about sorting them.
+     * </p>
+     */
+    public void setAlphaToCoverage(boolean value);
+}
diff --git a/engine/src/core/com/jme3/renderer/RendererException.java b/engine/src/core/com/jme3/renderer/RendererException.java
new file mode 100644
index 0000000..41964a2
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RendererException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+/**
+ * <code>RendererException</code> is raised when a renderer encounters
+ * a fatal rendering error.
+ * 
+ * @author Kirill Vainer
+ */
+public class RendererException extends RuntimeException {
+    
+    /**
+     * Creates a new instance of <code>RendererException</code>
+     */
+    public RendererException(String message){
+        super(message);
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/Statistics.java b/engine/src/core/com/jme3/renderer/Statistics.java
new file mode 100644
index 0000000..fb8f2a0
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Statistics.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.scene.Mesh;
+import com.jme3.shader.Shader;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import java.util.HashSet;
+
+/**
+ * The statistics class allows tracking of real-time rendering statistics.
+ * <p>
+ * The <code>Statistics</code> can be retrieved by using {@link Renderer#getStatistics() }.
+ * 
+ * @author Kirill Vainer
+ */
+public class Statistics {
+
+    protected int numObjects;
+    protected int numTriangles;
+    protected int numVertices;
+    protected int numShaderSwitches;
+    protected int numTextureBinds;
+    protected int numFboSwitches;
+    protected int numUniformsSet;
+
+    protected int memoryShaders;
+    protected int memoryFrameBuffers;
+    protected int memoryTextures;
+
+    protected HashSet<Integer> shadersUsed = new HashSet<Integer>();
+    protected HashSet<Integer> texturesUsed = new HashSet<Integer>();
+    protected HashSet<Integer> fbosUsed = new HashSet<Integer>();
+
+    /**
+     * Returns a list of labels corresponding to each statistic.
+     * 
+     * @return a list of labels corresponding to each statistic.
+     * 
+     * @see #getData(int[]) 
+     */
+    public String[] getLabels(){
+        return new String[]{ "Vertices",
+                             "Triangles",
+                             "Uniforms",
+
+                             "Objects",
+
+                             "Shaders (S)",
+                             "Shaders (F)",
+                             "Shaders (M)",
+
+                             "Textures (S)",
+                             "Textures (F)",
+                             "Textures (M)",
+
+                             "FrameBuffers (S)",
+                             "FrameBuffers (F)",
+                             "FrameBuffers (M)" };
+
+    }
+
+    /**
+     * Retrieves the statistics data into the given array.
+     * The array should be as large as the array given in 
+     * {@link #getLabels() }.
+     * 
+     * @param data The data array to write to
+     */
+    public void getData(int[] data){
+        data[0] = numVertices;
+        data[1] = numTriangles;
+        data[2] = numUniformsSet;
+        data[3] = numObjects;
+
+        data[4] = numShaderSwitches;
+        data[5] = shadersUsed.size();
+        data[6] = memoryShaders;
+
+        data[7] = numTextureBinds;
+        data[8] = texturesUsed.size();
+        data[9] = memoryTextures;
+        
+        data[10] = numFboSwitches;
+        data[11] = fbosUsed.size();
+        data[12] = memoryFrameBuffers;
+    }
+
+    /**
+     * Called by the Renderer when a mesh has been drawn.
+     * 
+     */
+    public void onMeshDrawn(Mesh mesh, int lod){
+        numObjects ++;
+        numTriangles += mesh.getTriangleCount(lod);
+        numVertices += mesh.getVertexCount();
+    }
+
+    /**
+     * Called by the Renderer when a shader has been utilized.
+     * 
+     * @param shader The shader that was used
+     * @param wasSwitched If true, the shader has required a state switch
+     */
+    public void onShaderUse(Shader shader, boolean wasSwitched){
+        assert shader.getId() >= 1;
+
+        if (!shadersUsed.contains(shader.getId()))
+            shadersUsed.add(shader.getId());
+
+        if (wasSwitched)
+            numShaderSwitches++;
+    }
+
+    /**
+     * Called by the Renderer when a uniform was set.
+     */
+    public void onUniformSet(){
+        numUniformsSet ++;
+    }
+
+    /**
+     * Called by the Renderer when a texture has been set.
+     * 
+     * @param image The image that was set
+     * @param wasSwitched If true, the texture has required a state switch
+     */
+    public void onTextureUse(Image image, boolean wasSwitched){
+        assert image.getId() >= 1;
+
+        if (!texturesUsed.contains(image.getId()))
+            texturesUsed.add(image.getId());
+
+        if (wasSwitched)
+            numTextureBinds ++;
+    }
+
+    /**
+     * Called by the Renderer when a framebuffer has been set.
+     * 
+     * @param fb The framebuffer that was set
+     * @param wasSwitched If true, the framebuffer required a state switch
+     */
+    public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){
+        if (fb != null){
+            assert fb.getId() >= 1;
+
+            if (!fbosUsed.contains(fb.getId()))
+                fbosUsed.add(fb.getId());
+        }
+
+        if (wasSwitched)
+            numFboSwitches ++;
+    }
+    
+    /**
+     * Clears all frame-specific statistics such as objects used per frame.
+     */
+    public void clearFrame(){
+        shadersUsed.clear();
+        texturesUsed.clear();
+        fbosUsed.clear();
+
+        numObjects = 0;
+        numTriangles = 0;
+        numVertices = 0;
+        numShaderSwitches = 0;
+        numTextureBinds = 0;
+        numFboSwitches = 0;
+        numUniformsSet = 0;
+    }
+
+    /**
+     * Called by the Renderer when it creates a new shader
+     */
+    public void onNewShader(){
+        memoryShaders ++;
+    }
+
+    /**
+     * Called by the Renderer when it creates a new texture
+     */
+    public void onNewTexture(){
+        memoryTextures ++;
+    }
+
+    /**
+     * Called by the Renderer when it creates a new framebuffer
+     */
+    public void onNewFrameBuffer(){
+        memoryFrameBuffers ++;
+    }
+
+    /**
+     * Called by the Renderer when it deletes a shader
+     */
+    public void onDeleteShader(){
+        memoryShaders --;
+    }
+
+    /**
+     * Called by the Renderer when it deletes a texture
+     */
+    public void onDeleteTexture(){
+        memoryTextures --;
+    }
+
+    /**
+     * Called by the Renderer when it deletes a framebuffer
+     */
+    public void onDeleteFrameBuffer(){
+        memoryFrameBuffers --;
+    }
+
+    /**
+     * Called when video memory is cleared.
+     */
+    public void clearMemory(){
+        memoryFrameBuffers = 0;
+        memoryShaders = 0;
+        memoryTextures = 0;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/ViewPort.java b/engine/src/core/com/jme3/renderer/ViewPort.java
new file mode 100644
index 0000000..ad9ab2f
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/ViewPort.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.FrameBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A <code>ViewPort</code> represents a view inside the display
+ * window or a {@link FrameBuffer} to which scenes will be rendered. 
+ * <p>
+ * A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera}
+ * which is used to render a set of {@link #attachScene(com.jme3.scene.Spatial) scenes}.
+ * A view port has a location on the screen as set by the 
+ * {@link Camera#setViewPort(float, float, float, float) } method.
+ * By default, a view port does not clear the framebuffer, but it can be
+ * set to {@link #setClearFlags(boolean, boolean, boolean) clear the framebuffer}.
+ * The background color which the color buffer is cleared to can be specified 
+ * via the {@link #setBackgroundColor(com.jme3.math.ColorRGBA)} method.
+ * <p>
+ * A ViewPort has a list of {@link SceneProcessor}s which can
+ * control how the ViewPort is rendered by the {@link RenderManager}.
+ * 
+ * @author Kirill Vainer
+ * 
+ * @see RenderManager
+ * @see SceneProcessor
+ * @see Spatial
+ * @see Camera
+ */
+public class ViewPort {
+
+    protected final String name;
+    protected final Camera cam;
+    protected final RenderQueue queue = new RenderQueue();
+    protected final ArrayList<Spatial> sceneList = new ArrayList<Spatial>();
+    protected final ArrayList<SceneProcessor> processors = new ArrayList<SceneProcessor>();
+    protected FrameBuffer out = null;
+
+    protected final ColorRGBA backColor = new ColorRGBA(0,0,0,0);
+    protected boolean clearDepth = false, clearColor = false, clearStencil = false;
+    private boolean enabled = true;
+
+    /**
+     * Create a new viewport. User code should generally use these methods instead:<br>
+     * <ul>
+     * <li>{@link RenderManager#createPreView(java.lang.String, com.jme3.renderer.Camera) }</li>
+     * <li>{@link RenderManager#createMainView(java.lang.String, com.jme3.renderer.Camera)  }</li>
+     * <li>{@link RenderManager#createPostView(java.lang.String, com.jme3.renderer.Camera)  }</li>
+     * </ul>
+     * 
+     * @param name The name of the viewport. Used for debugging only.
+     * @param cam The camera through which the viewport is rendered. The camera
+     * cannot be swapped to a different one after creating the viewport.
+     */
+    public ViewPort(String name, Camera cam) {
+        this.name = name;
+        this.cam = cam;
+    }
+
+    /**
+     * Returns the name of the viewport as set in the constructor.
+     * 
+     * @return the name of the viewport
+     * 
+     * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera) 
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the list of {@link SceneProcessor scene processors} that were
+     * added to this <code>ViewPort</code>
+     * 
+     * @return the list of processors attached to this ViewPort
+     * 
+     * @see #addProcessor(com.jme3.post.SceneProcessor) 
+     */
+    public List<SceneProcessor> getProcessors(){
+        return processors;
+    }
+
+    /**
+     * Adds a {@link SceneProcessor} to this ViewPort.
+     * <p>
+     * SceneProcessors that are added to the ViewPort will be notified
+     * of events as the ViewPort is being rendered by the {@link RenderManager}.
+     * 
+     * @param processor The processor to add
+     * 
+     * @see SceneProcessor
+     */
+    public void addProcessor(SceneProcessor processor){
+        processors.add(processor);
+    }
+
+    /**
+     * Removes a {@link SceneProcessor} from this ViewPort.
+     * <p>
+     * The processor will no longer receive events occurring to this ViewPort.
+     * 
+     * @param processor The processor to remove
+     * 
+     * @see SceneProcessor
+     */
+    public void removeProcessor(SceneProcessor processor){
+        processors.remove(processor);
+        processor.cleanup();
+    }
+
+    /**
+     * Check if depth buffer clearing is enabled.
+     * 
+     * @return true if depth buffer clearing is enabled.
+     * 
+     * @see #setClearDepth(boolean) 
+     */
+    public boolean isClearDepth() {
+        return clearDepth;
+    }
+
+    /**
+     * Enable or disable clearing of the depth buffer for this ViewPort.
+     * <p>
+     * By default depth clearing is disabled.
+     * 
+     * @param clearDepth Enable/disable depth buffer clearing.
+     */
+    public void setClearDepth(boolean clearDepth) {
+        this.clearDepth = clearDepth;
+    }
+
+    /**
+     * Check if color buffer clearing is enabled.
+     * 
+     * @return true if color buffer clearing is enabled.
+     * 
+     * @see #setClearColor(boolean) 
+     */
+    public boolean isClearColor() {
+        return clearColor;
+    }
+
+    /**
+     * Enable or disable clearing of the color buffer for this ViewPort.
+     * <p>
+     * By default color clearing is disabled.
+     * 
+     * @param clearColor Enable/disable color buffer clearing.
+     */
+    public void setClearColor(boolean clearColor) {
+        this.clearColor = clearColor;
+    }
+
+    /**
+     * Check if stencil buffer clearing is enabled.
+     * 
+     * @return true if stencil buffer clearing is enabled.
+     * 
+     * @see #setClearStencil(boolean) 
+     */
+    public boolean isClearStencil() {
+        return clearStencil;
+    }
+
+    /**
+     * Enable or disable clearing of the stencil buffer for this ViewPort.
+     * <p>
+     * By default stencil clearing is disabled.
+     * 
+     * @param clearStencil Enable/disable stencil buffer clearing.
+     */
+    public void setClearStencil(boolean clearStencil) {
+        this.clearStencil = clearStencil;
+    }
+
+    /**
+     * Set the clear flags (color, depth, stencil) in one call.
+     * 
+     * @param color If color buffer clearing should be enabled.
+     * @param depth If depth buffer clearing should be enabled.
+     * @param stencil If stencil buffer clearing should be enabled.
+     * 
+     * @see #setClearColor(boolean) 
+     * @see #setClearDepth(boolean) 
+     * @see #setClearStencil(boolean) 
+     */
+    public void setClearFlags(boolean color, boolean depth, boolean stencil){
+        this.clearColor = color;
+        this.clearDepth = depth;
+        this.clearStencil = stencil;
+    }
+
+    /**
+     * Returns the framebuffer where this ViewPort's scenes are
+     * rendered to.
+     * 
+     * @return the framebuffer where this ViewPort's scenes are
+     * rendered to.
+     * 
+     * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer) 
+     */
+    public FrameBuffer getOutputFrameBuffer() {
+        return out;
+    }
+
+    /**
+     * Sets the output framebuffer for the ViewPort.
+     * <p>
+     * The output framebuffer specifies where the scenes attached
+     * to this ViewPort are rendered to. By default this is <code>null</code>
+     * which indicates the scenes are rendered to the display window.
+     * 
+     * @param out The framebuffer to render scenes to, or null if to render
+     * to the screen.
+     */
+    public void setOutputFrameBuffer(FrameBuffer out) {
+        this.out = out;
+    }
+
+    /**
+     * Returns the camera which renders the attached scenes.
+     * 
+     * @return the camera which renders the attached scenes.
+     * 
+     * @see Camera
+     */
+    public Camera getCamera() {
+        return cam;
+    }
+
+    /**
+     * Internal use only.
+     */
+    public RenderQueue getQueue() {
+        return queue;
+    }
+
+    /**
+     * Attaches a new scene to render in this ViewPort.
+     * 
+     * @param scene The scene to attach
+     * 
+     * @see Spatial
+     */
+    public void attachScene(Spatial scene){
+        sceneList.add(scene);
+    }
+
+    /**
+     * Detaches a scene from rendering.
+     * 
+     * @param scene The scene to detach
+     * 
+     * @see #attachScene(com.jme3.scene.Spatial) 
+     */
+    public void detachScene(Spatial scene){
+        sceneList.remove(scene);
+    }
+
+    /**
+     * Removes all attached scenes.
+     * 
+     * @see #attachScene(com.jme3.scene.Spatial) 
+     */
+    public void clearScenes() {
+        sceneList.clear();
+    }
+
+    /**
+     * Returns a list of all attached scenes.
+     * 
+     * @return a list of all attached scenes.
+     * 
+     * @see #attachScene(com.jme3.scene.Spatial) 
+     */
+    public List<Spatial> getScenes(){
+        return sceneList;
+    }
+
+    /**
+     * Sets the background color.
+     * <p>
+     * When the ViewPort's color buffer is cleared 
+     * (if {@link #setClearColor(boolean) color clearing} is enabled), 
+     * this specifies the color to which the color buffer is set to.
+     * By default the background color is black without alpha.
+     * 
+     * @param background the background color.
+     */
+    public void setBackgroundColor(ColorRGBA background){
+        backColor.set(background);
+    }
+
+    /**
+     * Returns the background color of this ViewPort
+     * 
+     * @return the background color of this ViewPort
+     * 
+     * @see #setBackgroundColor(com.jme3.math.ColorRGBA) 
+     */
+    public ColorRGBA getBackgroundColor(){
+        return backColor;
+    }
+    
+    /**
+     * Enable or disable this ViewPort.
+     * <p>
+     * Disabled ViewPorts are skipped by the {@link RenderManager} when
+     * rendering. By default all ViewPorts are enabled.
+     * 
+     * @param enable If the viewport should be disabled or enabled.
+     */
+    public void setEnabled(boolean enable) {
+        this.enabled = enable;
+    }
+    
+    /**
+     * Returns true if the viewport is enabled, false otherwise.
+     * @return true if the viewport is enabled, false otherwise.
+     * @see #setEnabled(boolean) 
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/package.html b/engine/src/core/com/jme3/renderer/package.html
new file mode 100644
index 0000000..9d80ec8
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/package.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.renderer</code> package provides classes responsible for 
+rendering. 
+<p>
+The most critical classes are the {@link com.jme3.renderer.Renderer},
+which is the low-level rendering implementation and is abstract, and the
+{@link com.jme3.renderer.RenderManager} class, which provides the high-level
+rendering logic on top of the <code>Renderer</code>.
+<p>
+To accompany rendering, several helper classes are available. 
+<ul>
+    <li>The {@link com.jme3.renderer.Camera} is used to specify the point-of-view 
+        from which scenes are rendered.</li>
+    <li>The {@link com.jme3.renderer.ViewPort} is the 
+aggregation of a Camera and a set of {@link com.jme3.scene.Spatial scenes}
+which are to be rendered, as well as additional info.</li>
+    <li>The {@link com.jme3.renderer.Caps} class contains renderer capabilities
+which the user can query to find out what features are available in the 
+rendering implementation. </li>
+    <li>The {@link com.jme3.renderer.Statistics} class is updated in real time
+        by the Renderer, and is used to find out various statistics about
+        the rendering</li>
+    <li>The {@link com.jme3.renderer.GLObjectManager} and {@link com.jme3.renderer.GLObject} classes
+    provide a link between the renderer's native objects and Java's garbage collected objects,
+    allowing the engine to track when the Java object counterpart is garbage collected
+    and then delete the native object counterpart from the renderer.</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java
new file mode 100644
index 0000000..8f42df7
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import java.util.Comparator;
+
+/**
+ * <code>GeometryComparator</code> is a special version of {@link Comparator}
+ * that is used to sort geometries for rendering in the {@link RenderQueue}.
+ * 
+ * @author Kirill Vainer
+ */
+public interface GeometryComparator extends Comparator<Geometry> {
+    
+    /**
+     * Set the camera to use for sorting.
+     * 
+     * @param cam The camera to use for sorting
+     */
+    public void setCamera(Camera cam);
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryList.java b/engine/src/core/com/jme3/renderer/queue/GeometryList.java
new file mode 100644
index 0000000..9f36ed9
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GeometryList.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.util.SortUtil;
+
+/**
+ * This class is a special purpose list of {@link Geometry} objects for render
+ * queuing.
+ *
+ * @author Jack Lindamood
+ * @author Three Rings - better sorting alg.
+ * @author Kirill Vainer
+ */
+public class GeometryList {
+
+    private static final int DEFAULT_SIZE = 32;
+
+    private Geometry[] geometries;
+    private Geometry[] geometries2;
+    private int size;
+    private GeometryComparator comparator;
+
+    /**
+     * Initializes the GeometryList to use the given {@link GeometryComparator}
+     * to use for comparing geometries.
+     * 
+     * @param comparator The comparator to use.
+     */
+    public GeometryList(GeometryComparator comparator) {
+        size = 0;
+        geometries = new Geometry[DEFAULT_SIZE];
+        geometries2 = new Geometry[DEFAULT_SIZE];
+        this.comparator = comparator;
+    }
+
+    /**
+     * Set the camera that will be set on the geometry comparators 
+     * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}.
+     * 
+     * @param cam Camera to use for sorting.
+     */
+    public void setCamera(Camera cam){
+        this.comparator.setCamera(cam);
+    }
+
+    /**
+     * Returns the number of elements in this GeometryList.
+     * 
+     * @return Number of elements in the list
+     */
+    public int size(){
+        return size;
+    }
+
+    /**
+     * Returns the element at the given index.
+     * 
+     * @param index The index to lookup
+     * @return Geometry at the index
+     */
+    public Geometry get(int index){
+        return geometries[index];
+    }
+
+    /**
+     * Adds a geometry to the list. 
+     * List size is doubled if there is no room.
+     *
+     * @param g
+     *            The geometry to add.
+     */
+    public void add(Geometry g) {
+        if (size == geometries.length) {
+            Geometry[] temp = new Geometry[size * 2];
+            System.arraycopy(geometries, 0, temp, 0, size);
+            geometries = temp; // original list replaced by double-size list
+            
+            geometries2 = new Geometry[size * 2];
+        }
+        geometries[size++] = g;
+    }
+
+    /**
+     * Resets list size to 0.
+     */
+    public void clear() {
+        for (int i = 0; i < size; i++){
+            geometries[i] = null;
+        }
+
+        size = 0;
+    }
+
+    /**
+     * Sorts the elements in the list according to their Comparator.
+     */
+    public void sort() {
+        if (size > 1) {
+            // sort the spatial list using the comparator
+            
+//            SortUtil.qsort(geometries, 0, size, comparator);
+//            Arrays.sort(geometries, 0, size, comparator);            
+            
+            System.arraycopy(geometries, 0, geometries2, 0, size);
+            SortUtil.msort(geometries2, geometries, 0, size-1, comparator);
+            
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/renderer/queue/GuiComparator.java b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java
new file mode 100644
index 0000000..b35a03c
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+/**
+ * <code>GuiComparator</code> sorts geometries back-to-front based
+ * on their Z position.
+ *
+ * @author Kirill Vainer
+ */
+public class GuiComparator implements GeometryComparator {
+
+    public int compare(Geometry o1, Geometry o2) {
+        float z1 = o1.getWorldTranslation().getZ();
+        float z2 = o2.getWorldTranslation().getZ();
+        if (z1 > z2)
+            return 1;
+        else if (z1 < z2)
+            return -1;
+        else
+            return 0;
+    }
+
+    public void setCamera(Camera cam) {
+    }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/NullComparator.java b/engine/src/core/com/jme3/renderer/queue/NullComparator.java
new file mode 100644
index 0000000..6463558
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/NullComparator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+/**
+ * <code>NullComparator</code> does not sort geometries. They will be in
+ * arbitrary order.
+ * 
+ * @author Kirill Vainer
+ */
+public class NullComparator implements GeometryComparator {
+    public int compare(Geometry o1, Geometry o2) {
+        return 0;
+    }
+
+    public void setCamera(Camera cam) {
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java
new file mode 100644
index 0000000..85f4093
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java
@@ -0,0 +1,96 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.renderer.queue;

+

+import com.jme3.material.Material;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.scene.Geometry;

+

+public class OpaqueComparator implements GeometryComparator {

+

+    private Camera cam;

+    private final Vector3f tempVec  = new Vector3f();

+    private final Vector3f tempVec2 = new Vector3f();

+

+    public void setCamera(Camera cam){

+        this.cam = cam;

+    }

+

+    public float distanceToCam(Geometry spat){

+        if (spat == null)

+            return Float.NEGATIVE_INFINITY;

+ 

+        if (spat.queueDistance != Float.NEGATIVE_INFINITY)

+                return spat.queueDistance;

+ 

+        Vector3f camPosition = cam.getLocation();

+        Vector3f viewVector = cam.getDirection(tempVec2);

+        Vector3f spatPosition = null;

+ 

+        if (spat.getWorldBound() != null){

+            spatPosition = spat.getWorldBound().getCenter();

+        }else{

+            spatPosition = spat.getWorldTranslation();

+        }

+ 

+        spatPosition.subtract(camPosition, tempVec);

+        spat.queueDistance = tempVec.dot(viewVector);

+ 

+        return spat.queueDistance;

+    }

+

+    public int compare(Geometry o1, Geometry o2) {

+        Material m1 = o1.getMaterial();

+        Material m2 = o2.getMaterial();

+

+        int sortId = m1.compareTo(m2);

+

+        if (sortId == 0){

+            // use the same shader.

+            // sort front-to-back then.

+            float d1 = distanceToCam(o1);

+            float d2 = distanceToCam(o2);

+

+            if (d1 == d2)

+                return 0;

+            else if (d1 < d2)

+                return -1;

+            else

+                return 1;

+        }else{

+            return sortId;

+        }

+    }

+

+}

diff --git a/engine/src/core/com/jme3/renderer/queue/RenderQueue.java b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java
new file mode 100644
index 0000000..dba604e
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer.queue;
+
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+
+/**
+ * <code>RenderQueue</code> is used to queue up and sort 
+ * {@link Geometry geometries} for rendering.
+ * 
+ * @author Kirill Vainer
+ */
+public class RenderQueue {
+
+    private GeometryList opaqueList;
+    private GeometryList guiList;
+    private GeometryList transparentList;
+    private GeometryList translucentList;
+    private GeometryList skyList;
+    private GeometryList shadowRecv;
+    private GeometryList shadowCast;
+
+    /**
+     * Creates a new RenderQueue, the default {@link GeometryComparator comparators}
+     * are used for all {@link GeometryList geometry lists}.
+     */
+    public RenderQueue() {
+        this.opaqueList = new GeometryList(new OpaqueComparator());
+        this.guiList = new GeometryList(new GuiComparator());
+        this.transparentList = new GeometryList(new TransparentComparator());
+        this.translucentList = new GeometryList(new TransparentComparator());
+        this.skyList = new GeometryList(new NullComparator());
+        this.shadowRecv = new GeometryList(new OpaqueComparator());
+        this.shadowCast = new GeometryList(new OpaqueComparator());
+    }
+
+    /**
+     * The render queue <code>Bucket</code> specifies the bucket
+     * to which the spatial will be placed when rendered. 
+     * <p>
+     * The behavior of the rendering will differ depending on which 
+     * bucket the spatial is placed. A spatial's queue bucket can be set
+     * via {@link Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) }.
+     */
+    public enum Bucket {
+        /**
+         * The renderer will try to find the optimal order for rendering all 
+         * objects using this mode.
+         * You should use this mode for most normal objects, except transparent
+         * ones, as it could give a nice performance boost to your application.
+         */
+        Opaque,
+        
+        /**
+         * This is the mode you should use for object with
+         * transparency in them. It will ensure the objects furthest away are
+         * rendered first. That ensures when another transparent object is drawn on
+         * top of previously drawn objects, you can see those (and the object drawn
+         * using Opaque) through the transparent parts of the newly drawn
+         * object. 
+         */
+        Transparent,
+        
+        /**
+         * A special mode used for rendering really far away, flat objects - 
+         * e.g. skies. In this mode, the depth is set to infinity so 
+         * spatials in this bucket will appear behind everything, the downside
+         * to this bucket is that 3D objects will not be rendered correctly
+         * due to lack of depth testing.
+         */
+        Sky,
+        
+        /**
+         * A special mode used for rendering transparent objects that
+         * should not be effected by {@link SceneProcessor}. 
+         * Generally this would contain translucent objects, and
+         * also objects that do not write to the depth buffer such as
+         * particle emitters.
+         */
+        Translucent,
+        
+        /**
+         * This is a special mode, for drawing 2D object
+         * without perspective (such as GUI or HUD parts).
+         * The spatial's world coordinate system has the range
+         * of [0, 0, -1] to [Width, Height, 1] where Width/Height is
+         * the resolution of the screen rendered to. Any spatials
+         * outside of that range are culled.
+         */
+        Gui,
+        
+        /**
+         * A special mode, that will ensure that this spatial uses the same
+         * mode as the parent Node does.
+         */
+        Inherit,
+    }
+
+    /**
+     * <code>ShadowMode</code> is a marker used to specify how shadow
+     * effects should treat the spatial.
+     */
+    public enum ShadowMode {
+        /**
+         * Disable both shadow casting and shadow receiving for this spatial.
+         * Generally used for special effects like particle emitters.
+         */
+        Off,
+        
+        /**
+         * Enable casting of shadows but not receiving them. 
+         */
+        Cast,
+        
+        /**
+         * Enable receiving of shadows but not casting them.
+         */
+        Receive,
+        
+        /**
+         * Enable both receiving and casting of shadows.
+         */
+        CastAndReceive,
+        
+        /**
+         * Inherit the <code>ShadowMode</code> from the parent node.
+         */
+        Inherit
+    }
+
+    /**
+     *  Sets a different geometry comparator for the specified bucket, one
+     *  of Gui, Opaque, Sky, or Transparent.  The GeometryComparators are
+     *  used to sort the accumulated list of geometries before actual rendering
+     *  occurs.
+     *
+     *  <p>The most significant comparator is the one for the transparent
+     *  bucket since there is no correct way to sort the transparent bucket
+     *  that will handle all geometry all the time.  In certain cases, the
+     *  application may know the best way to sort and now has the option of
+     *  configuring a specific implementation.</p>
+     *
+     *  <p>The default comparators are:</p>
+     *  <ul>
+     *  <li>Bucket.Opaque: {@link com.jme3.renderer.queue.OpaqueComparator} which sorts
+     *                     by material first and front to back within the same material.
+     *  <li>Bucket.Transparent: {@link com.jme3.renderer.queue.TransparentComparator} which
+     *                     sorts purely back to front by leading bounding edge with no material sort.
+     *  <li>Bucket.Translucent: {@link com.jme3.renderer.queue.TransparentComparator} which
+     *                     sorts purely back to front by leading bounding edge with no material sort. this bucket is rendered after post processors.
+     *  <li>Bucket.Sky: {@link com.jme3.renderer.queue.NullComparator} which does no sorting
+     *                     at all.
+     *  <li>Bucket.Gui: {@link com.jme3.renderer.queue.GuiComparator} sorts geometries back to
+     *                     front based on their Z values.
+     */
+    public void setGeometryComparator(Bucket bucket, GeometryComparator c) {
+        switch (bucket) {
+            case Gui:
+                guiList = new GeometryList(c);
+                break;
+            case Opaque:
+                opaqueList = new GeometryList(c);
+                break;
+            case Sky:
+                skyList = new GeometryList(c);
+                break;
+            case Transparent:
+                transparentList = new GeometryList(c);
+                break;
+            case Translucent:
+                translucentList = new GeometryList(c);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown bucket type: " + bucket);
+        }
+    }
+
+    /**
+     * Adds a geometry to a shadow bucket.
+     * Note that this operation is done automatically by the
+     * {@link RenderManager}. {@link SceneProcessor}s that handle
+     * shadow rendering should fetch the queue by using
+     * {@link #getShadowQueueContent(com.jme3.renderer.queue.RenderQueue.ShadowMode) },
+     * by default no action is taken on the shadow queues.
+     * 
+     * @param g The geometry to add
+     * @param shadBucket The shadow bucket type, if it is
+     * {@link ShadowMode#CastAndReceive}, it is added to both the cast
+     * and the receive buckets.
+     */
+    public void addToShadowQueue(Geometry g, ShadowMode shadBucket) {
+        switch (shadBucket) {
+            case Inherit:
+                break;
+            case Off:
+                break;
+            case Cast:
+                shadowCast.add(g);
+                break;
+            case Receive:
+                shadowRecv.add(g);
+                break;
+            case CastAndReceive:
+                shadowCast.add(g);
+                shadowRecv.add(g);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unrecognized shadow bucket type: " + shadBucket);
+        }
+    }
+
+    /**
+     * Adds a geometry to the given bucket.
+     * The {@link RenderManager} automatically handles this task
+     * when flattening the scene graph. The bucket to add
+     * the geometry is determined by {@link Geometry#getQueueBucket() }.
+     * 
+     * @param g  The geometry to add
+     * @param bucket The bucket to add to, usually 
+     * {@link Geometry#getQueueBucket() }.
+     */
+    public void addToQueue(Geometry g, Bucket bucket) {
+        switch (bucket) {
+            case Gui:
+                guiList.add(g);
+                break;
+            case Opaque:
+                opaqueList.add(g);
+                break;
+            case Sky:
+                skyList.add(g);
+                break;
+            case Transparent:
+                transparentList.add(g);
+                break;
+            case Translucent:
+                translucentList.add(g);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown bucket type: " + bucket);
+        }
+    }
+
+    /**
+     * 
+     * @param shadBucket
+     * @return 
+     */
+    public GeometryList getShadowQueueContent(ShadowMode shadBucket) {
+        switch (shadBucket) {
+            case Cast:
+                return shadowCast;
+            case Receive:
+                return shadowRecv;
+            default:
+                throw new IllegalArgumentException("Only Cast or Receive are allowed");
+        }
+    }
+
+    private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) {
+        list.setCamera(cam); // select camera for sorting
+        list.sort();
+        for (int i = 0; i < list.size(); i++) {
+            Geometry obj = list.get(i);
+            assert obj != null;
+            rm.renderGeometry(obj);
+            obj.queueDistance = Float.NEGATIVE_INFINITY;
+        }
+        if (clear) {
+            list.clear();
+        }
+    }
+
+    public void renderShadowQueue(GeometryList list, RenderManager rm, Camera cam, boolean clear) {
+        renderGeometryList(list, rm, cam, clear);
+    }
+
+    public void renderShadowQueue(ShadowMode shadBucket, RenderManager rm, Camera cam, boolean clear) {
+        switch (shadBucket) {
+            case Cast:
+                renderGeometryList(shadowCast, rm, cam, clear);
+                break;
+            case Receive:
+                renderGeometryList(shadowRecv, rm, cam, clear);
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected shadow bucket: " + shadBucket);
+        }
+    }
+
+    public boolean isQueueEmpty(Bucket bucket) {
+        switch (bucket) {
+            case Gui:
+                return guiList.size() == 0;
+            case Opaque:
+                return opaqueList.size() == 0;
+            case Sky:
+                return skyList.size() == 0;
+            case Transparent:
+                return transparentList.size() == 0;
+            case Translucent:
+                return translucentList.size() == 0;
+            default:
+                throw new UnsupportedOperationException("Unsupported bucket type: " + bucket);
+        }
+    }
+
+    public void renderQueue(Bucket bucket, RenderManager rm, Camera cam) {
+        renderQueue(bucket, rm, cam, true);
+    }
+
+    public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean clear) {
+        switch (bucket) {
+            case Gui:
+                renderGeometryList(guiList, rm, cam, clear);
+                break;
+            case Opaque:
+                renderGeometryList(opaqueList, rm, cam, clear);
+                break;
+            case Sky:
+                renderGeometryList(skyList, rm, cam, clear);
+                break;
+            case Transparent:
+                renderGeometryList(transparentList, rm, cam, clear);
+                break;
+            case Translucent:
+                renderGeometryList(translucentList, rm, cam, clear);
+                break;
+
+            default:
+                throw new UnsupportedOperationException("Unsupported bucket type: " + bucket);
+        }
+    }
+
+    public void clear() {
+        opaqueList.clear();
+        guiList.clear();
+        transparentList.clear();
+        translucentList.clear();
+        skyList.clear();
+        shadowCast.clear();
+        shadowRecv.clear();
+    }
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java
new file mode 100644
index 0000000..0821eba
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+public class TransparentComparator implements GeometryComparator {
+
+    private Camera cam;
+    private final Vector3f tempVec = new Vector3f();
+
+    public void setCamera(Camera cam){
+        this.cam = cam;
+    }
+
+    /**
+     * Calculates the distance from a spatial to the camera. Distance is a
+     * squared distance.
+     *
+     * @param spat
+     *            Spatial to distancize.
+     * @return Distance from Spatial to camera.
+     */
+    private float distanceToCam2(Geometry spat){
+        if (spat == null)
+            return Float.NEGATIVE_INFINITY;
+
+        if (spat.queueDistance != Float.NEGATIVE_INFINITY)
+            return spat.queueDistance;
+
+        Vector3f camPosition = cam.getLocation();
+        Vector3f viewVector = cam.getDirection();
+        Vector3f spatPosition = null;
+
+        if (spat.getWorldBound() != null){
+            spatPosition = spat.getWorldBound().getCenter();
+        }else{
+            spatPosition = spat.getWorldTranslation();
+        }
+
+        spatPosition.subtract(camPosition, tempVec);
+        spat.queueDistance = tempVec.dot(tempVec);
+
+        float retval = Math.abs(tempVec.dot(viewVector)
+                / viewVector.dot(viewVector));
+        viewVector.mult(retval, tempVec);
+
+        spat.queueDistance = tempVec.length();
+
+        return spat.queueDistance;
+    }
+
+    private float distanceToCam(Geometry spat){
+        // NOTE: It is best to check the distance
+        // to the bound's closest edge vs. the bound's center here.
+        return spat.getWorldBound().distanceToEdge(cam.getLocation());
+    }
+
+    public int compare(Geometry o1, Geometry o2) {
+        float d1 = distanceToCam(o1);
+        float d2 = distanceToCam(o2);
+
+        if (d1 == d2)
+            return 0;
+        else if (d1 < d2)
+            return 1;
+        else
+            return -1;
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/AssetLinkNode.java b/engine/src/core/com/jme3/scene/AssetLinkNode.java
new file mode 100644
index 0000000..e50680c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/AssetLinkNode.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ModelKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.util.Map.Entry;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The AssetLinkNode does not store its children when exported to file.
+ * Instead, you can add a list of AssetKeys that will be loaded and attached
+ * when the AssetLinkNode is restored.
+ * 
+ * @author normenhansen
+ */
+public class AssetLinkNode extends Node {
+
+    protected ArrayList<ModelKey> assetLoaderKeys = new ArrayList<ModelKey>();
+    protected Map<ModelKey, Spatial> assetChildren = new HashMap<ModelKey, Spatial>();
+
+    public AssetLinkNode() {
+    }
+
+    public AssetLinkNode(ModelKey key) {
+        this(key.getName(), key);
+    }
+
+    public AssetLinkNode(String name, ModelKey key) {
+        super(name);
+        assetLoaderKeys.add(key);
+    }
+
+    /**
+     * Add a "linked" child. These are loaded from the assetManager when the
+     * AssetLinkNode is loaded from a binary file.
+     * @param key
+     */
+    public void addLinkedChild(ModelKey key) {
+        if (assetLoaderKeys.contains(key)) {
+            return;
+        }
+        assetLoaderKeys.add(key);
+    }
+
+    public void removeLinkedChild(ModelKey key) {
+        assetLoaderKeys.remove(key);
+    }
+
+    public ArrayList<ModelKey> getAssetLoaderKeys() {
+        return assetLoaderKeys;
+    }
+
+    public void attachLinkedChild(AssetManager manager, ModelKey key) {
+        addLinkedChild(key);
+        Spatial child = manager.loadAsset(key);
+        assetChildren.put(key, child);
+        attachChild(child);
+    }
+
+    public void attachLinkedChild(Spatial spat, ModelKey key) {
+        addLinkedChild(key);
+        assetChildren.put(key, spat);
+        attachChild(spat);
+    }
+
+    public void detachLinkedChild(ModelKey key) {
+        Spatial spatial = assetChildren.get(key);
+        if (spatial != null) {
+            detachChild(spatial);
+        }
+        removeLinkedChild(key);
+        assetChildren.remove(key);
+    }
+
+    public void detachLinkedChild(Spatial child, ModelKey key) {
+        removeLinkedChild(key);
+        assetChildren.remove(key);
+        detachChild(child);
+    }
+
+    /**
+     * Loads the linked children AssetKeys from the AssetManager and attaches them to the Node<br>
+     * If they are already attached, they will be reloaded.
+     * @param manager
+     */
+    public void attachLinkedChildren(AssetManager manager) {
+        detachLinkedChildren();
+        for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+            ModelKey assetKey = it.next();
+            Spatial curChild = assetChildren.get(assetKey);
+            if (curChild != null) {
+                curChild.removeFromParent();
+            }
+            Spatial child = manager.loadAsset(assetKey);
+            attachChild(child);
+            assetChildren.put(assetKey, child);
+        }
+    }
+
+    public void detachLinkedChildren() {
+        Set<Entry<ModelKey, Spatial>> set = assetChildren.entrySet();
+        for (Iterator<Entry<ModelKey, Spatial>> it = set.iterator(); it.hasNext();) {
+            Entry<ModelKey, Spatial> entry = it.next();
+            entry.getValue().removeFromParent();
+            it.remove();
+        }
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        BinaryImporter importer = BinaryImporter.getInstance();
+        AssetManager loaderManager = e.getAssetManager();
+
+        assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
+        for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+            ModelKey modelKey = it.next();
+            AssetInfo info = loaderManager.locateAsset(modelKey);
+            Spatial child = null;
+            if (info != null) {
+                child = (Spatial) importer.load(info);
+            }
+            if (child != null) {
+                child.parent = this;
+                children.add(child);
+                assetChildren.put(modelKey, child);
+            } else {
+                Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", 
+                                                                    new Object[]{ modelKey, key });
+            }
+        }
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        SafeArrayList<Spatial> childs = children;
+        children = new SafeArrayList<Spatial>(Spatial.class);
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null);
+        children = childs;
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/BatchNode.java b/engine/src/core/com/jme3/scene/BatchNode.java
new file mode 100644
index 0000000..bc1b2cb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/BatchNode.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
+ * There is one geometry per different material in the sub tree.
+ * this geometries are directly attached to the node in the scene graph.
+ * usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph and theire material set
+ * (see todo more automagic for further enhancements)
+ * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
+ * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
+ * sub geoms can be removed but it may be slower than the normal spatial removing
+ * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
+ * To integrate them in the batch you have to call the batch() method again on the batchNode.
+ * 
+ * TODO normal or tangents or both looks a bit weird
+ * TODO more automagic (batch when needed in the updateLigicalState)
+ * @author Nehon
+ */
+public class BatchNode extends Node implements Savable {
+
+    private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
+    /**
+     * the map of geometry holding the batched meshes
+     */
+    protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
+    /**
+     * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer 
+     */
+    private float[] tmpFloat;
+    private float[] tmpFloatN;
+    private float[] tmpFloatT;
+    int maxVertCount = 0;
+    boolean useTangents = false;
+    boolean needsFullRebatch = true;
+
+    /**
+     * Construct a batchNode
+     */
+    public BatchNode() {
+        super();
+    }
+
+    public BatchNode(String name) {
+        super(name);
+    }
+
+    @Override
+    public void updateGeometricState() {
+        if ((refreshFlags & RF_LIGHTLIST) != 0) {
+            updateWorldLightList();
+        }
+
+        if ((refreshFlags & RF_TRANSFORM) != 0) {
+            // combine with parent transforms- same for all spatial
+            // subclasses.
+            updateWorldTransforms();
+        }
+
+        if (!children.isEmpty()) {
+            // the important part- make sure child geometric state is refreshed
+            // first before updating own world bound. This saves
+            // a round-trip later on.
+            // NOTE 9/19/09
+            // Although it does save a round trip,
+
+            for (Spatial child : children.getArray()) {
+                child.updateGeometricState();
+            }
+
+            for (Batch batch : batches.values()) {
+                if (batch.needMeshUpdate) {
+                    batch.geometry.getMesh().updateBound();
+                    batch.geometry.updateWorldBound();
+                    batch.needMeshUpdate = false;
+
+                }
+            }
+
+
+        }
+
+        if ((refreshFlags & RF_BOUND) != 0) {
+            updateWorldBound();
+        }
+
+        assert refreshFlags == 0;
+    }
+
+    protected Transform getTransforms(Geometry geom) {
+        return geom.getWorldTransform();
+    }
+
+    protected void updateSubBatch(Geometry bg) {
+        Batch batch = batches.get(bg.getMaterial());
+        if (batch != null) {
+            Mesh mesh = batch.geometry.getMesh();
+
+            VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
+            FloatBuffer posBuf = (FloatBuffer) pvb.getData();
+            VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
+            FloatBuffer normBuf = (FloatBuffer) nvb.getData();
+
+            if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
+
+                VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
+                FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
+                doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+                tvb.updateData(tanBuf);
+            } else {
+                doTransforms(posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+            }
+            pvb.updateData(posBuf);
+            nvb.updateData(normBuf);
+
+
+            batch.needMeshUpdate = true;
+        }
+    }
+
+    /**
+     * Batch this batchNode
+     * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
+     */
+    public void batch() {
+        doBatch();
+        //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice        
+        for (Batch batch : batches.values()) {
+            batch.geometry.setIgnoreTransform(true);
+        }
+    }
+
+    protected void doBatch() {
+        Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
+        maxVertCount = 0;
+        int nbGeoms = 0;
+
+        gatherGeomerties(matMap, this, needsFullRebatch);
+        if (needsFullRebatch) {
+            for (Batch batch : batches.values()) {
+                batch.geometry.removeFromParent();
+            }
+            batches.clear();
+        }
+        
+        for (Material material : matMap.keySet()) {
+            Mesh m = new Mesh();
+            List<Geometry> list = matMap.get(material);
+            nbGeoms += list.size();
+            if (!needsFullRebatch) {
+                list.add(batches.get(material).geometry);
+            }
+            mergeGeometries(m, list);
+            m.setDynamic();
+            Batch batch = new Batch();
+
+            batch.geometry = new Geometry(name + "-batch" + batches.size());
+            batch.geometry.setMaterial(material);
+            this.attachChild(batch.geometry);
+
+
+            batch.geometry.setMesh(m);
+            batch.geometry.getMesh().updateCounts();
+            batch.geometry.getMesh().updateBound();
+            batches.put(material, batch);
+        }
+
+        logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
+
+
+        //init temp float arrays
+        tmpFloat = new float[maxVertCount * 3];
+        tmpFloatN = new float[maxVertCount * 3];
+        if (useTangents) {
+            tmpFloatT = new float[maxVertCount * 4];
+        }
+    }
+
+    private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
+
+        if (n.getClass() == Geometry.class) {
+
+            if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
+                Geometry g = (Geometry) n;
+                if (!g.isBatched() || rebatch) {
+                    if (g.getMaterial() == null) {
+                        throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
+                    }
+                    List<Geometry> list = map.get(g.getMaterial());
+                    if (list == null) {
+                        list = new ArrayList<Geometry>();
+                        map.put(g.getMaterial(), list);
+                    }
+                    g.setTransformRefresh();
+                    list.add(g);
+                }
+            }
+
+        } else if (n instanceof Node) {
+            for (Spatial child : ((Node) n).getChildren()) {
+                if (child instanceof BatchNode) {
+                    continue;
+                }
+                gatherGeomerties(map, child, rebatch);
+            }
+        }
+
+    }
+
+    private boolean isBatch(Spatial s) {
+        for (Batch batch : batches.values()) {
+            if (batch.geometry == s) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets the material to the all the batches of this BatchNode
+     * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
+     * 
+     * @param material the material to use for this geometry
+     */
+    @Override
+    public void setMaterial(Material material) {
+//        for (Batch batch : batches.values()) {
+//            batch.geometry.setMaterial(material);
+//        }
+        throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
+    }
+
+    /**
+     * Returns the material that is used for the first batch of this BatchNode
+     * 
+     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
+     * 
+     * @return the material that is used for the first batch of this BatchNode
+     * 
+     * @see #setMaterial(com.jme3.material.Material) 
+     */
+    public Material getMaterial() {
+        if (!batches.isEmpty()) {
+            Batch b = batches.get(batches.keySet().iterator().next());
+            return b.geometry.getMaterial();
+        }
+        return null;//material;
+    }
+
+//    /**
+//     * Sets the material to the a specific batch of this BatchNode
+//     * 
+//     * 
+//     * @param material the material to use for this geometry
+//     */   
+//    public void setMaterial(Material material,int batchIndex) {
+//        if (!batches.isEmpty()) {
+//            
+//        }
+//        
+//    }
+//
+//    /**
+//     * Returns the material that is used for the first batch of this BatchNode
+//     * 
+//     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
+//     * 
+//     * @return the material that is used for the first batch of this BatchNode
+//     * 
+//     * @see #setMaterial(com.jme3.material.Material) 
+//     */
+//    public Material getMaterial(int batchIndex) {
+//        if (!batches.isEmpty()) {
+//            Batch b = batches.get(batches.keySet().iterator().next());
+//            return b.geometry.getMaterial();
+//        }
+//        return null;//material;
+//    }
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+//
+//        if (material != null) {
+//            oc.write(material.getAssetName(), "materialName", null);
+//        }
+//        oc.write(material, "material", null);
+
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+
+
+//        material = null;
+//        String matName = ic.readString("materialName", null);
+//        if (matName != null) {
+//            // Material name is set,
+//            // Attempt to load material via J3M
+//            try {
+//                material = im.getAssetManager().loadMaterial(matName);
+//            } catch (AssetNotFoundException ex) {
+//                // Cannot find J3M file.
+//                logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
+//                        matName);
+//            }
+//        }
+//        // If material is NULL, try to load it from the geometry
+//        if (material == null) {
+//            material = (Material) ic.readSavable("material", null);
+//        }
+
+    }
+
+    /**
+     * Merges all geometries in the collection into
+     * the output mesh. Does not take into account materials.
+     * 
+     * @param geometries
+     * @param outMesh
+     */
+    private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
+        int[] compsForBuf = new int[VertexBuffer.Type.values().length];
+        VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
+
+        int totalVerts = 0;
+        int totalTris = 0;
+        int totalLodLevels = 0;
+
+        Mesh.Mode mode = null;
+        for (Geometry geom : geometries) {
+            totalVerts += geom.getVertexCount();
+            totalTris += geom.getTriangleCount();
+            totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
+            if (maxVertCount < geom.getVertexCount()) {
+                maxVertCount = geom.getVertexCount();
+            }
+            Mesh.Mode listMode;
+            int components;
+            switch (geom.getMesh().getMode()) {
+                case Points:
+                    listMode = Mesh.Mode.Points;
+                    components = 1;
+                    break;
+                case LineLoop:
+                case LineStrip:
+                case Lines:
+                    listMode = Mesh.Mode.Lines;
+                    components = 2;
+                    break;
+                case TriangleFan:
+                case TriangleStrip:
+                case Triangles:
+                    listMode = Mesh.Mode.Triangles;
+                    components = 3;
+                    break;
+                default:
+                    throw new UnsupportedOperationException();
+            }
+
+            for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
+                compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
+                formatForBuf[entry.getKey()] = entry.getValue().getFormat();
+            }
+
+            if (mode != null && mode != listMode) {
+                throw new UnsupportedOperationException("Cannot combine different"
+                        + " primitive types: " + mode + " != " + listMode);
+            }
+            mode = listMode;
+            compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
+        }
+
+        outMesh.setMode(mode);
+        if (totalVerts >= 65536) {
+            // make sure we create an UnsignedInt buffer so
+            // we can fit all of the meshes
+            formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
+        } else {
+            formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
+        }
+
+        // generate output buffers based on retrieved info
+        for (int i = 0; i < compsForBuf.length; i++) {
+            if (compsForBuf[i] == 0) {
+                continue;
+            }
+
+            Buffer data;
+            if (i == VertexBuffer.Type.Index.ordinal()) {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
+            } else {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
+            }
+
+            VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
+            vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data);
+            outMesh.setBuffer(vb);
+        }
+
+        int globalVertIndex = 0;
+        int globalTriIndex = 0;
+
+        for (Geometry geom : geometries) {
+            Mesh inMesh = geom.getMesh();
+            geom.batch(this, globalVertIndex);
+
+            int geomVertCount = inMesh.getVertexCount();
+            int geomTriCount = inMesh.getTriangleCount();
+
+            for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
+                VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+                VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+                if (outBuf == null) {
+                    continue;
+                }
+
+                if (VertexBuffer.Type.Index.ordinal() == bufType) {
+                    int components = compsForBuf[bufType];
+
+                    IndexBuffer inIdx = inMesh.getIndicesAsList();
+                    IndexBuffer outIdx = outMesh.getIndexBuffer();
+
+                    for (int tri = 0; tri < geomTriCount; tri++) {
+                        for (int comp = 0; comp < components; comp++) {
+                            int idx = inIdx.get(tri * components + comp) + globalVertIndex;
+                            outIdx.put((globalTriIndex + tri) * components + comp, idx);
+                        }
+                    }
+                } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();                    
+                    doCopyBuffer(inPos, globalVertIndex, outPos, 3);
+                } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                    doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]);
+                    if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
+                        useTangents = true;
+                    }
+                } else {
+                    inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
+//                    for (int vert = 0; vert < geomVertCount; vert++) {
+//                        int curGlobalVertIndex = globalVertIndex + vert;
+//                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
+//                    }
+                }
+            }
+
+            globalVertIndex += geomVertCount;
+            globalTriIndex += geomTriCount;
+        }
+    }
+
+    private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+        Vector3f norm = vars.vect2;
+
+        int length = (end - start) * 3;
+
+        // offset is given in element units
+        // convert to be in component units
+        int offset = start * 3;
+        bufPos.position(offset);
+        bufNorm.position(offset);
+        bufPos.get(tmpFloat, 0, length);
+        bufNorm.get(tmpFloatN, 0, length);
+        int index = 0;
+        while (index < length) {
+            pos.x = tmpFloat[index];
+            norm.x = tmpFloatN[index++];
+            pos.y = tmpFloat[index];
+            norm.y = tmpFloatN[index++];
+            pos.z = tmpFloat[index];
+            norm.z = tmpFloatN[index];
+
+            transform.mult(pos, pos);
+            transform.multNormal(norm, norm);
+
+            index -= 2;
+            tmpFloat[index] = pos.x;
+            tmpFloatN[index++] = norm.x;
+            tmpFloat[index] = pos.y;
+            tmpFloatN[index++] = norm.y;
+            tmpFloat[index] = pos.z;
+            tmpFloatN[index++] = norm.z;
+
+        }
+        vars.release();
+        bufPos.position(offset);
+        //using bulk put as it's faster
+        bufPos.put(tmpFloat, 0, length);
+        bufNorm.position(offset);
+        //using bulk put as it's faster
+        bufNorm.put(tmpFloatN, 0, length);
+    }
+
+    private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+        Vector3f norm = vars.vect2;
+        Vector3f tan = vars.vect3;
+
+        int length = (end - start) * 3;
+        int tanLength = (end - start) * 4;
+
+        // offset is given in element units
+        // convert to be in component units
+        int offset = start * 3;
+        int tanOffset = start * 4;
+
+        bufPos.position(offset);
+        bufNorm.position(offset);
+        bufTangents.position(tanOffset);
+        bufPos.get(tmpFloat, 0, length);
+        bufNorm.get(tmpFloatN, 0, length);
+        bufTangents.get(tmpFloatT, 0, tanLength);
+
+        int index = 0;
+        int tanIndex = 0;
+        while (index < length) {
+            pos.x = tmpFloat[index];
+            norm.x = tmpFloatN[index++];
+            pos.y = tmpFloat[index];
+            norm.y = tmpFloatN[index++];
+            pos.z = tmpFloat[index];
+            norm.z = tmpFloatN[index];
+
+            tan.x = tmpFloatT[tanIndex++];
+            tan.y = tmpFloatT[tanIndex++];
+            tan.z = tmpFloatT[tanIndex++];
+
+            transform.mult(pos, pos);
+            transform.multNormal(norm, norm);
+            transform.multNormal(tan, tan);
+
+            index -= 2;
+            tanIndex -= 3;
+
+            tmpFloat[index] = pos.x;
+            tmpFloatN[index++] = norm.x;
+            tmpFloat[index] = pos.y;
+            tmpFloatN[index++] = norm.y;
+            tmpFloat[index] = pos.z;
+            tmpFloatN[index++] = norm.z;
+
+            tmpFloatT[tanIndex++] = tan.x;
+            tmpFloatT[tanIndex++] = tan.y;
+            tmpFloatT[tanIndex++] = tan.z;
+
+            //Skipping 4th element of tangent buffer (handedness)
+            tanIndex++;
+
+        }
+        vars.release();
+        bufPos.position(offset);
+        //using bulk put as it's faster
+        bufPos.put(tmpFloat, 0, length);
+        bufNorm.position(offset);
+        //using bulk put as it's faster
+        bufNorm.put(tmpFloatN, 0, length);
+        bufTangents.position(tanOffset);
+        //using bulk put as it's faster
+        bufTangents.put(tmpFloatT, 0, tanLength);
+    }
+
+    private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= componentSize;
+
+        for (int i = 0; i < inBuf.capacity() / componentSize; i++) {
+            pos.x = inBuf.get(i * componentSize + 0);
+            pos.y = inBuf.get(i * componentSize + 1);
+            pos.z = inBuf.get(i * componentSize + 2);
+
+            outBuf.put(offset + i * componentSize + 0, pos.x);
+            outBuf.put(offset + i * componentSize + 1, pos.y);
+            outBuf.put(offset + i * componentSize + 2, pos.z);
+        }
+        vars.release();
+    }
+
+    protected class Batch {
+
+        Geometry geometry;
+        boolean needMeshUpdate = false;
+    }
+
+    protected void setNeedsFullRebatch(boolean needsFullRebatch) {
+        this.needsFullRebatch = needsFullRebatch;
+    }
+    
+    public int getOffsetIndex(Geometry batchedGeometry){
+        return batchedGeometry.startIndex;
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/CameraNode.java b/engine/src/core/com/jme3/scene/CameraNode.java
new file mode 100644
index 0000000..c6bb48b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CameraNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+
+/**
+ * <code>CameraNode</code> simply uses {@link CameraControl} to implement
+ * linking of camera and node data.
+ *
+ * @author Tim8Dev
+ */
+public class CameraNode extends Node {
+
+    private CameraControl camControl;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public CameraNode() {
+    }
+
+    public CameraNode(String name, Camera camera) {
+        this(name, new CameraControl(camera));
+    }
+
+    public CameraNode(String name, CameraControl control) {
+        super(name);
+        addControl(control);
+        camControl = control;
+    }
+
+    public void setEnabled(boolean enabled) {
+        camControl.setEnabled(enabled);
+    }
+
+    public boolean isEnabled() {
+        return camControl.isEnabled();
+    }
+
+    public void setControlDir(ControlDirection controlDir) {
+        camControl.setControlDir(controlDir);
+    }
+
+    public void setCamera(Camera camera) {
+        camControl.setCamera(camera);
+    }
+
+    public ControlDirection getControlDir() {
+        return camControl.getControlDir();
+    }
+
+    public Camera getCamera() {
+        return camControl.getCamera();
+    }
+
+//    @Override
+//    public void lookAt(Vector3f position, Vector3f upVector) {
+//        this.lookAt(position, upVector);
+//        camControl.getCamera().lookAt(position, upVector);
+//    }
+}
diff --git a/engine/src/core/com/jme3/scene/CollisionData.java b/engine/src/core/com/jme3/scene/CollisionData.java
new file mode 100644
index 0000000..914bf24
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CollisionData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix4f;
+
+/**
+ * <code>CollisionData</code> is an interface that can be used to 
+ * do triangle-accurate collision with bounding volumes and rays.
+ *
+ * @author Kirill Vainer
+ */
+public interface CollisionData extends Savable, Cloneable {
+    public int collideWith(Collidable other,
+                           Matrix4f worldMatrix,
+                           BoundingVolume worldBound,
+                           CollisionResults results);
+}
diff --git a/engine/src/core/com/jme3/scene/Geometry.java b/engine/src/core/com/jme3/scene/Geometry.java
new file mode 100644
index 0000000..b02196d
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Geometry.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Geometry</code> defines a leaf node of the scene graph. The leaf node
+ * contains the geometric data for rendering objects. It manages all rendering
+ * information such as a {@link Material} object to define how the surface
+ * should be shaded and the {@link Mesh} data to contain the actual geometry.
+ * 
+ * @author Kirill Vainer
+ */
+public class Geometry extends Spatial {
+
+    // Version #1: removed shared meshes. 
+    // models loaded with shared mesh will be automatically fixed.
+    public static final int SAVABLE_VERSION = 1;
+    
+    private static final Logger logger = Logger.getLogger(Geometry.class.getName());
+    protected Mesh mesh;
+    protected transient int lodLevel = 0;
+    protected Material material;
+    /**
+     * When true, the geometry's transform will not be applied.
+     */
+    protected boolean ignoreTransform = false;
+    protected transient Matrix4f cachedWorldMat = new Matrix4f();
+    /**
+     * used when geometry is batched
+     */
+    protected BatchNode batchNode = null;
+    /**
+     * the start index of this geom's mesh in the batchNode mesh
+     */
+    protected int startIndex;
+    /**
+     * the previous transforms of the geometry used to compute world transforms
+     */
+    protected Transform prevBatchTransforms = null;
+    /**
+     * the cached offset matrix used when the geometry is batched
+     */
+    protected Matrix4f cachedOffsetMat = null;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Geometry() {
+    }
+
+    /**
+     * Create a geometry node without any mesh data.
+     * Both the mesh and the material are null, the geometry
+     * cannot be rendered until those are set.
+     * 
+     * @param name The name of this geometry
+     */
+    public Geometry(String name) {
+        super(name);
+    }
+
+    /**
+     * Create a geometry node with mesh data.
+     * The material of the geometry is null, it cannot
+     * be rendered until it is set.
+     * 
+     * @param name The name of this geometry
+     * @param mesh The mesh data for this geometry
+     */
+    public Geometry(String name, Mesh mesh) {
+        this(name);
+        if (mesh == null) {
+            throw new NullPointerException();
+        }
+
+        this.mesh = mesh;
+    }
+
+    /**
+     * @return If ignoreTransform mode is set.
+     * 
+     * @see Geometry#setIgnoreTransform(boolean) 
+     */
+    public boolean isIgnoreTransform() {
+        return ignoreTransform;
+    }
+
+    /**
+     * @param ignoreTransform If true, the geometry's transform will not be applied.
+     */
+    public void setIgnoreTransform(boolean ignoreTransform) {
+        this.ignoreTransform = ignoreTransform;
+    }
+
+    /**
+     * Sets the LOD level to use when rendering the mesh of this geometry.
+     * Level 0 indicates that the default index buffer should be used,
+     * levels [1, LodLevels + 1] represent the levels set on the mesh
+     * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
+     * 
+     * @param lod The lod level to set
+     */
+    @Override
+    public void setLodLevel(int lod) {
+        if (mesh.getNumLodLevels() == 0) {
+            throw new IllegalStateException("LOD levels are not set on this mesh");
+        }
+
+        if (lod < 0 || lod >= mesh.getNumLodLevels()) {
+            throw new IllegalArgumentException("LOD level is out of range: " + lod);
+        }
+
+        lodLevel = lod;
+    }
+
+    /**
+     * Returns the LOD level set with {@link #setLodLevel(int) }.
+     * 
+     * @return the LOD level set
+     */
+    public int getLodLevel() {
+        return lodLevel;
+    }
+
+    /**
+     * Returns this geometry's mesh vertex count.
+     * 
+     * @return this geometry's mesh vertex count.
+     * 
+     * @see Mesh#getVertexCount() 
+     */
+    public int getVertexCount() {
+        return mesh.getVertexCount();
+    }
+
+    /**
+     * Returns this geometry's mesh triangle count.
+     * 
+     * @return this geometry's mesh triangle count.
+     * 
+     * @see Mesh#getTriangleCount() 
+     */
+    public int getTriangleCount() {
+        return mesh.getTriangleCount();
+    }
+
+    /**
+     * Sets the mesh to use for this geometry when rendering.
+     * 
+     * @param mesh the mesh to use for this geometry
+     * 
+     * @throws IllegalArgumentException If mesh is null
+     */
+    public void setMesh(Mesh mesh) {
+        if (mesh == null) {
+            throw new IllegalArgumentException();
+        }
+        if (isBatched()) {
+            throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
+        }
+
+        this.mesh = mesh;
+        setBoundRefresh();
+    }
+
+    /**
+     * Returns the mseh to use for this geometry
+     * 
+     * @return the mseh to use for this geometry
+     * 
+     * @see #setMesh(com.jme3.scene.Mesh) 
+     */
+    public Mesh getMesh() {
+        return mesh;
+    }
+
+    /**
+     * Sets the material to use for this geometry.
+     * 
+     * @param material the material to use for this geometry
+     */
+    @Override
+    public void setMaterial(Material material) {
+        if (isBatched()) {
+            throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
+        }
+        this.material = material;
+    }
+
+    /**
+     * Returns the material that is used for this geometry.
+     * 
+     * @return the material that is used for this geometry
+     * 
+     * @see #setMaterial(com.jme3.material.Material) 
+     */
+    public Material getMaterial() {
+        return material;
+    }
+
+    /**
+     * @return The bounding volume of the mesh, in model space.
+     */
+    public BoundingVolume getModelBound() {
+        return mesh.getBound();
+    }
+
+    /**
+     * Updates the bounding volume of the mesh. Should be called when the
+     * mesh has been modified.
+     */
+    public void updateModelBound() {
+        mesh.updateBound();
+        setBoundRefresh();
+    }
+
+    /**
+     * <code>updateWorldBound</code> updates the bounding volume that contains
+     * this geometry. The location of the geometry is based on the location of
+     * all this node's parents.
+     *
+     * @see Spatial#updateWorldBound()
+     */
+    @Override
+    protected void updateWorldBound() {
+        super.updateWorldBound();
+        if (mesh == null) {
+            throw new NullPointerException("Geometry: " + getName() + " has null mesh");
+        }
+
+        if (mesh.getBound() != null) {
+            if (ignoreTransform) {
+                // we do not transform the model bound by the world transform,
+                // just use the model bound as-is
+                worldBound = mesh.getBound().clone(worldBound);
+            } else {
+                worldBound = mesh.getBound().transform(worldTransform, worldBound);
+            }
+        }
+    }
+
+    @Override
+    protected void updateWorldTransforms() {
+
+        super.updateWorldTransforms();
+        computeWorldMatrix();
+
+        if (isBatched()) {
+            computeOffsetTransform();
+            batchNode.updateSubBatch(this);
+            prevBatchTransforms.set(batchNode.getTransforms(this));
+
+        }
+        // geometry requires lights to be sorted
+        worldLights.sort(true);
+    }
+
+    /**
+     * Batch this geometry, should only be called by the BatchNode.
+     * @param node the batchNode
+     * @param startIndex the starting index of this geometry in the batched mesh
+     */
+    protected void batch(BatchNode node, int startIndex) {
+        this.batchNode = node;
+        this.startIndex = startIndex;
+        prevBatchTransforms = new Transform();
+        cachedOffsetMat = new Matrix4f();
+        setCullHint(CullHint.Always);
+    }
+
+    /**
+     * unBatch this geometry. 
+     */
+    protected void unBatch() {
+        this.startIndex = 0;
+        prevBatchTransforms = null;
+        cachedOffsetMat = null;
+        //once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
+        this.batchNode.setNeedsFullRebatch(true);
+        this.batchNode = null;
+        setCullHint(CullHint.Dynamic);
+    }
+
+    @Override
+    public boolean removeFromParent() {
+        boolean removed = super.removeFromParent();
+        //if the geometry is batched we also have to unbatch it
+        if (isBatched()) {
+            unBatch();
+        }
+        return removed;
+    }
+
+    /**
+     * Recomputes the cached offset matrix used when the geometry is batched     * 
+     */
+    public void computeOffsetTransform() {
+        TempVars vars = TempVars.get();
+        Matrix4f tmpMat = vars.tempMat42;
+
+        // Compute the cached world matrix
+        cachedOffsetMat.loadIdentity();
+        cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
+        cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
+
+
+        Matrix4f scaleMat = vars.tempMat4;
+        scaleMat.loadIdentity();
+        scaleMat.scale(prevBatchTransforms.getScale());
+        cachedOffsetMat.multLocal(scaleMat);
+        cachedOffsetMat.invertLocal();
+
+        tmpMat.loadIdentity();
+        tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
+        tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
+        scaleMat.loadIdentity();
+        scaleMat.scale(batchNode.getTransforms(this).getScale());
+        tmpMat.multLocal(scaleMat);
+
+        tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
+
+        vars.release();
+    }
+
+    /**
+     * Indicate that the transform of this spatial has changed and that
+     * a refresh is required.
+     */
+    @Override
+    protected void setTransformRefresh() {
+        refreshFlags |= RF_TRANSFORM;
+        setBoundRefresh();
+    }
+
+    /**
+     * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
+     * This will require a localized transform update for this geometry.
+     */
+    public void computeWorldMatrix() {
+        // Force a local update of the geometry's transform
+        checkDoTransformUpdate();
+
+        // Compute the cached world matrix
+        cachedWorldMat.loadIdentity();
+        cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
+        cachedWorldMat.setTranslation(worldTransform.getTranslation());
+
+        TempVars vars = TempVars.get();
+        Matrix4f scaleMat = vars.tempMat4;
+        scaleMat.loadIdentity();
+        scaleMat.scale(worldTransform.getScale());
+        cachedWorldMat.multLocal(scaleMat);
+        vars.release();
+    }
+
+    /**
+     * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh}
+     * from model space to world space. This matrix is computed based on the
+     * {@link Geometry#getWorldTransform() world transform} of this geometry.
+     * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
+     * before using this method.
+     * 
+     * @return Matrix to transform from local space to world space
+     */
+    public Matrix4f getWorldMatrix() {
+        return cachedWorldMat;
+    }
+
+    /**
+     * Sets the model bound to use for this geometry.
+     * This alters the bound used on the mesh as well via
+     * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
+     * forces the world bounding volume to be recomputed.
+     * 
+     * @param modelBound The model bound to set
+     */
+    @Override
+    public void setModelBound(BoundingVolume modelBound) {
+        this.worldBound = null;
+        mesh.setBound(modelBound);
+        setBoundRefresh();
+
+        // NOTE: Calling updateModelBound() would cause the mesh
+        // to recompute the bound based on the geometry thus making
+        // this call useless!
+        //updateModelBound();
+    }
+
+    public int collideWith(Collidable other, CollisionResults results) {
+        // Force bound to update
+        checkDoBoundUpdate();
+        // Update transform, and compute cached world matrix
+        computeWorldMatrix();
+
+        assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0;
+
+        if (mesh != null) {
+            // NOTE: BIHTree in mesh already checks collision with the
+            // mesh's bound
+            int prevSize = results.size();
+            int added = mesh.collideWith(other, cachedWorldMat, worldBound, results);
+            int newSize = results.size();
+            for (int i = prevSize; i < newSize; i++) {
+                results.getCollisionDirect(i).setGeometry(this);
+            }
+            return added;
+        }
+        return 0;
+    }
+
+    @Override
+    public void depthFirstTraversal(SceneGraphVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+    }
+
+    public boolean isBatched() {
+        return batchNode != null;
+    }
+
+    /**
+     * This version of clone is a shallow clone, in other words, the
+     * same mesh is referenced as the original geometry.
+     * Exception: if the mesh is marked as being a software
+     * animated mesh, (bind pose is set) then the positions
+     * and normals are deep copied.
+     */
+    @Override
+    public Geometry clone(boolean cloneMaterial) {
+        Geometry geomClone = (Geometry) super.clone(cloneMaterial);
+        geomClone.cachedWorldMat = cachedWorldMat.clone();
+        if (material != null) {
+            if (cloneMaterial) {
+                geomClone.material = material.clone();
+            } else {
+                geomClone.material = material;
+            }
+        }
+
+        if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
+            geomClone.mesh = mesh.cloneForAnim();
+        }
+
+        return geomClone;
+    }
+
+    /**
+     * This version of clone is a shallow clone, in other words, the
+     * same mesh is referenced as the original geometry.
+     * Exception: if the mesh is marked as being a software
+     * animated mesh, (bind pose is set) then the positions
+     * and normals are deep copied.
+     */
+    @Override
+    public Geometry clone() {
+        return clone(true);
+    }
+
+    /**
+     * Creates a deep clone of the geometry,
+     * this creates an identical copy of the mesh
+     * with the vertexbuffer data duplicated.
+     */
+    @Override
+    public Spatial deepClone() {
+        Geometry geomClone = clone(true);
+        geomClone.mesh = mesh.deepClone();
+        return geomClone;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(mesh, "mesh", null);
+        if (material != null) {
+            oc.write(material.getAssetName(), "materialName", null);
+        }
+        oc.write(material, "material", null);
+        oc.write(ignoreTransform, "ignoreTransform", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        mesh = (Mesh) ic.readSavable("mesh", null);
+
+        material = null;
+        String matName = ic.readString("materialName", null);
+        if (matName != null) {
+            // Material name is set,
+            // Attempt to load material via J3M
+            try {
+                material = im.getAssetManager().loadMaterial(matName);
+            } catch (AssetNotFoundException ex) {
+                // Cannot find J3M file.
+                logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key});
+            }
+        }
+        // If material is NULL, try to load it from the geometry
+        if (material == null) {
+            material = (Material) ic.readSavable("material", null);
+        }
+        ignoreTransform = ic.readBoolean("ignoreTransform", false);
+        
+        if (ic.getSavableVersion(Geometry.class) == 0){
+            // Fix shared mesh (if set)
+            Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH);
+            if (sharedMesh != null){
+                getMesh().extractVertexData(sharedMesh);
+            }
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/LightNode.java b/engine/src/core/com/jme3/scene/LightNode.java
new file mode 100644
index 0000000..5e6dc76
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/LightNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.light.Light;
+import com.jme3.scene.control.LightControl;
+import com.jme3.scene.control.LightControl.ControlDirection;
+
+/**
+ * <code>LightNode</code> is used to link together a {@link Light} object
+ * with a {@link Node} object. 
+ *
+ * @author Tim8Dev
+ */
+public class LightNode extends Node {
+
+    private LightControl lightControl;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public LightNode() {
+    }
+
+    public LightNode(String name, Light light) {
+        this(name, new LightControl(light));
+    }
+
+    public LightNode(String name, LightControl control) {
+        super(name);
+        addControl(control);
+        lightControl = control;
+    }
+
+    /**
+     * Enable or disable the <code>LightNode</code> functionality.
+     * 
+     * @param enabled If false, the functionality of LightNode will
+     * be disabled.
+     */
+    public void setEnabled(boolean enabled) {
+        lightControl.setEnabled(enabled);
+    }
+
+    public boolean isEnabled() {
+        return lightControl.isEnabled();
+    }
+
+    public void setControlDir(ControlDirection controlDir) {
+        lightControl.setControlDir(controlDir);
+    }
+
+    public void setLight(Light light) {
+        lightControl.setLight(light);
+    }
+
+    public ControlDirection getControlDir() {
+        return lightControl.getControlDir();
+    }
+
+    public Light getLight() {
+        return lightControl.getLight();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java
new file mode 100644
index 0000000..6c587f2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Mesh.java
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.bih.BIHTree;
+import com.jme3.export.*;
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>Mesh</code> is used to store rendering data.
+ * <p>
+ * All visible elements in a scene are represented by meshes.
+ * Meshes may contain three types of geometric primitives:
+ * <ul>
+ * <li>Points - Every vertex represents a single point in space, 
+ * the size of each point is specified via {@link Mesh#setPointSize(float) }.
+ * Points can also be used for {@link RenderState#setPointSprite(boolean) point
+ * sprite} mode.</li>
+ * <li>Lines - 2 vertices represent a line segment, with the width specified
+ * via {@link Mesh#setLineWidth(float) }.</li>
+ * <li>Triangles - 3 vertices represent a solid triangle primitive. </li>
+ * </ul>
+ * 
+ * @author Kirill Vainer
+ */
+public class Mesh implements Savable, Cloneable {
+
+    /**
+     * The mode of the Mesh specifies both the type of primitive represented
+     * by the mesh and how the data should be interpreted.
+     */
+    public enum Mode {
+        /**
+         * A primitive is a single point in space. The size of the points 
+         * can be specified with {@link Mesh#setPointSize(float) }.
+         */
+        Points(true),
+        
+        /**
+         * A primitive is a line segment. Every two vertices specify
+         * a single line. {@link Mesh#setLineWidth(float) } can be used 
+         * to set the width of the lines.
+         */
+        Lines(true),
+        
+        /**
+         * A primitive is a line segment. The first two vertices specify
+         * a single line, while subsequent vertices are combined with the 
+         * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can 
+         * be used to set the width of the lines.
+         */
+        LineStrip(false),
+        
+        /**
+         * Identical to {@link #LineStrip} except that at the end
+         * the last vertex is connected with the first to form a line.
+         * {@link Mesh#setLineWidth(float) } can be used 
+         * to set the width of the lines.
+         */
+        LineLoop(false),
+        
+        /**
+         * A primitive is a triangle. Each 3 vertices specify a single
+         * triangle.
+         */
+        Triangles(true),
+        
+        /**
+         * Similar to {@link #Triangles}, the first 3 vertices 
+         * specify a triangle, while subsequent vertices are combined with
+         * the previous two to form a triangle. 
+         */
+        TriangleStrip(false),
+        
+        /**
+         * Similar to {@link #Triangles}, the first 3 vertices 
+         * specify a triangle, each 2 subsequent vertices are combined
+         * with the very first vertex to make a triangle.
+         */
+        TriangleFan(false),
+        
+        /**
+         * A combination of various triangle modes. It is best to avoid
+         * using this mode as it may not be supported by all renderers.
+         * The {@link Mesh#setModeStart(int[]) mode start points} and
+         * {@link Mesh#setElementLengths(int[]) element lengths} must 
+         * be specified for this mode.
+         */
+        Hybrid(false);
+        
+        private boolean listMode = false;
+        
+        private Mode(boolean listMode){
+            this.listMode = listMode;
+        }
+        
+        /**
+         * Returns true if the specified mode is a list mode (meaning
+         * ,it specifies the indices as a linear list and not some special 
+         * format).
+         * Will return true for the types {@link #Points}, {@link #Lines} and
+         * {@link #Triangles}.
+         * 
+         * @return true if the mode is a list type mode
+         */
+        public boolean isListMode(){
+            return listMode;
+        }
+    }
+
+    /**
+     * The bounding volume that contains the mesh entirely.
+     * By default a BoundingBox (AABB).
+     */
+    private BoundingVolume meshBound =  new BoundingBox();
+
+    private CollisionData collisionTree = null;
+
+    private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+    private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
+    private VertexBuffer[] lodLevels;
+    private float pointSize = 1;
+    private float lineWidth = 1;
+
+    private transient int vertexArrayID = -1;
+
+    private int vertCount = -1;
+    private int elementCount = -1;
+    private int maxNumWeights = -1; // only if using skeletal animation
+
+    private int[] elementLengths;
+    private int[] modeStart;
+
+    private Mode mode = Mode.Triangles;
+
+    /**
+     * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
+     */
+    public Mesh(){
+    }
+
+    /**
+     * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
+     * buffers} are shared between this and the clone mesh, the rest
+     * of the data is cloned.
+     * 
+     * @return A shallow clone of the mesh
+     */
+    @Override
+    public Mesh clone() {
+        try {
+            Mesh clone = (Mesh) super.clone();
+            clone.meshBound = meshBound.clone();
+            clone.collisionTree = collisionTree != null ? collisionTree : null;
+            clone.buffers = buffers.clone();
+            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+            clone.vertexArrayID = -1;
+            if (elementLengths != null) {
+                clone.elementLengths = elementLengths.clone();
+            }
+            if (modeStart != null) {
+                clone.modeStart = modeStart.clone();
+            }
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Creates a deep clone of this mesh. 
+     * The {@link VertexBuffer vertex buffers} and the data inside them
+     * is cloned.
+     * 
+     * @return a deep clone of this mesh.
+     */
+    public Mesh deepClone(){
+        try{
+            Mesh clone = (Mesh) super.clone();
+            clone.meshBound = meshBound != null ? meshBound.clone() : null;
+
+            // TODO: Collision tree cloning
+            //clone.collisionTree = collisionTree != null ? collisionTree : null;
+            clone.collisionTree = null; // it will get re-generated in any case
+
+            clone.buffers = new IntMap<VertexBuffer>();
+            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+            for (Entry<VertexBuffer> ent : buffers){
+                VertexBuffer bufClone = ent.getValue().clone();
+                clone.buffers.put(ent.getKey(), bufClone);
+                clone.buffersList.add(bufClone);
+            }
+            
+            clone.vertexArrayID = -1;
+            clone.vertCount = -1;
+            clone.elementCount = -1;
+            
+            // although this could change
+            // if the bone weight/index buffers are modified
+            clone.maxNumWeights = maxNumWeights; 
+            
+            clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
+            clone.modeStart = modeStart != null ? modeStart.clone() : null;
+            return clone;
+        }catch (CloneNotSupportedException ex){
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Clone the mesh for animation use.
+     * This creates a shallow clone of the mesh, sharing most
+     * of the {@link VertexBuffer vertex buffer} data, however the
+     * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers
+     * are deeply cloned.
+     * 
+     * @return A clone of the mesh for animation use.
+     */
+    public Mesh cloneForAnim(){
+        Mesh clone = clone();
+        if (getBuffer(Type.BindPosePosition) != null){
+            VertexBuffer oldPos = getBuffer(Type.Position);
+            
+            // NOTE: creates deep clone
+            VertexBuffer newPos = oldPos.clone();
+            clone.clearBuffer(Type.Position);
+            clone.setBuffer(newPos);
+
+            if (getBuffer(Type.BindPoseNormal) != null){
+                VertexBuffer oldNorm = getBuffer(Type.Normal);
+                VertexBuffer newNorm = oldNorm.clone();
+                clone.clearBuffer(Type.Normal);
+                clone.setBuffer(newNorm);
+                
+                if (getBuffer(Type.BindPoseTangent) != null){
+                    VertexBuffer oldTang = getBuffer(Type.Tangent);
+                    VertexBuffer newTang = oldTang.clone();
+                    clone.clearBuffer(Type.Tangent);
+                    clone.setBuffer(newTang);
+                }
+            }
+        }
+        return clone;
+    }
+
+    /**
+     * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
+     * and {@link Type#BindPoseTangent} 
+     * buffers for this mesh by duplicating them based on the position and normal
+     * buffers already set on the mesh.
+     * This method does nothing if the mesh has no bone weight or index
+     * buffers.
+     * 
+     * @param forSoftwareAnim Should be true if the bind pose is to be generated.
+     */
+    public void generateBindPose(boolean forSoftwareAnim){
+        if (forSoftwareAnim){
+            VertexBuffer pos = getBuffer(Type.Position);
+            if (pos == null || getBuffer(Type.BoneIndex) == null) {
+                // ignore, this mesh doesn't have positional data
+                // or it doesn't have bone-vertex assignments, so its not animated
+                return;
+            }
+
+            VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
+            bindPos.setupData(Usage.CpuOnly,
+                    3,
+                    Format.Float,
+                    BufferUtils.clone(pos.getData()));
+            setBuffer(bindPos);
+
+            // XXX: note that this method also sets stream mode
+            // so that animation is faster. this is not needed for hardware skinning
+            pos.setUsage(Usage.Stream);
+
+            VertexBuffer norm = getBuffer(Type.Normal);
+            if (norm != null) {
+                VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
+                bindNorm.setupData(Usage.CpuOnly,
+                        3,
+                        Format.Float,
+                        BufferUtils.clone(norm.getData()));
+                setBuffer(bindNorm);
+                norm.setUsage(Usage.Stream);
+            }
+            
+            VertexBuffer tangents = getBuffer(Type.Tangent);
+            if (tangents != null) {
+                VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+                bindTangents.setupData(Usage.CpuOnly,
+                        4,
+                        Format.Float,
+                        BufferUtils.clone(tangents.getData()));
+                setBuffer(bindTangents);
+                tangents.setUsage(Usage.Stream);
+            }
+        }
+    }
+
+    /**
+     * Prepares the mesh for software skinning by converting the bone index
+     * and weight buffers to heap buffers. 
+     * 
+     * @param forSoftwareAnim Should be true to enable the conversion.
+     */
+    public void prepareForAnim(boolean forSoftwareAnim){
+        if (forSoftwareAnim){
+            // convert indices
+            VertexBuffer indices = getBuffer(Type.BoneIndex);
+            ByteBuffer originalIndex = (ByteBuffer) indices.getData();
+            ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
+            originalIndex.clear();
+            arrayIndex.put(originalIndex);
+            indices.updateData(arrayIndex);
+
+            // convert weights
+            VertexBuffer weights = getBuffer(Type.BoneWeight);
+            FloatBuffer originalWeight = (FloatBuffer) weights.getData();
+            FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
+            originalWeight.clear();
+            arrayWeight.put(originalWeight);
+            weights.updateData(arrayWeight);
+        }
+    }
+
+    /**
+     * Set the LOD (level of detail) index buffers on this mesh.
+     * 
+     * @param lodLevels The LOD levels to set
+     */
+    public void setLodLevels(VertexBuffer[] lodLevels){
+        this.lodLevels = lodLevels;
+    }
+
+    /**
+     * @return The number of LOD levels set on this mesh, including the main
+     * index buffer, returns zero if there are no lod levels.
+     */
+    public int getNumLodLevels(){
+        return lodLevels != null ? lodLevels.length : 0;
+    }
+
+    /**
+     * Returns the lod level at the given index.
+     * 
+     * @param lod The lod level index, this does not include
+     * the main index buffer.
+     * @return The LOD index buffer at the index
+     * 
+     * @throws IndexOutOfBoundsException If the index is outside of the 
+     * range [0, {@link #getNumLodLevels()}].
+     * 
+     * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) 
+     */
+    public VertexBuffer getLodLevel(int lod){
+        return lodLevels[lod];
+    }
+    
+    /**
+     * Get the element lengths for {@link Mode#Hybrid} mesh mode.
+     * 
+     * @return element lengths
+     */
+    public int[] getElementLengths() {
+        return elementLengths;
+    }
+
+    /**
+     * Set the element lengths for {@link Mode#Hybrid} mesh mode.
+     * 
+     * @param elementLengths The element lengths to set
+     */
+    public void setElementLengths(int[] elementLengths) {
+        this.elementLengths = elementLengths;
+    }
+
+    /**
+     * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
+     * 
+     * @return mode start indices
+     */
+    public int[] getModeStart() {
+        return modeStart;
+    }
+
+    /**
+     * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
+     * 
+     * @return mode start indices
+     */
+    public void setModeStart(int[] modeStart) {
+        this.modeStart = modeStart;
+    }
+
+    /**
+     * Returns the mesh mode
+     * 
+     * @return the mesh mode
+     * 
+     * @see #setMode(com.jme3.scene.Mesh.Mode) 
+     */
+    public Mode getMode() {
+        return mode;
+    }
+
+    /**
+     * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
+     * 
+     * @param mode The new mode to set
+     * 
+     * @see Mode
+     */
+    public void setMode(Mode mode) {
+        this.mode = mode;
+        updateCounts();
+    }
+
+    /**
+     * Returns the maximum number of weights per vertex on this mesh.
+     * 
+     * @return maximum number of weights per vertex
+     * 
+     * @see #setMaxNumWeights(int) 
+     */
+    public int getMaxNumWeights() {
+        return maxNumWeights;
+    }
+
+    /**
+     * Set the maximum number of weights per vertex on this mesh.
+     * Only relevant if this mesh has bone index/weight buffers.
+     * This value should be between 0 and 4.
+     * 
+     * @param maxNumWeights 
+     */
+    public void setMaxNumWeights(int maxNumWeights) {
+        this.maxNumWeights = maxNumWeights;
+    }
+
+    /**
+     * Returns the size of points for point meshes
+     * 
+     * @return the size of points
+     * 
+     * @see #setPointSize(float) 
+     */
+    public float getPointSize() {
+        return pointSize;
+    }
+
+    /**
+     * Set the size of points for meshes of mode {@link Mode#Points}. 
+     * The point size is specified as on-screen pixels, the default
+     * value is 1.0. The point size
+     * does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
+     * render state is enabled, in that case, the vertex shader must specify the 
+     * point size by writing to <code>gl_PointSize</code>.
+     * 
+     * @param pointSize The size of points
+     */
+    public void setPointSize(float pointSize) {
+        this.pointSize = pointSize;
+    }
+
+    /**
+     * Returns the line width for line meshes.
+     * 
+     * @return the line width
+     */
+    public float getLineWidth() {
+        return lineWidth;
+    }
+
+    /**
+     * Specify the line width for meshes of the line modes, such
+     * as {@link Mode#Lines}. The line width is specified as on-screen pixels, 
+     * the default value is 1.0.
+     * 
+     * @param lineWidth The line width
+     */
+    public void setLineWidth(float lineWidth) {
+        this.lineWidth = lineWidth;
+    }
+
+    /**
+     * Indicates to the GPU that this mesh will not be modified (a hint). 
+     * Sets the usage mode to {@link Usage#Static}
+     * for all {@link VertexBuffer vertex buffers} on this Mesh.
+     */
+    public void setStatic() {
+        for (Entry<VertexBuffer> entry : buffers){
+            entry.getValue().setUsage(Usage.Static);
+        }
+    }
+
+    /**
+     * Indicates to the GPU that this mesh will be modified occasionally (a hint).
+     * Sets the usage mode to {@link Usage#Dynamic}
+     * for all {@link VertexBuffer vertex buffers} on this Mesh.
+     */
+    public void setDynamic() {
+        for (Entry<VertexBuffer> entry : buffers){
+            entry.getValue().setUsage(Usage.Dynamic);
+        }
+    }
+
+    /**
+     * Indicates to the GPU that this mesh will be modified every frame (a hint).
+     * Sets the usage mode to {@link Usage#Stream}
+     * for all {@link VertexBuffer vertex buffers} on this Mesh.
+     */
+    public void setStreamed(){
+        for (Entry<VertexBuffer> entry : buffers){
+            entry.getValue().setUsage(Usage.Stream);
+        }
+    }
+
+    /**
+     * Interleaves the data in this mesh. This operation cannot be reversed.
+     * Some GPUs may prefer the data in this format, however it is a good idea
+     * to <em>avoid</em> using this method as it disables some engine features.
+     */
+    public void setInterleaved(){
+        ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
+        for (Entry<VertexBuffer> entry : buffers){
+            vbs.add(entry.getValue());
+        }
+//        ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
+        // index buffer not included when interleaving
+        vbs.remove(getBuffer(Type.Index));
+
+        int stride = 0; // aka bytes per vertex
+        for (int i = 0; i < vbs.size(); i++){
+            VertexBuffer vb = vbs.get(i);
+//            if (vb.getFormat() != Format.Float){
+//                throw new UnsupportedOperationException("Cannot interleave vertex buffer.\n" +
+//                                                        "Contains not-float data.");
+//            }
+            stride += vb.componentsLength;
+            vb.getData().clear(); // reset position & limit (used later)
+        }
+
+        VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
+        ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
+        allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
+        
+        // adding buffer directly so that no update counts is forced
+        buffers.put(Type.InterleavedData.ordinal(), allData);
+        buffersList.add(allData);
+
+        for (int vert = 0; vert < getVertexCount(); vert++){
+            for (int i = 0; i < vbs.size(); i++){
+                VertexBuffer vb = vbs.get(i);
+                switch (vb.getFormat()){
+                    case Float:
+                        FloatBuffer fb = (FloatBuffer) vb.getData();
+                        for (int comp = 0; comp < vb.components; comp++){
+                            dataBuf.putFloat(fb.get());
+                        }
+                        break;
+                    case Byte:
+                    case UnsignedByte:
+                        ByteBuffer bb = (ByteBuffer) vb.getData();
+                        for (int comp = 0; comp < vb.components; comp++){
+                            dataBuf.put(bb.get());
+                        }
+                        break;
+                    case Half:
+                    case Short:
+                    case UnsignedShort:
+                        ShortBuffer sb = (ShortBuffer) vb.getData();
+                        for (int comp = 0; comp < vb.components; comp++){
+                            dataBuf.putShort(sb.get());
+                        }
+                        break;
+                    case Int:
+                    case UnsignedInt:
+                        IntBuffer ib = (IntBuffer) vb.getData();
+                        for (int comp = 0; comp < vb.components; comp++){
+                            dataBuf.putInt(ib.get());
+                        }
+                        break;
+                    case Double:
+                        DoubleBuffer db = (DoubleBuffer) vb.getData();
+                        for (int comp = 0; comp < vb.components; comp++){
+                            dataBuf.putDouble(db.get());
+                        }
+                        break;
+                }
+            }
+        }
+
+        int offset = 0;
+        for (VertexBuffer vb : vbs){
+            vb.setOffset(offset);
+            vb.setStride(stride);
+            
+            vb.updateData(null);
+            //vb.setupData(vb.usage, vb.components, vb.format, null);
+            offset += vb.componentsLength;
+        }
+    }
+
+    private int computeNumElements(int bufSize){
+        switch (mode){
+            case Triangles:
+                return bufSize / 3;
+            case TriangleFan:
+            case TriangleStrip:
+                return bufSize - 2;
+            case Points:
+                return bufSize;
+            case Lines:
+                return bufSize / 2;
+            case LineLoop:
+                return bufSize;
+            case LineStrip:
+                return bufSize - 1;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Update the {@link #getVertexCount() vertex} and 
+     * {@link #getTriangleCount() triangle} counts for this mesh
+     * based on the current data. This method should be called
+     * after the {@link Buffer#capacity() capacities} of the mesh's
+     * {@link VertexBuffer vertex buffers} has been altered.
+     * 
+     * @throws IllegalStateException If this mesh is in 
+     * {@link #setInterleaved() interleaved} format.
+     */
+    public void updateCounts(){
+        if (getBuffer(Type.InterleavedData) != null)
+            throw new IllegalStateException("Should update counts before interleave");
+
+        VertexBuffer pb = getBuffer(Type.Position);
+        VertexBuffer ib = getBuffer(Type.Index);
+        if (pb != null){
+            vertCount = pb.getData().capacity() / pb.getNumComponents();
+        }
+        if (ib != null){
+            elementCount = computeNumElements(ib.getData().capacity());
+        }else{
+            elementCount = computeNumElements(vertCount);
+        }
+    }
+
+    /**
+     * Returns the triangle count for the given LOD level.
+     * 
+     * @param lod The lod level to look up
+     * @return The triangle count for that LOD level
+     */
+    public int getTriangleCount(int lod){
+        if (lodLevels != null){
+            if (lod < 0)
+                throw new IllegalArgumentException("LOD level cannot be < 0");
+
+            if (lod >= lodLevels.length)
+                throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
+
+            return computeNumElements(lodLevels[lod].getData().capacity());
+        }else if (lod == 0){
+            return elementCount;
+        }else{
+            throw new IllegalArgumentException("There are no LOD levels on the mesh!");
+        }
+    }
+
+    /**
+     * Returns how many triangles or elements are on this Mesh.
+     * This value is only updated when {@link #updateCounts() } is called.
+     * If the mesh mode is not a triangle mode, then this returns the 
+     * number of elements/primitives, e.g. how many lines or how many points,
+     * instead of how many triangles.
+     * 
+     * @return how many triangles/elements are on this Mesh.
+     */
+    public int getTriangleCount(){
+        return elementCount;
+    }
+
+    /**
+     * Returns the number of vertices on this mesh.
+     * The value is computed based on the position buffer, which 
+     * must be set on all meshes.
+     * 
+     * @return Number of vertices on the mesh
+     */
+    public int getVertexCount(){
+        return vertCount;
+    }
+
+    /**
+     * Gets the triangle vertex positions at the given triangle index 
+     * and stores them into the v1, v2, v3 arguments.
+     * 
+     * @param index The index of the triangle. 
+     * Should be between 0 and {@link #getTriangleCount()}.
+     * 
+     * @param v1 Vector to contain first vertex position
+     * @param v2 Vector to contain second vertex position
+     * @param v3 Vector to contain third vertex position
+     */
+    public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){
+        VertexBuffer pb = getBuffer(Type.Position);
+        IndexBuffer ib = getIndicesAsList();
+        if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){
+            FloatBuffer fpb = (FloatBuffer) pb.getData();
+
+            // aquire triangle's vertex indices
+            int vertIndex = index * 3;
+            int vert1 = ib.get(vertIndex);
+            int vert2 = ib.get(vertIndex+1);
+            int vert3 = ib.get(vertIndex+2);
+
+            BufferUtils.populateFromBuffer(v1, fpb, vert1);
+            BufferUtils.populateFromBuffer(v2, fpb, vert2);
+            BufferUtils.populateFromBuffer(v3, fpb, vert3);
+        }else{
+            throw new UnsupportedOperationException("Position buffer not set or "
+                                                  + " has incompatible format");
+        }
+    }
+    
+    /**
+     * Gets the triangle vertex positions at the given triangle index 
+     * and stores them into the {@link Triangle} argument.
+     * Also sets the triangle index to the <code>index</code> argument.
+     * 
+     * @param index The index of the triangle. 
+     * Should be between 0 and {@link #getTriangleCount()}.
+     * 
+     * @param tri The triangle to store the positions in
+     */
+    public void getTriangle(int index, Triangle tri){
+        getTriangle(index, tri.get1(), tri.get2(), tri.get3());
+        tri.setIndex(index);
+        tri.setNormal(null);
+    }
+
+    /**
+     * Gets the triangle vertex indices at the given triangle index 
+     * and stores them into the given int array.
+     * 
+     * @param index The index of the triangle. 
+     * Should be between 0 and {@link #getTriangleCount()}.
+     * 
+     * @param indices Indices of the triangle's vertices
+     */
+    public void getTriangle(int index, int[] indices){
+        IndexBuffer ib = getIndicesAsList();
+
+        // acquire triangle's vertex indices
+        int vertIndex = index * 3;
+        indices[0] = ib.get(vertIndex);
+        indices[1] = ib.get(vertIndex+1);
+        indices[2] = ib.get(vertIndex+2);
+    }
+
+    /**
+     * Returns the mesh's VAO ID. Internal use only.
+     */
+    public int getId(){
+        return vertexArrayID;
+    }
+
+    /**
+     * Sets the mesh's VAO ID. Internal use only.
+     */
+    public void setId(int id){
+        if (vertexArrayID != -1)
+            throw new IllegalStateException("ID has already been set.");
+        
+        vertexArrayID = id;
+    }
+
+    /**
+     * Generates a collision tree for the mesh.
+     * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, 
+     * com.jme3.math.Matrix4f, 
+     * com.jme3.bounding.BoundingVolume, 
+     * com.jme3.collision.CollisionResults) }.
+     */
+    public void createCollisionData(){
+        BIHTree tree = new BIHTree(this);
+        tree.construct();
+        collisionTree = tree;
+    }
+
+    /**
+     * Handles collision detection, internal use only.
+     * User code should only use collideWith() on scene
+     * graph elements such as {@link Spatial}s.
+     */
+    public int collideWith(Collidable other, 
+                           Matrix4f worldMatrix,
+                           BoundingVolume worldBound,
+                           CollisionResults results){
+
+        if (collisionTree == null){
+            createCollisionData();
+        }
+        
+        return collisionTree.collideWith(other, worldMatrix, worldBound, results);
+    }
+
+    /**
+     * Set a floating point {@link VertexBuffer} on the mesh. 
+     * 
+     * @param type The type of {@link VertexBuffer}, 
+     * e.g. {@link Type#Position}, {@link Type#Normal}, etc.
+     * 
+     * @param components Number of components on the vertex buffer, should
+     * be between 1 and 4.
+     * 
+     * @param buf The floating point data to contain
+     */
+    public void setBuffer(Type type, int components, FloatBuffer buf) {
+//        VertexBuffer vb = buffers.get(type);
+        VertexBuffer vb = buffers.get(type.ordinal());
+        if (vb == null){
+            if (buf == null)
+                return;
+
+            vb = new VertexBuffer(type);
+            vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+//            buffers.put(type, vb);
+            buffers.put(type.ordinal(), vb);
+            buffersList.add(vb);
+        }else{
+            vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+        }
+        updateCounts();
+    }
+
+    public void setBuffer(Type type, int components, float[] buf){
+        setBuffer(type, components, BufferUtils.createFloatBuffer(buf));
+    }
+
+    public void setBuffer(Type type, int components, IntBuffer buf) {
+        VertexBuffer vb = buffers.get(type.ordinal());
+        if (vb == null){
+            vb = new VertexBuffer(type);
+            vb.setupData(Usage.Dynamic, components, Format.UnsignedInt, buf);
+            buffers.put(type.ordinal(), vb);
+            buffersList.add(vb);
+            updateCounts();
+        }
+    }
+
+    public void setBuffer(Type type, int components, int[] buf){
+        setBuffer(type, components, BufferUtils.createIntBuffer(buf));
+    }
+
+    public void setBuffer(Type type, int components, ShortBuffer buf) {
+        VertexBuffer vb = buffers.get(type.ordinal());
+        if (vb == null){
+            vb = new VertexBuffer(type);
+            vb.setupData(Usage.Dynamic, components, Format.UnsignedShort, buf);
+            buffers.put(type.ordinal(), vb);
+            buffersList.add(vb);
+            updateCounts();
+        }
+    }
+
+    public void setBuffer(Type type, int components, byte[] buf){
+        setBuffer(type, components, BufferUtils.createByteBuffer(buf));
+    }
+
+    public void setBuffer(Type type, int components, ByteBuffer buf) {
+        VertexBuffer vb = buffers.get(type.ordinal());
+        if (vb == null){
+            vb = new VertexBuffer(type);
+            vb.setupData(Usage.Dynamic, components, Format.UnsignedByte, buf);
+            buffers.put(type.ordinal(), vb);
+            buffersList.add(vb);
+            updateCounts();
+        }
+    }
+
+    public void setBuffer(VertexBuffer vb){
+        if (buffers.containsKey(vb.getBufferType().ordinal()))
+            throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
+
+        buffers.put(vb.getBufferType().ordinal(), vb);
+        buffersList.add(vb);
+        updateCounts();
+    }
+
+    /**
+     * Clears or unsets the {@link VertexBuffer} set on this mesh
+     * with the given type.
+     * Does nothing if the vertex buffer type is not set initially
+     * 
+     * @param type The type to remove
+     */
+    public void clearBuffer(VertexBuffer.Type type){
+        VertexBuffer vb = buffers.remove(type.ordinal());
+        if (vb != null){
+            buffersList.remove(vb);
+            updateCounts();
+        }
+    }
+
+    public void setBuffer(Type type, int components, short[] buf){
+        setBuffer(type, components, BufferUtils.createShortBuffer(buf));
+    }
+
+    /**
+     * Get the {@link VertexBuffer} stored on this mesh with the given
+     * type.
+     * 
+     * @param type The type of VertexBuffer
+     * @return the VertexBuffer data, or null if not set 
+     */
+    public VertexBuffer getBuffer(Type type){
+        return buffers.get(type.ordinal());
+    }
+
+    /**
+     * Get the {@link VertexBuffer} data stored on this mesh in float
+     * format.
+     * 
+     * @param type The type of VertexBuffer
+     * @return the VertexBuffer data, or null if not set
+     */
+    public FloatBuffer getFloatBuffer(Type type) {
+        VertexBuffer vb = getBuffer(type);
+        if (vb == null)
+            return null;
+
+        return (FloatBuffer) vb.getData();
+    }
+    
+    /**
+     * Get the {@link VertexBuffer} data stored on this mesh in short
+     * format.
+     * 
+     * @param type The type of VertexBuffer
+     * @return the VertexBuffer data, or null if not set
+     */
+    public ShortBuffer getShortBuffer(Type type) {
+        VertexBuffer vb = getBuffer(type);
+        if (vb == null)
+            return null;
+
+        return (ShortBuffer) vb.getData();
+    }
+
+    /**
+     * Acquires an index buffer that will read the vertices on the mesh
+     * as a list.
+     * 
+     * @return A virtual or wrapped index buffer to read the data as a list
+     */
+    public IndexBuffer getIndicesAsList(){
+        if (mode == Mode.Hybrid)
+            throw new UnsupportedOperationException("Hybrid mode not supported");
+        
+        IndexBuffer ib = getIndexBuffer();
+        if (ib != null){
+            if (mode.isListMode()){
+                // already in list mode
+                return ib; 
+            }else{
+                // not in list mode but it does have an index buffer
+                // wrap it so the data is converted to list format
+                return new WrappedIndexBuffer(this);
+            }
+        }else{
+            // return a virtual index buffer that will supply
+            // "fake" indices in list format
+            return new VirtualIndexBuffer(vertCount, mode);
+        }
+    }
+    
+    /**
+     * Get the index buffer for this mesh. 
+     * Will return <code>null</code> if no index buffer is set.
+     * 
+     * @return The index buffer of this mesh.
+     * 
+     * @see Type#Index
+     */
+    public IndexBuffer getIndexBuffer() {
+        VertexBuffer vb = getBuffer(Type.Index);
+        if (vb == null)
+            return null;
+        
+        Buffer buf = vb.getData();
+        if (buf instanceof ByteBuffer) {
+            return new IndexByteBuffer((ByteBuffer) buf);
+        } else if (buf instanceof ShortBuffer) {
+            return new IndexShortBuffer((ShortBuffer) buf);
+        } else if (buf instanceof IntBuffer) {
+            return new IndexIntBuffer((IntBuffer) buf);
+        } else {
+            throw new UnsupportedOperationException("Index buffer type unsupported: "+ buf.getClass());
+        }
+    }
+
+    /**
+     * Extracts the vertex attributes from the given mesh into
+     * this mesh, by using this mesh's {@link #getIndexBuffer() index buffer}
+     * to index into the attributes of the other mesh.
+     * Note that this will also change this mesh's index buffer so that
+     * the references to the vertex data match the new indices.
+     * 
+     * @param other The mesh to extract the vertex data from
+     */
+    public void extractVertexData(Mesh other) {
+        // Determine the number of unique vertices need to
+        // be created. Also determine the mappings
+        // between old indices to new indices (since we avoid duplicating
+        // vertices, this is a map and not an array).
+        VertexBuffer oldIdxBuf = getBuffer(Type.Index);
+        IndexBuffer indexBuf = getIndexBuffer();
+        int numIndices = indexBuf.size();
+
+        IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
+        ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
+        int newIndex = 0;
+
+        for (int i = 0; i < numIndices; i++) {
+            int oldIndex = indexBuf.get(i);
+
+            if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
+                // this vertex has not been added, so allocate a 
+                // new index for it and add it to the map
+                oldIndicesToNewIndices.put(oldIndex, newIndex);
+                newIndicesToOldIndices.add(oldIndex);
+
+                // increment to have the next index
+                newIndex++;
+            }
+        }
+
+        // Number of unique verts to be created now available
+        int newNumVerts = newIndicesToOldIndices.size();
+
+        if (newIndex != newNumVerts) {
+            throw new AssertionError();
+        }
+
+        // Create the new index buffer. 
+        // Do not overwrite the old one because we might be able to 
+        // convert from int index buffer to short index buffer
+        IndexBuffer newIndexBuf;
+        if (newNumVerts >= 65536) {
+            newIndexBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(numIndices));
+        } else {
+            newIndexBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(numIndices));
+        }
+
+        for (int i = 0; i < numIndices; i++) {
+            // Map the old indices to the new indices
+            int oldIndex = indexBuf.get(i);
+            newIndex = oldIndicesToNewIndices.get(oldIndex);
+
+            newIndexBuf.put(i, newIndex);
+        }
+        
+        VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
+        newIdxBuf.setupData(oldIdxBuf.getUsage(), 
+                            oldIdxBuf.getNumComponents(), 
+                            newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
+                            newIndexBuf.getBuffer());
+        clearBuffer(Type.Index);
+        setBuffer(newIdxBuf);
+
+        // Now, create the vertex buffers
+        SafeArrayList<VertexBuffer> oldVertexData = other.getBufferList();
+        for (VertexBuffer oldVb : oldVertexData) {
+            if (oldVb.getBufferType() == VertexBuffer.Type.Index) {
+                // ignore the index buffer
+                continue;
+            }
+
+            // Create a new vertex buffer with similar configuration, but
+            // with the capacity of number of unique vertices
+            Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts);
+
+            VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
+            newVb.setNormalized(oldVb.isNormalized());
+            newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer);
+
+            // Copy the vertex data from the old buffer into the new buffer
+            for (int i = 0; i < newNumVerts; i++) {
+                int oldIndex = newIndicesToOldIndices.get(i);
+
+                // Copy the vertex attribute from the old index
+                // to the new index
+                oldVb.copyElement(oldIndex, newVb, i);
+            }
+            
+            // Set the buffer on the mesh
+            clearBuffer(newVb.getBufferType());
+            setBuffer(newVb);
+        }
+        
+        // Copy max weights per vertex as well
+        setMaxNumWeights(other.getMaxNumWeights());
+        
+        // The data has been copied over, update informations
+        updateCounts();
+        updateBound();
+    }
+    
+    /**
+     * Scales the texture coordinate buffer on this mesh by the given
+     * scale factor. 
+     * <p>
+     * Note that values above 1 will cause the 
+     * texture to tile, while values below 1 will cause the texture 
+     * to stretch.
+     * </p>
+     * 
+     * @param scaleFactor The scale factor to scale by. Every texture
+     * coordinate is multiplied by this vector to get the result.
+     * 
+     * @throws IllegalStateException If there's no texture coordinate
+     * buffer on the mesh
+     * @throws UnsupportedOperationException If the texture coordinate
+     * buffer is not in 2D float format.
+     */
+    public void scaleTextureCoordinates(Vector2f scaleFactor){
+        VertexBuffer tc = getBuffer(Type.TexCoord);
+        if (tc == null)
+            throw new IllegalStateException("The mesh has no texture coordinates");
+
+        if (tc.getFormat() != VertexBuffer.Format.Float)
+            throw new UnsupportedOperationException("Only float texture coord format is supported");
+
+        if (tc.getNumComponents() != 2)
+            throw new UnsupportedOperationException("Only 2D texture coords are supported");
+
+        FloatBuffer fb = (FloatBuffer) tc.getData();
+        fb.clear();
+        for (int i = 0; i < fb.capacity() / 2; i++){
+            float x = fb.get();
+            float y = fb.get();
+            fb.position(fb.position()-2);
+            x *= scaleFactor.getX();
+            y *= scaleFactor.getY();
+            fb.put(x).put(y);
+        }
+        fb.clear();
+        tc.updateData(fb);
+    }
+
+    /**
+     * Updates the bounding volume of this mesh. 
+     * The method does nothing if the mesh has no {@link Type#Position} buffer.
+     * It is expected that the position buffer is a float buffer with 3 components.
+     */
+    public void updateBound(){
+        VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position);
+        if (meshBound != null && posBuf != null){
+            meshBound.computeFromPoints((FloatBuffer)posBuf.getData());
+        }
+    }
+
+    /**
+     * Returns the {@link BoundingVolume} of this Mesh.
+     * By default the bounding volume is a {@link BoundingBox}.
+     * 
+     * @return the bounding volume of this mesh
+     */
+    public BoundingVolume getBound() {
+        return meshBound;
+    }
+
+    /**
+     * Sets the {@link BoundingVolume} for this Mesh.
+     * The bounding volume is recomputed by calling {@link #updateBound() }.
+     * 
+     * @param modelBound The model bound to set
+     */
+    public void setBound(BoundingVolume modelBound) {
+        meshBound = modelBound;
+    }
+
+    /**
+     * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
+     * The integer key for the map is the {@link Enum#ordinal() ordinal}
+     * of the vertex buffer's {@link Type}.
+     * Note that the returned map is a reference to the map used internally, 
+     * modifying it will cause undefined results.
+     * 
+     * @return map of vertex buffers on this mesh.
+     */
+    public IntMap<VertexBuffer> getBuffers(){
+        return buffers;
+    }
+    
+    /**
+     * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh.
+     * Using a list instead an IntMap via the {@link #getBuffers() } method is
+     * better for iteration as there's no need to create an iterator instance.
+     * Note that the returned list is a reference to the list used internally,
+     * modifying it will cause undefined results.
+     * 
+     * @return list of vertex buffers on this mesh.
+     */
+    public SafeArrayList<VertexBuffer> getBufferList(){
+        return buffersList;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule out = ex.getCapsule(this);
+
+//        HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
+//        for (Entry<VertexBuffer> buf : buffers){
+//            if (buf.getValue() != null)
+//                map.put(buf.getKey()+"a", buf.getValue());
+//        }
+//        out.writeStringSavableMap(map, "buffers", null);
+
+        out.write(meshBound, "modelBound", null);
+        out.write(vertCount, "vertCount", -1);
+        out.write(elementCount, "elementCount", -1);
+        out.write(maxNumWeights, "max_num_weights", -1);
+        out.write(mode, "mode", Mode.Triangles);
+        out.write(collisionTree, "collisionTree", null);
+        out.write(elementLengths, "elementLengths", null);
+        out.write(modeStart, "modeStart", null);
+        out.write(pointSize, "pointSize", 1f);
+
+        out.writeIntSavableMap(buffers, "buffers", null);
+        out.write(lodLevels, "lodLevels", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        meshBound = (BoundingVolume) in.readSavable("modelBound", null);
+        vertCount = in.readInt("vertCount", -1);
+        elementCount = in.readInt("elementCount", -1);
+        maxNumWeights = in.readInt("max_num_weights", -1);
+        mode = in.readEnum("mode", Mode.class, Mode.Triangles);
+        elementLengths = in.readIntArray("elementLengths", null);
+        modeStart = in.readIntArray("modeStart", null);
+        collisionTree = (BIHTree) in.readSavable("collisionTree", null);
+        elementLengths = in.readIntArray("elementLengths", null);
+        modeStart = in.readIntArray("modeStart", null);
+        pointSize = in.readFloat("pointSize", 1f);
+
+//        in.readStringSavableMap("buffers", null);
+        buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
+        for (Entry<VertexBuffer> entry : buffers){
+            buffersList.add(entry.getValue());
+        }
+        
+        Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
+        if (lodLevelsSavable != null) {
+            lodLevels = new VertexBuffer[lodLevelsSavable.length];
+            System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/Node.java b/engine/src/core/com/jme3/scene/Node.java
new file mode 100644
index 0000000..bb7534a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Node.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.material.Material;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * <code>Node</code> defines an internal node of a scene graph. The internal
+ * node maintains a collection of children and handles merging said children
+ * into a single bound to allow for very fast culling of multiple nodes. Node
+ * allows for any number of children to be attached.
+ * 
+ * @author Mark Powell
+ * @author Gregg Patton
+ * @author Joshua Slack
+ */
+public class Node extends Spatial implements Savable {
+
+    private static final Logger logger = Logger.getLogger(Node.class.getName());
+
+
+    /** 
+     * This node's children.
+     */
+    protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Node() {
+    }
+
+    /**
+     * Constructor instantiates a new <code>Node</code> with a default empty
+     * list for containing children.
+     * 
+     * @param name
+     *            the name of the scene element. This is required for
+     *            identification and comparision purposes.
+     */
+    public Node(String name) {
+        super(name);
+    }
+
+    /**
+     * 
+     * <code>getQuantity</code> returns the number of children this node
+     * maintains.
+     * 
+     * @return the number of children this node maintains.
+     */
+    public int getQuantity() {
+        return children.size();        
+    }
+
+    @Override
+    protected void setTransformRefresh(){
+        super.setTransformRefresh();
+        for (Spatial child : children.getArray()){
+            if ((child.refreshFlags & RF_TRANSFORM) != 0)
+                continue;
+
+            child.setTransformRefresh();
+        }
+    }
+
+    @Override
+    protected void setLightListRefresh(){
+        super.setLightListRefresh();
+        for (Spatial child : children.getArray()){
+            if ((child.refreshFlags & RF_LIGHTLIST) != 0)
+                continue;
+
+            child.setLightListRefresh();
+        }
+    }
+
+    @Override
+    protected void updateWorldBound(){
+        super.updateWorldBound();
+        
+        // for a node, the world bound is a combination of all it's children
+        // bounds
+        BoundingVolume resultBound = null;
+        for (Spatial child : children.getArray()) {
+            // child bound is assumed to be updated
+            assert (child.refreshFlags & RF_BOUND) == 0;
+            if (resultBound != null) {
+                // merge current world bound with child world bound
+                resultBound.mergeLocal(child.getWorldBound());
+            } else {
+                // set world bound to first non-null child world bound
+                if (child.getWorldBound() != null) {
+                    resultBound = child.getWorldBound().clone(this.worldBound);
+                }
+            }
+        }
+        this.worldBound = resultBound;
+    }
+
+    @Override
+    public void updateLogicalState(float tpf){
+        super.updateLogicalState(tpf);
+
+        if (children.isEmpty()) {
+            return;
+        }
+        
+        for (Spatial child : children.getArray()) {
+            child.updateLogicalState(tpf);
+        }
+    }
+
+    @Override
+    public void updateGeometricState(){
+        if ((refreshFlags & RF_LIGHTLIST) != 0){
+            updateWorldLightList();
+        }
+
+        if ((refreshFlags & RF_TRANSFORM) != 0){
+            // combine with parent transforms- same for all spatial
+            // subclasses.
+            updateWorldTransforms();
+        }
+
+        if (!children.isEmpty()) {
+            // the important part- make sure child geometric state is refreshed
+            // first before updating own world bound. This saves
+            // a round-trip later on.
+            // NOTE 9/19/09
+            // Although it does save a round trip,
+            for (Spatial child : children.getArray()) {
+                child.updateGeometricState();
+            }
+        }            
+
+        if ((refreshFlags & RF_BOUND) != 0){
+            updateWorldBound();
+        }
+
+        assert refreshFlags == 0;
+    }
+
+    /**
+     * <code>getTriangleCount</code> returns the number of triangles contained
+     * in all sub-branches of this node that contain geometry.
+     * 
+     * @return the triangle count of this branch.
+     */
+    @Override
+    public int getTriangleCount() {
+        int count = 0;
+        if(children != null) {
+            for(int i = 0; i < children.size(); i++) {
+                count += children.get(i).getTriangleCount();
+            }
+        }
+
+        return count;
+    }
+    
+    /**
+     * <code>getVertexCount</code> returns the number of vertices contained
+     * in all sub-branches of this node that contain geometry.
+     * 
+     * @return the vertex count of this branch.
+     */
+    @Override
+    public int getVertexCount() {
+        int count = 0;
+        if(children != null) {
+            for(int i = 0; i < children.size(); i++) {
+               count += children.get(i).getVertexCount();
+            }
+        }
+
+        return count;
+    }
+
+    /**
+     * <code>attachChild</code> attaches a child to this node. This node
+     * becomes the child's parent. The current number of children maintained is
+     * returned.
+     * <br>
+     * If the child already had a parent it is detached from that former parent.
+     * 
+     * @param child
+     *            the child to attach to this node.
+     * @return the number of children maintained by this node.
+     * @throws NullPointerException If child is null.
+     */
+    public int attachChild(Spatial child) {
+        if (child == null)
+            throw new NullPointerException();
+
+        if (child.getParent() != this && child != this) {
+            if (child.getParent() != null) {
+                child.getParent().detachChild(child);
+            }
+            child.setParent(this);
+            children.add(child);
+
+            // XXX: Not entirely correct? Forces bound update up the
+            // tree stemming from the attached child. Also forces
+            // transform update down the tree-
+            child.setTransformRefresh();
+            child.setLightListRefresh();
+            if (logger.isLoggable(Level.INFO)) {
+                logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+                        new Object[]{child.getName(), getName()});
+            }
+        }
+        
+        return children.size();
+    }
+    
+    /**
+     * 
+     * <code>attachChildAt</code> attaches a child to this node at an index. This node
+     * becomes the child's parent. The current number of children maintained is
+     * returned.
+     * <br>
+     * If the child already had a parent it is detached from that former parent.
+     * 
+     * @param child
+     *            the child to attach to this node.
+     * @return the number of children maintained by this node.
+     * @throws NullPointerException if child is null.
+     */
+    public int attachChildAt(Spatial child, int index) {
+        if (child == null)
+            throw new NullPointerException();
+
+        if (child.getParent() != this && child != this) {
+            if (child.getParent() != null) {
+                child.getParent().detachChild(child);
+            }
+            child.setParent(this);
+            children.add(index, child);
+            child.setTransformRefresh();
+            child.setLightListRefresh();
+            if (logger.isLoggable(Level.INFO)) {
+                logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+                        new Object[]{child.getName(), getName()});
+            }
+        }
+        
+        return children.size();
+    }
+
+    /**
+     * <code>detachChild</code> removes a given child from the node's list.
+     * This child will no longer be maintained.
+     * 
+     * @param child
+     *            the child to remove.
+     * @return the index the child was at. -1 if the child was not in the list.
+     */
+    public int detachChild(Spatial child) {
+        if (child == null)
+            throw new NullPointerException();
+
+        if (child.getParent() == this) {
+            int index = children.indexOf(child);
+            if (index != -1) {
+                detachChildAt(index);
+            }
+            return index;
+        } 
+            
+        return -1;        
+    }
+
+    /**
+     * <code>detachChild</code> removes a given child from the node's list.
+     * This child will no longe be maintained. Only the first child with a
+     * matching name is removed.
+     * 
+     * @param childName
+     *            the child to remove.
+     * @return the index the child was at. -1 if the child was not in the list.
+     */
+    public int detachChildNamed(String childName) {
+        if (childName == null)
+            throw new NullPointerException();
+
+        for (int x = 0, max = children.size(); x < max; x++) {
+            Spatial child =  children.get(x);
+            if (childName.equals(child.getName())) {
+                detachChildAt( x );
+                return x;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * 
+     * <code>detachChildAt</code> removes a child at a given index. That child
+     * is returned for saving purposes.
+     * 
+     * @param index
+     *            the index of the child to be removed.
+     * @return the child at the supplied index.
+     */
+    public Spatial detachChildAt(int index) {
+        Spatial child =  children.remove(index);
+        if ( child != null ) {
+            child.setParent( null );
+            logger.log(Level.INFO, "{0}: Child removed.", this.toString());
+
+            // since a child with a bound was detached;
+            // our own bound will probably change.
+            setBoundRefresh();
+
+            // our world transform no longer influences the child.
+            // XXX: Not neccessary? Since child will have transform updated
+            // when attached anyway.
+            child.setTransformRefresh();
+            // lights are also inherited from parent
+            child.setLightListRefresh();
+        }
+        return child;
+    }
+
+    /**
+     * 
+     * <code>detachAllChildren</code> removes all children attached to this
+     * node.
+     */
+    public void detachAllChildren() {
+        for ( int i = children.size() - 1; i >= 0; i-- ) {
+            detachChildAt(i);
+        }
+        logger.log(Level.INFO, "{0}: All children removed.", this.toString());
+    }
+
+    /**
+     * <code>getChildIndex</code> returns the index of the given spatial
+     * in this node's list of children.
+     * @param sp
+     *          The spatial to look up
+     * @return 
+     *          The index of the spatial in the node's children, or -1
+     *          if the spatial is not attached to this node
+     */
+    public int getChildIndex(Spatial sp) {
+        return children.indexOf(sp);
+    }
+
+    /**
+     * More efficient than e.g detaching and attaching as no updates are needed.
+     * 
+     * @param index1 The index of the first child to swap
+     * @param index2 The index of the second child to swap
+     */
+    public void swapChildren(int index1, int index2) {
+        Spatial c2 =  children.get(index2);
+        Spatial c1 =  children.remove(index1);
+        children.add(index1, c2);
+        children.remove(index2);
+        children.add(index2, c1);
+    }
+
+    /**
+     * 
+     * <code>getChild</code> returns a child at a given index.
+     * 
+     * @param i
+     *            the index to retrieve the child from.
+     * @return the child at a specified index.
+     */
+    public Spatial getChild(int i) {
+        return children.get(i);
+    }
+
+    /**
+     * <code>getChild</code> returns the first child found with exactly the
+     * given name (case sensitive.)
+     * 
+     * @param name
+     *            the name of the child to retrieve. If null, we'll return null.
+     * @return the child if found, or null.
+     */
+    public Spatial getChild(String name) {
+        if (name == null) 
+            return null;
+
+        for (Spatial child : children.getArray()) {
+            if (name.equals(child.getName())) {
+                return child;
+            } else if(child instanceof Node) {
+                Spatial out = ((Node)child).getChild(name);
+                if(out != null) {
+                    return out;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * determines if the provided Spatial is contained in the children list of
+     * this node.
+     * 
+     * @param spat
+     *            the child object to look for.
+     * @return true if the object is contained, false otherwise.
+     */
+    public boolean hasChild(Spatial spat) {
+        if (children.contains(spat))
+            return true;
+
+        for (Spatial child : children.getArray()) {
+            if (child instanceof Node && ((Node) child).hasChild(spat))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns all children to this node. Note that modifying that given
+     * list is not allowed.
+     *
+     * @return a list containing all children to this node
+     */
+    public List<Spatial> getChildren() {
+        return children;
+    }
+
+    @Override
+    public void setMaterial(Material mat){
+        for (int i = 0; i < children.size(); i++){
+            children.get(i).setMaterial(mat);
+        }
+    }
+
+    @Override
+    public void setLodLevel(int lod){
+        super.setLodLevel(lod);
+        for (Spatial child : children.getArray()) {
+            child.setLodLevel(lod);
+        }
+    }
+
+    public int collideWith(Collidable other, CollisionResults results){
+        int total = 0;
+        for (Spatial child : children.getArray()){
+            total += child.collideWith(other, results);
+        }
+        return total;
+    }
+
+
+     /**
+     * Returns flat list of Spatials implementing the specified class AND
+     * with name matching the specified pattern.
+     * </P> <P>
+     * Note that we are <i>matching</i> the pattern, therefore the pattern
+     * must match the entire pattern (i.e. it behaves as if it is sandwiched
+     * between "^" and "$").
+     * You can set regex modes, like case insensitivity, by using the (?X)
+     * or (?X:Y) constructs.
+     * </P> <P>
+     * By design, it is always safe to code loops like:<CODE><PRE>
+     *     for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
+     * </PRE></CODE>
+     * </P> <P>
+     * "Descendants" does not include self, per the definition of the word.
+     * To test for descendants AND self, you must do a
+     * <code>node.matches(aClass, aRegex)</code> +
+     * <code>node.descendantMatches(aClass, aRegex)</code>.
+     * <P>
+     *
+     * @param spatialSubclass Subclass which matching Spatials must implement.
+     *                        Null causes all Spatials to qualify.
+     * @param nameRegex  Regular expression to match Spatial name against.
+     *                        Null causes all Names to qualify.
+     * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
+     *
+     * @see java.util.regex.Pattern
+     * @see Spatial#matches(java.lang.Class, java.lang.String) 
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends Spatial>List<T> descendantMatches(
+            Class<T> spatialSubclass, String nameRegex) {
+        List<T> newList = new ArrayList<T>();
+        if (getQuantity() < 1) return newList;
+        for (Spatial child : getChildren()) {
+            if (child.matches(spatialSubclass, nameRegex))
+                newList.add((T)child);
+            if (child instanceof Node)
+                newList.addAll(((Node) child).descendantMatches(
+                        spatialSubclass, nameRegex));
+        }
+        return newList;
+    }
+
+    /**
+     * Convenience wrapper.
+     *
+     * @see #descendantMatches(java.lang.Class, java.lang.String) 
+     */
+    public <T extends Spatial>List<T> descendantMatches(
+            Class<T> spatialSubclass) {
+        return descendantMatches(spatialSubclass, null);
+    }
+
+    /**
+     * Convenience wrapper.
+     *
+     * @see #descendantMatches(java.lang.Class, java.lang.String) 
+     */
+    public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
+        return descendantMatches(null, nameRegex);
+    }
+
+    @Override
+    public Node clone(boolean cloneMaterials){
+        Node nodeClone = (Node) super.clone(cloneMaterials);
+//        nodeClone.children = new ArrayList<Spatial>();
+//        for (Spatial child : children){
+//            Spatial childClone = child.clone();
+//            childClone.parent = nodeClone;
+//            nodeClone.children.add(childClone);
+//        }
+        return nodeClone;
+    }
+
+    @Override
+    public Spatial deepClone(){
+        Node nodeClone = (Node) super.clone();
+        nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+        for (Spatial child : children){
+            Spatial childClone = child.deepClone();
+            childClone.parent = nodeClone;
+            nodeClone.children.add(childClone);
+        }
+        return nodeClone;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        // XXX: Load children before loading itself!!
+        // This prevents empty children list if controls query
+        // it in Control.setSpatial().
+        
+        children = new SafeArrayList( Spatial.class, 
+                                      e.getCapsule(this).readSavableArrayList("children", null) );
+
+        // go through children and set parent to this node
+        if (children != null) {
+            for (Spatial child : children.getArray()) {
+                child.parent = this;
+            }
+        }
+        
+        super.read(e);
+    }
+
+    @Override
+    public void setModelBound(BoundingVolume modelBound) {
+        if(children != null) {
+            for (Spatial child : children.getArray()) {
+                child.setModelBound(modelBound != null ? modelBound.clone(null) : null);
+            }
+        }
+    }
+
+    @Override
+    public void updateModelBound() {
+        if(children != null) {
+            for (Spatial child : children.getArray()) {
+                child.updateModelBound();
+            }
+        }
+    }
+    
+    @Override
+    public void depthFirstTraversal(SceneGraphVisitor visitor) {
+        for (Spatial child : children.getArray()) {
+            child.depthFirstTraversal(visitor);
+        }
+        visitor.visit(this);
+    }
+    
+    @Override
+    protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+        queue.addAll(children);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitor.java b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
new file mode 100644
index 0000000..35cc115
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
@@ -0,0 +1,16 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. 
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public interface SceneGraphVisitor {
+    /**
+     * Called when a spatial is visited in the scene graph.
+     * 
+     * @param spatial The visited spatial
+     */
+    public void visit(Spatial spatial);
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
new file mode 100644
index 0000000..b91a8fb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
@@ -0,0 +1,35 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. The adapter version of the interface simply separates 
+ * between the {@link Geometry geometries} and the {@link Node nodes} by
+ * supplying visit methods that take them.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public class SceneGraphVisitorAdapter implements SceneGraphVisitor {
+    
+    /**
+     * Called when a {@link Geometry} is visited.
+     * 
+     * @param geom The visited geometry
+     */
+    public void visit(Geometry geom) {}
+    
+    /**
+     * Called when a {@link visit} is visited.
+     * 
+     * @param geom The visited node
+     */
+    public void visit(Node geom) {}
+
+    @Override
+    public final void visit(Spatial spatial) {
+        if (spatial instanceof Geometry) {
+            visit((Geometry)spatial);
+        } else {
+            visit((Node)spatial);
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/SimpleBatchNode.java b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
new file mode 100644
index 0000000..0f1ed29
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
@@ -0,0 +1,56 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.math.Transform;
+
+/**
+ * 
+ * SimpleBatchNode  comes with some restrictions, but can yield better performances.
+ * Geometries to be batched has to be attached directly to the BatchNode
+ * You can't attach a Node to a SimpleBatchNode
+ * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
+ * @see BatchNode
+ * @author Nehon
+ */
+public class SimpleBatchNode extends BatchNode {
+
+    public SimpleBatchNode() {
+        super();
+    }
+
+    public SimpleBatchNode(String name) {
+        super(name);
+    }
+
+    @Override
+    public int attachChild(Spatial child) {
+
+        if (!(child instanceof Geometry)) {
+            throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure");
+        }
+
+        return super.attachChild(child);
+    }
+
+    @Override
+    protected void setTransformRefresh() {
+
+        refreshFlags |= RF_TRANSFORM;
+        setBoundRefresh();
+        for (Batch batch : batches.values()) {
+            batch.geometry.setTransformRefresh();
+        }
+    }
+    
+     protected Transform getTransforms(Geometry geom){
+        return geom.getLocalTransform();
+    }
+
+    @Override
+    public void batch() {
+        doBatch();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java
new file mode 100644
index 0000000..3785d6e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Spatial.java
@@ -0,0 +1,1478 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.export.*;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.control.Control;
+import com.jme3.util.SafeArrayList;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * <code>Spatial</code> defines the base class for scene graph nodes. It
+ * maintains a link to a parent, it's local transforms and the world's
+ * transforms. All other scene graph elements, such as {@link Node} and
+ * {@link Geometry} are subclasses of <code>Spatial</code>.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Revision: 4075 $, $Data$
+ */
+public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
+
+    private static final Logger logger = Logger.getLogger(Spatial.class.getName());
+
+    /**
+     * Specifies how frustum culling should be handled by 
+     * this spatial.
+     */
+    public enum CullHint {
+
+        /** 
+         * Do whatever our parent does. If no parent, default to {@link #Dynamic}.
+         */
+        Inherit,
+        /**
+         * Do not draw if we are not at least partially within the view frustum
+         * of the camera. This is determined via the defined
+         * Camera planes whether or not this Spatial should be culled.
+         */
+        Dynamic,
+        /** 
+         * Always cull this from the view, throwing away this object
+         * and any children from rendering commands.
+         */
+        Always,
+        /**
+         * Never cull this from view, always draw it. 
+         * Note that we will still get culled if our parent is culled.
+         */
+        Never;
+    }
+
+    /**
+     * Specifies if this spatial should be batched
+     */
+    public enum BatchHint {
+
+        /** 
+         * Do whatever our parent does. If no parent, default to {@link #Always}.
+         */
+        Inherit,
+        /** 
+         * This spatial will always be batched when attached to a BatchNode.
+         */
+        Always,
+        /** 
+         * This spatial will never be batched when attached to a BatchNode.
+         */
+        Never;
+    }
+    /**
+     * Refresh flag types
+     */
+    protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
+            RF_BOUND = 0x02,
+            RF_LIGHTLIST = 0x04; // changes in light lists          
+    protected CullHint cullHint = CullHint.Inherit;
+    protected BatchHint batchHint = BatchHint.Inherit;
+    /** 
+     * Spatial's bounding volume relative to the world.
+     */
+    protected BoundingVolume worldBound;
+    /**
+     * LightList
+     */
+    protected LightList localLights;
+    protected transient LightList worldLights;
+    /** 
+     * This spatial's name.
+     */
+    protected String name;
+    // scale values
+    protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
+    protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
+    protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
+    public transient float queueDistance = Float.NEGATIVE_INFINITY;
+    protected Transform localTransform;
+    protected Transform worldTransform;
+    protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class);
+    protected HashMap<String, Savable> userData = null;
+    /**
+     * Used for smart asset caching
+     * 
+     * @see AssetKey#useSmartCache() 
+     */
+    protected AssetKey key;
+    /** 
+     * Spatial's parent, or null if it has none.
+     */
+    protected transient Node parent;
+    /**
+     * Refresh flags. Indicate what data of the spatial need to be
+     * updated to reflect the correct state.
+     */
+    protected transient int refreshFlags = 0;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Spatial() {
+        localTransform = new Transform();
+        worldTransform = new Transform();
+
+        localLights = new LightList(this);
+        worldLights = new LightList(this);
+
+        refreshFlags |= RF_BOUND;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Spatial</code> object setting the
+     * rotation, translation and scale value to defaults.
+     *
+     * @param name
+     *            the name of the scene element. This is required for
+     *            identification and comparison purposes.
+     */
+    public Spatial(String name) {
+        this();
+        this.name = name;
+    }
+
+    public void setKey(AssetKey key) {
+        this.key = key;
+    }
+
+    public AssetKey getKey() {
+        return key;
+    }
+
+    /**
+     * Indicate that the transform of this spatial has changed and that
+     * a refresh is required.
+     */
+    protected void setTransformRefresh() {
+        refreshFlags |= RF_TRANSFORM;
+        setBoundRefresh();
+    }
+
+    protected void setLightListRefresh() {
+        refreshFlags |= RF_LIGHTLIST;
+    }
+
+    /**
+     * Indicate that the bounding of this spatial has changed and that
+     * a refresh is required.
+     */
+    protected void setBoundRefresh() {
+        refreshFlags |= RF_BOUND;
+
+        // XXX: Replace with a recursive call?
+        Spatial p = parent;
+        while (p != null) {
+            if ((p.refreshFlags & RF_BOUND) != 0) {
+                return;
+            }
+
+            p.refreshFlags |= RF_BOUND;
+            p = p.parent;
+        }
+    }
+
+    /**
+     * <code>checkCulling</code> checks the spatial with the camera to see if it
+     * should be culled.
+     * <p>
+     * This method is called by the renderer. Usually it should not be called
+     * directly.
+     *
+     * @param cam The camera to check against.
+     * @return true if inside or intersecting camera frustum
+     * (should be rendered), false if outside.
+     */
+    public boolean checkCulling(Camera cam) {
+        if (refreshFlags != 0) {
+            throw new IllegalStateException("Scene graph is not properly updated for rendering.\n"
+                    + "State was changed after rootNode.updateGeometricState() call. \n"
+                    + "Make sure you do not modify the scene from another thread!\n"
+                    + "Problem spatial name: " + getName());
+        }
+
+        CullHint cm = getCullHint();
+        assert cm != CullHint.Inherit;
+        if (cm == Spatial.CullHint.Always) {
+            setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
+            return false;
+        } else if (cm == Spatial.CullHint.Never) {
+            setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+            return true;
+        }
+
+        // check to see if we can cull this node
+        frustrumIntersects = (parent != null ? parent.frustrumIntersects
+                : Camera.FrustumIntersect.Intersects);
+
+        if (frustrumIntersects == Camera.FrustumIntersect.Intersects) {
+            if (getQueueBucket() == Bucket.Gui) {
+                return cam.containsGui(getWorldBound());
+            } else {
+                frustrumIntersects = cam.contains(getWorldBound());
+            }
+        }
+
+        return frustrumIntersects != Camera.FrustumIntersect.Outside;
+    }
+
+    /**
+     * Sets the name of this spatial.
+     *
+     * @param name
+     *            The spatial's new name.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the name of this spatial.
+     *
+     * @return This spatial's name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the local {@link LightList}, which are the lights
+     * that were directly attached to this <code>Spatial</code> through the
+     * {@link #addLight(com.jme3.light.Light) } and 
+     * {@link #removeLight(com.jme3.light.Light) } methods.
+     * 
+     * @return The local light list
+     */
+    public LightList getLocalLightList() {
+        return localLights;
+    }
+
+    /**
+     * Returns the world {@link LightList}, containing the lights
+     * combined from all this <code>Spatial's</code> parents up to and including
+     * this <code>Spatial</code>'s lights.
+     * 
+     * @return The combined world light list
+     */
+    public LightList getWorldLightList() {
+        return worldLights;
+    }
+
+    /**
+     * <code>getWorldRotation</code> retrieves the absolute rotation of the
+     * Spatial.
+     *
+     * @return the Spatial's world rotation quaternion.
+     */
+    public Quaternion getWorldRotation() {
+        checkDoTransformUpdate();
+        return worldTransform.getRotation();
+    }
+
+    /**
+     * <code>getWorldTranslation</code> retrieves the absolute translation of
+     * the spatial.
+     *
+     * @return the Spatial's world tranlsation vector.
+     */
+    public Vector3f getWorldTranslation() {
+        checkDoTransformUpdate();
+        return worldTransform.getTranslation();
+    }
+
+    /**
+     * <code>getWorldScale</code> retrieves the absolute scale factor of the
+     * spatial.
+     *
+     * @return the Spatial's world scale factor.
+     */
+    public Vector3f getWorldScale() {
+        checkDoTransformUpdate();
+        return worldTransform.getScale();
+    }
+
+    /**
+     * <code>getWorldTransform</code> retrieves the world transformation
+     * of the spatial.
+     *
+     * @return the world transform.
+     */
+    public Transform getWorldTransform() {
+        checkDoTransformUpdate();
+        return worldTransform;
+    }
+
+    /**
+     * <code>rotateUpTo</code> is a utility function that alters the
+     * local rotation to point the Y axis in the direction given by newUp.
+     *
+     * @param newUp
+     *            the up vector to use - assumed to be a unit vector.
+     */
+    public void rotateUpTo(Vector3f newUp) {
+        TempVars vars = TempVars.get();
+
+        Vector3f compVecA = vars.vect1;
+        Quaternion q = vars.quat1;
+
+        // First figure out the current up vector.
+        Vector3f upY = compVecA.set(Vector3f.UNIT_Y);
+        Quaternion rot = localTransform.getRotation();
+        rot.multLocal(upY);
+
+        // get angle between vectors
+        float angle = upY.angleBetween(newUp);
+
+        // figure out rotation axis by taking cross product
+        Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal();
+
+        // Build a rotation quat and apply current local rotation.
+        q.fromAngleNormalAxis(angle, rotAxis);
+        q.mult(rot, rot);
+
+        vars.release();
+
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>lookAt</code> is a convenience method for auto-setting the local
+     * rotation based on a position and an up vector. It computes the rotation
+     * to transform the z-axis to point onto 'position' and the y-axis to 'up'.
+     * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } 
+     * this method takes a world position to look at and not a relative direction.
+     *
+     * @param position
+     *            where to look at in terms of world coordinates
+     * @param upVector
+     *            a vector indicating the (local) up direction. (typically {0,
+     *            1, 0} in jME.)
+     */
+    public void lookAt(Vector3f position, Vector3f upVector) {
+        Vector3f worldTranslation = getWorldTranslation();
+
+        TempVars vars = TempVars.get();
+
+        Vector3f compVecA = vars.vect4;
+        vars.release();
+
+        compVecA.set(position).subtractLocal(worldTranslation);
+        getLocalRotation().lookAt(compVecA, upVector);
+
+        setTransformRefresh();
+    }
+
+    /**
+     * Should be overridden by Node and Geometry.
+     */
+    protected void updateWorldBound() {
+        // the world bound of a leaf is the same as it's model bound
+        // for a node, the world bound is a combination of all it's children
+        // bounds
+        // -> handled by subclass
+        refreshFlags &= ~RF_BOUND;
+    }
+
+    protected void updateWorldLightList() {
+        if (parent == null) {
+            worldLights.update(localLights, null);
+            refreshFlags &= ~RF_LIGHTLIST;
+        } else {
+            if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
+                worldLights.update(localLights, parent.worldLights);
+                refreshFlags &= ~RF_LIGHTLIST;
+            } else {
+                assert false;
+            }
+        }
+    }
+
+    /**
+     * Should only be called from updateGeometricState().
+     * In most cases should not be subclassed.
+     */
+    protected void updateWorldTransforms() {
+        if (parent == null) {
+            worldTransform.set(localTransform);
+            refreshFlags &= ~RF_TRANSFORM;
+        } else {
+            // check if transform for parent is updated
+            assert ((parent.refreshFlags & RF_TRANSFORM) == 0);
+            worldTransform.set(localTransform);
+            worldTransform.combineWithParent(parent.worldTransform);
+            refreshFlags &= ~RF_TRANSFORM;
+        }
+    }
+
+    /**
+     * Computes the world transform of this Spatial in the most 
+     * efficient manner possible.
+     */
+    void checkDoTransformUpdate() {
+        if ((refreshFlags & RF_TRANSFORM) == 0) {
+            return;
+        }
+
+        if (parent == null) {
+            worldTransform.set(localTransform);
+            refreshFlags &= ~RF_TRANSFORM;
+        } else {
+            TempVars vars = TempVars.get();
+
+            Spatial[] stack = vars.spatialStack;
+            Spatial rootNode = this;
+            int i = 0;
+            while (true) {
+                Spatial hisParent = rootNode.parent;
+                if (hisParent == null) {
+                    rootNode.worldTransform.set(rootNode.localTransform);
+                    rootNode.refreshFlags &= ~RF_TRANSFORM;
+                    i--;
+                    break;
+                }
+
+                stack[i] = rootNode;
+
+                if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) {
+                    break;
+                }
+
+                rootNode = hisParent;
+                i++;
+            }
+
+            vars.release();
+
+            for (int j = i; j >= 0; j--) {
+                rootNode = stack[j];
+                //rootNode.worldTransform.set(rootNode.localTransform);
+                //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform);
+                //rootNode.refreshFlags &= ~RF_TRANSFORM;
+                rootNode.updateWorldTransforms();
+            }
+        }
+    }
+
+    /**
+     * Computes this Spatial's world bounding volume in the most efficient
+     * manner possible.
+     */
+    void checkDoBoundUpdate() {
+        if ((refreshFlags & RF_BOUND) == 0) {
+            return;
+        }
+
+        checkDoTransformUpdate();
+
+        // Go to children recursively and update their bound
+        if (this instanceof Node) {
+            Node node = (Node) this;
+            int len = node.getQuantity();
+            for (int i = 0; i < len; i++) {
+                Spatial child = node.getChild(i);
+                child.checkDoBoundUpdate();
+            }
+        }
+
+        // All children's bounds have been updated. Update my own now.
+        updateWorldBound();
+    }
+
+    private void runControlUpdate(float tpf) {
+        if (controls.isEmpty()) {
+            return;
+        }
+
+        for (Control c : controls.getArray()) {
+            c.update(tpf);
+        }
+    }
+
+    /**
+     * Called when the Spatial is about to be rendered, to notify
+     * controls attached to this Spatial using the Control.render() method.
+     *
+     * @param rm The RenderManager rendering the Spatial.
+     * @param vp The ViewPort to which the Spatial is being rendered to.
+     *
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
+     * @see Spatial#getControl(java.lang.Class) 
+     */
+    public void runControlRender(RenderManager rm, ViewPort vp) {
+        if (controls.isEmpty()) {
+            return;
+        }
+
+        for (Control c : controls.getArray()) {
+            c.render(rm, vp);
+        }
+    }
+
+    /**
+     * Add a control to the list of controls.
+     * @param control The control to add.
+     *
+     * @see Spatial#removeControl(java.lang.Class) 
+     */
+    public void addControl(Control control) {
+        controls.add(control);
+        control.setSpatial(this);
+    }
+
+    /**
+     * Removes the first control that is an instance of the given class.
+     *
+     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     */
+    public void removeControl(Class<? extends Control> controlType) {
+        for (int i = 0; i < controls.size(); i++) {
+            if (controlType.isAssignableFrom(controls.get(i).getClass())) {
+                Control control = controls.remove(i);
+                control.setSpatial(null);
+            }
+        }
+    }
+
+    /**
+     * Removes the given control from this spatial's controls.
+     * 
+     * @param control The control to remove
+     * @return True if the control was successfuly removed. False if 
+     * the control is not assigned to this spatial.
+     * 
+     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     */
+    public boolean removeControl(Control control) {
+        boolean result = controls.remove(control);
+        if (result) {
+            control.setSpatial(null);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the first control that is an instance of the given class,
+     * or null if no such control exists.
+     *
+     * @param controlType The superclass of the control to look for.
+     * @return The first instance in the list of the controlType class, or null.
+     *
+     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     */
+    public <T extends Control> T getControl(Class<T> controlType) {
+        for (Control c : controls.getArray()) {
+            if (controlType.isAssignableFrom(c.getClass())) {
+                return (T) c;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the control at the given index in the list.
+     *
+     * @param index The index of the control in the list to find.
+     * @return The control at the given index.
+     *
+     * @throws IndexOutOfBoundsException
+     *      If the index is outside the range [0, getNumControls()-1]
+     *
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
+     */
+    public Control getControl(int index) {
+        return controls.get(index);
+    }
+
+    /**
+     * @return The number of controls attached to this Spatial.
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
+     * @see Spatial#removeControl(java.lang.Class) 
+     */
+    public int getNumControls() {
+        return controls.size();
+    }
+
+    /**
+     * <code>updateLogicalState</code> calls the <code>update()</code> method
+     * for all controls attached to this Spatial.
+     *
+     * @param tpf Time per frame.
+     *
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
+     */
+    public void updateLogicalState(float tpf) {
+        runControlUpdate(tpf);
+    }
+
+    /**
+     * <code>updateGeometricState</code> updates the lightlist,
+     * computes the world transforms, and computes the world bounds
+     * for this Spatial.
+     * Calling this when the Spatial is attached to a node
+     * will cause undefined results. User code should only call this
+     * method on Spatials having no parent.
+     * 
+     * @see Spatial#getWorldLightList()
+     * @see Spatial#getWorldTransform()
+     * @see Spatial#getWorldBound()
+     */
+    public void updateGeometricState() {
+        // assume that this Spatial is a leaf, a proper implementation
+        // for this method should be provided by Node.
+
+        // NOTE: Update world transforms first because
+        // bound transform depends on them.
+        if ((refreshFlags & RF_LIGHTLIST) != 0) {
+            updateWorldLightList();
+        }
+        if ((refreshFlags & RF_TRANSFORM) != 0) {
+            updateWorldTransforms();
+        }
+        if ((refreshFlags & RF_BOUND) != 0) {
+            updateWorldBound();
+        }
+
+        assert refreshFlags == 0;
+    }
+
+    /**
+     * Convert a vector (in) from this spatials' local coordinate space to world
+     * coordinate space.
+     *
+     * @param in
+     *            vector to read from
+     * @param store
+     *            where to write the result (null to create a new vector, may be
+     *            same as in)
+     * @return the result (store)
+     */
+    public Vector3f localToWorld(final Vector3f in, Vector3f store) {
+        checkDoTransformUpdate();
+        return worldTransform.transformVector(in, store);
+    }
+
+    /**
+     * Convert a vector (in) from world coordinate space to this spatials' local
+     * coordinate space.
+     *
+     * @param in
+     *            vector to read from
+     * @param store
+     *            where to write the result
+     * @return the result (store)
+     */
+    public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {
+        checkDoTransformUpdate();
+        return worldTransform.transformInverseVector(in, store);
+    }
+
+    /**
+     * <code>getParent</code> retrieves this node's parent. If the parent is
+     * null this is the root node.
+     *
+     * @return the parent of this node.
+     */
+    public Node getParent() {
+        return parent;
+    }
+
+    /**
+     * Called by {@link Node#attachChild(Spatial)} and
+     * {@link Node#detachChild(Spatial)} - don't call directly.
+     * <code>setParent</code> sets the parent of this node.
+     *
+     * @param parent
+     *            the parent of this node.
+     */
+    protected void setParent(Node parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * <code>removeFromParent</code> removes this Spatial from it's parent.
+     *
+     * @return true if it has a parent and performed the remove.
+     */
+    public boolean removeFromParent() {
+        if (parent != null) {
+            parent.detachChild(this);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial.
+     *
+     * @param ancestor
+     *            the ancestor object to look for.
+     * @return true if the ancestor is found, false otherwise.
+     */
+    public boolean hasAncestor(Node ancestor) {
+        if (parent == null) {
+            return false;
+        } else if (parent.equals(ancestor)) {
+            return true;
+        } else {
+            return parent.hasAncestor(ancestor);
+        }
+    }
+
+    /**
+     * <code>getLocalRotation</code> retrieves the local rotation of this
+     * node.
+     *
+     * @return the local rotation of this node.
+     */
+    public Quaternion getLocalRotation() {
+        return localTransform.getRotation();
+    }
+
+    /**
+     * <code>setLocalRotation</code> sets the local rotation of this node
+     * by using a {@link Matrix3f}.
+     *
+     * @param rotation
+     *            the new local rotation.
+     */
+    public void setLocalRotation(Matrix3f rotation) {
+        localTransform.getRotation().fromRotationMatrix(rotation);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>setLocalRotation</code> sets the local rotation of this node.
+     *
+     * @param quaternion
+     *            the new local rotation.
+     */
+    public void setLocalRotation(Quaternion quaternion) {
+        localTransform.setRotation(quaternion);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>getLocalScale</code> retrieves the local scale of this node.
+     *
+     * @return the local scale of this node.
+     */
+    public Vector3f getLocalScale() {
+        return localTransform.getScale();
+    }
+
+    /**
+     * <code>setLocalScale</code> sets the local scale of this node.
+     *
+     * @param localScale
+     *            the new local scale, applied to x, y and z
+     */
+    public void setLocalScale(float localScale) {
+        localTransform.setScale(localScale);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>setLocalScale</code> sets the local scale of this node.
+     */
+    public void setLocalScale(float x, float y, float z) {
+        localTransform.setScale(x, y, z);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>setLocalScale</code> sets the local scale of this node.
+     *
+     * @param localScale
+     *            the new local scale.
+     */
+    public void setLocalScale(Vector3f localScale) {
+        localTransform.setScale(localScale);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>getLocalTranslation</code> retrieves the local translation of
+     * this node.
+     *
+     * @return the local translation of this node.
+     */
+    public Vector3f getLocalTranslation() {
+        return localTransform.getTranslation();
+    }
+
+    /**
+     * <code>setLocalTranslation</code> sets the local translation of this
+     * spatial.
+     *
+     * @param localTranslation
+     *            the local translation of this spatial.
+     */
+    public void setLocalTranslation(Vector3f localTranslation) {
+        this.localTransform.setTranslation(localTranslation);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>setLocalTranslation</code> sets the local translation of this
+     * spatial.
+     */
+    public void setLocalTranslation(float x, float y, float z) {
+        this.localTransform.setTranslation(x, y, z);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>setLocalTransform</code> sets the local transform of this
+     * spatial.
+     */
+    public void setLocalTransform(Transform t) {
+        this.localTransform.set(t);
+        setTransformRefresh();
+    }
+
+    /**
+     * <code>getLocalTransform</code> retrieves the local transform of
+     * this spatial.
+     *
+     * @return the local transform of this spatial.
+     */
+    public Transform getLocalTransform() {
+        return localTransform;
+    }
+
+    /**
+     * Applies the given material to the Spatial, this will propagate the
+     * material down to the geometries in the scene graph.
+     *
+     * @param material The material to set.
+     */
+    public void setMaterial(Material material) {
+    }
+
+    /**
+     * <code>addLight</code> adds the given light to the Spatial; causing
+     * all child Spatials to be effected by it.
+     *
+     * @param light The light to add.
+     */
+    public void addLight(Light light) {
+        localLights.add(light);
+        setLightListRefresh();
+    }
+
+    /**
+     * <code>removeLight</code> removes the given light from the Spatial.
+     * 
+     * @param light The light to remove.
+     * @see Spatial#addLight(com.jme3.light.Light) 
+     */
+    public void removeLight(Light light) {
+        localLights.remove(light);
+        setLightListRefresh();
+    }
+
+    /**
+     * Translates the spatial by the given translation vector.
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial move(float x, float y, float z) {
+        this.localTransform.getTranslation().addLocal(x, y, z);
+        setTransformRefresh();
+
+        return this;
+    }
+
+    /**
+     * Translates the spatial by the given translation vector.
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial move(Vector3f offset) {
+        this.localTransform.getTranslation().addLocal(offset);
+        setTransformRefresh();
+
+        return this;
+    }
+
+    /**
+     * Scales the spatial by the given value
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial scale(float s) {
+        return scale(s, s, s);
+    }
+
+    /**
+     * Scales the spatial by the given scale vector.
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial scale(float x, float y, float z) {
+        this.localTransform.getScale().multLocal(x, y, z);
+        setTransformRefresh();
+
+        return this;
+    }
+
+    /**
+     * Rotates the spatial by the given rotation.
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial rotate(Quaternion rot) {
+        this.localTransform.getRotation().multLocal(rot);
+        setTransformRefresh();
+
+        return this;
+    }
+
+    /**
+     * Rotates the spatial by the yaw, roll and pitch angles (in radians),
+     * in the local coordinate space.
+     *
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial rotate(float yaw, float roll, float pitch) {
+        TempVars vars = TempVars.get();
+        Quaternion q = vars.quat1;
+        q.fromAngles(yaw, roll, pitch);
+        rotate(q);
+        vars.release();
+
+        return this;
+    }
+
+    /**
+     * Centers the spatial in the origin of the world bound.
+     * @return The spatial on which this method is called, e.g <code>this</code>.
+     */
+    public Spatial center() {
+        Vector3f worldTrans = getWorldTranslation();
+        Vector3f worldCenter = getWorldBound().getCenter();
+
+        Vector3f absTrans = worldTrans.subtract(worldCenter);
+        setLocalTranslation(absTrans);
+
+        return this;
+    }
+
+    /**
+     * @see #setCullHint(CullHint)
+     * @return the cull mode of this spatial, or if set to CullHint.Inherit,
+     * the cullmode of it's parent.
+     */
+    public CullHint getCullHint() {
+        if (cullHint != CullHint.Inherit) {
+            return cullHint;
+        } else if (parent != null) {
+            return parent.getCullHint();
+        } else {
+            return CullHint.Dynamic;
+        }
+    }
+
+    public BatchHint getBatchHint() {
+        if (batchHint != BatchHint.Inherit) {
+            return batchHint;
+        } else if (parent != null) {
+            return parent.getBatchHint();
+        } else {
+            return BatchHint.Always;
+        }
+    }
+
+    /**
+     * Returns this spatial's renderqueue bucket. If the mode is set to inherit,
+     * then the spatial gets its renderqueue bucket from its parent.
+     *
+     * @return The spatial's current renderqueue mode.
+     */
+    public RenderQueue.Bucket getQueueBucket() {
+        if (queueBucket != RenderQueue.Bucket.Inherit) {
+            return queueBucket;
+        } else if (parent != null) {
+            return parent.getQueueBucket();
+        } else {
+            return RenderQueue.Bucket.Opaque;
+        }
+    }
+
+    /**
+     * @return The shadow mode of this spatial, if the local shadow
+     * mode is set to inherit, then the parent's shadow mode is returned.
+     *
+     * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+     * @see ShadowMode
+     */
+    public RenderQueue.ShadowMode getShadowMode() {
+        if (shadowMode != RenderQueue.ShadowMode.Inherit) {
+            return shadowMode;
+        } else if (parent != null) {
+            return parent.getShadowMode();
+        } else {
+            return ShadowMode.Off;
+        }
+    }
+
+    /**
+     * Sets the level of detail to use when rendering this Spatial,
+     * this call propagates to all geometries under this Spatial.
+     *
+     * @param lod The lod level to set.
+     */
+    public void setLodLevel(int lod) {
+    }
+
+    /**
+     * <code>updateModelBound</code> recalculates the bounding object for this
+     * Spatial.
+     */
+    public abstract void updateModelBound();
+
+    /**
+     * <code>setModelBound</code> sets the bounding object for this Spatial.
+     *
+     * @param modelBound
+     *            the bounding object for this spatial.
+     */
+    public abstract void setModelBound(BoundingVolume modelBound);
+
+    /**
+     * @return The sum of all verticies under this Spatial.
+     */
+    public abstract int getVertexCount();
+
+    /**
+     * @return The sum of all triangles under this Spatial.
+     */
+    public abstract int getTriangleCount();
+
+    /**
+     * @return A clone of this Spatial, the scene graph in its entirety
+     * is cloned and can be altered independently of the original scene graph.
+     *
+     * Note that meshes of geometries are not cloned explicitly, they
+     * are shared if static, or specially cloned if animated.
+     *
+     * All controls will be cloned using the Control.cloneForSpatial method
+     * on the clone.
+     *
+     * @see Mesh#cloneForAnim() 
+     */
+    public Spatial clone(boolean cloneMaterial) {
+        try {
+            Spatial clone = (Spatial) super.clone();
+            if (worldBound != null) {
+                clone.worldBound = worldBound.clone();
+            }
+            clone.worldLights = worldLights.clone();
+            clone.localLights = localLights.clone();
+
+            // Set the new owner of the light lists
+            clone.localLights.setOwner(clone);
+            clone.worldLights.setOwner(clone);
+
+            // No need to force cloned to update.
+            // This node already has the refresh flags
+            // set below so it will have to update anyway.
+            clone.worldTransform = worldTransform.clone();
+            clone.localTransform = localTransform.clone();
+
+            if (clone instanceof Node) {
+                Node node = (Node) this;
+                Node nodeClone = (Node) clone;
+                nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+                for (Spatial child : node.children) {
+                    Spatial childClone = child.clone(cloneMaterial);
+                    childClone.parent = nodeClone;
+                    nodeClone.children.add(childClone);
+                }
+            }
+
+            clone.parent = null;
+            clone.setBoundRefresh();
+            clone.setTransformRefresh();
+            clone.setLightListRefresh();
+
+            clone.controls = new SafeArrayList<Control>(Control.class);
+            for (int i = 0; i < controls.size(); i++) {
+                clone.controls.add(controls.get(i).cloneForSpatial(clone));
+            }
+
+            if (userData != null) {
+                clone.userData = (HashMap<String, Savable>) userData.clone();
+            }
+
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * @return A clone of this Spatial, the scene graph in its entirety
+     * is cloned and can be altered independently of the original scene graph.
+     *
+     * Note that meshes of geometries are not cloned explicitly, they
+     * are shared if static, or specially cloned if animated.
+     *
+     * All controls will be cloned using the Control.cloneForSpatial method
+     * on the clone.
+     *
+     * @see Mesh#cloneForAnim() 
+     */
+    @Override
+    public Spatial clone() {
+        return clone(true);
+    }
+
+    /**
+     * @return Similar to Spatial.clone() except will create a deep clone
+     * of all geometry's meshes, normally this method shouldn't be used
+     * instead use Spatial.clone()
+     *
+     * @see Spatial#clone()
+     */
+    public abstract Spatial deepClone();
+
+    public void setUserData(String key, Object data) {
+        if (userData == null) {
+            userData = new HashMap<String, Savable>();
+        }
+
+        if (data instanceof Savable) {
+            userData.put(key, (Savable) data);
+        } else {
+            userData.put(key, new UserData(UserData.getObjectType(data), data));
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getUserData(String key) {
+        if (userData == null) {
+            return null;
+        }
+
+        Savable s = userData.get(key);
+        if (s instanceof UserData) {
+            return (T) ((UserData) s).getValue();
+        } else {
+            return (T) s;
+        }
+    }
+
+    public Collection<String> getUserDataKeys() {
+        if (userData != null) {
+            return userData.keySet();
+        }
+
+        return Collections.EMPTY_SET;
+    }
+
+    /**
+     * Note that we are <i>matching</i> the pattern, therefore the pattern
+     * must match the entire pattern (i.e. it behaves as if it is sandwiched
+     * between "^" and "$").
+     * You can set regex modes, like case insensitivity, by using the (?X)
+     * or (?X:Y) constructs.
+     *
+     * @param spatialSubclass Subclass which this must implement.
+     *                        Null causes all Spatials to qualify.
+     * @param nameRegex  Regular expression to match this name against.
+     *                        Null causes all Names to qualify.
+     * @return true if this implements the specified class and this's name
+     *         matches the specified pattern.
+     *
+     * @see java.util.regex.Pattern
+     */
+    public boolean matches(Class<? extends Spatial> spatialSubclass,
+            String nameRegex) {
+        if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
+            return false;
+        }
+
+        if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(name, "name", null);
+        capsule.write(worldBound, "world_bound", null);
+        capsule.write(cullHint, "cull_mode", CullHint.Inherit);
+        capsule.write(batchHint, "batch_hint", BatchHint.Inherit);
+        capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit);
+        capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit);
+        capsule.write(localTransform, "transform", Transform.IDENTITY);
+        capsule.write(localLights, "lights", null);
+
+        // Shallow clone the controls array to convert its type.
+        capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null);
+        capsule.writeStringSavableMap(userData, "user_data", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+
+        name = ic.readString("name", null);
+        worldBound = (BoundingVolume) ic.readSavable("world_bound", null);
+        cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit);
+        batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit);
+        queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class,
+                RenderQueue.Bucket.Inherit);
+        shadowMode = ic.readEnum("shadow_mode", ShadowMode.class,
+                ShadowMode.Inherit);
+
+        localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY);
+
+        localLights = (LightList) ic.readSavable("lights", null);
+        localLights.setOwner(this);
+
+        //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
+        //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
+        //The SkeletonControl must be the last in the stack so we add the list of all other control before it.
+        //When backward compatibility won't be needed anymore this can be replaced by : 
+        //controls = ic.readSavableArrayList("controlsList", null));
+        controls.addAll(0, ic.readSavableArrayList("controlsList", null));
+
+        userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
+    }
+
+    /**
+     * <code>getWorldBound</code> retrieves the world bound at this node
+     * level.
+     *
+     * @return the world bound at this level.
+     */
+    public BoundingVolume getWorldBound() {
+        checkDoBoundUpdate();
+        return worldBound;
+    }
+
+    /**
+     * <code>setCullHint</code> sets how scene culling should work on this
+     * spatial during drawing. NOTE: You must set this AFTER attaching to a 
+     * parent or it will be reset with the parent's cullMode value.
+     *
+     * @param hint
+     *            one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or
+     *            CullHint.Never
+     */
+    public void setCullHint(CullHint hint) {
+        cullHint = hint;
+    }
+
+    /**
+     * <code>setBatchHint</code> sets how batching should work on this
+     * spatial. NOTE: You must set this AFTER attaching to a 
+     * parent or it will be reset with the parent's cullMode value.
+     *
+     * @param hint
+     *            one of BatchHint.Never, BatchHint.Always, BatchHint.Inherit 
+     */
+    public void setBatchHint(BatchHint hint) {
+        batchHint = hint;
+    }
+
+    /**
+     * @return the cullmode set on this Spatial
+     */
+    public CullHint getLocalCullHint() {
+        return cullHint;
+    }
+
+    /**
+     * @return the batchHint set on this Spatial
+     */
+    public BatchHint getLocalBatchHint() {
+        return batchHint;
+    }
+
+    /**
+     * <code>setQueueBucket</code> determines at what phase of the
+     * rendering process this Spatial will rendered. See the
+     * {@link Bucket} enum for an explanation of the various 
+     * render queue buckets.
+     * 
+     * @param queueBucket
+     *            The bucket to use for this Spatial.
+     */
+    public void setQueueBucket(RenderQueue.Bucket queueBucket) {
+        this.queueBucket = queueBucket;
+    }
+
+    /**
+     * Sets the shadow mode of the spatial
+     * The shadow mode determines how the spatial should be shadowed,
+     * when a shadowing technique is used. See the
+     * documentation for the class {@link ShadowMode} for more information.
+     *
+     * @see ShadowMode
+     *
+     * @param shadowMode The local shadow mode to set.
+     */
+    public void setShadowMode(RenderQueue.ShadowMode shadowMode) {
+        this.shadowMode = shadowMode;
+    }
+
+    /**
+     * @return The locally set queue bucket mode
+     *
+     * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket)
+     */
+    public RenderQueue.Bucket getLocalQueueBucket() {
+        return queueBucket;
+    }
+
+    /**
+     * @return The locally set shadow mode
+     *
+     * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+     */
+    public RenderQueue.ShadowMode getLocalShadowMode() {
+        return shadowMode;
+    }
+
+    /**
+     * Returns this spatial's last frustum intersection result. This int is set
+     * when a check is made to determine if the bounds of the object fall inside
+     * a camera's frustum. If a parent is found to fall outside the frustum, the
+     * value for this spatial will not be updated.
+     *
+     * @return The spatial's last frustum intersection result.
+     */
+    public Camera.FrustumIntersect getLastFrustumIntersection() {
+        return frustrumIntersects;
+    }
+
+    /**
+     * Overrides the last intersection result. This is useful for operations
+     * that want to start rendering at the middle of a scene tree and don't want
+     * the parent of that node to influence culling.
+     *
+     * @param intersects
+     *            the new value
+     */
+    public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) {
+        frustrumIntersects = intersects;
+    }
+
+    /**
+     * Returns the Spatial's name followed by the class of the spatial <br>
+     * Example: "MyNode (com.jme3.scene.Spatial)
+     *
+     * @return Spatial's name followed by the class of the Spatial
+     */
+    @Override
+    public String toString() {
+        return name + " (" + this.getClass().getSimpleName() + ')';
+    }
+
+    /**
+     * Creates a transform matrix that will convert from this spatials'
+     * local coordinate space to the world coordinate space
+     * based on the world transform.
+     *
+     * @param store Matrix where to store the result, if null, a new one
+     * will be created and returned.
+     *
+     * @return store if not null, otherwise, a new matrix containing the result.
+     *
+     * @see Spatial#getWorldTransform() 
+     */
+    public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
+        if (store == null) {
+            store = new Matrix4f();
+        } else {
+            store.loadIdentity();
+        }
+        // multiply with scale first, then rotate, finally translate (cf.
+        // Eberly)
+        store.scale(getWorldScale());
+        store.multLocal(getWorldRotation());
+        store.setTranslation(getWorldTranslation());
+        return store;
+    }
+
+    /**
+     * Visit each scene graph element ordered by DFS
+     * @param visitor
+     */
+    public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
+
+    /**
+     * Visit each scene graph element ordered by BFS
+     * @param visitor
+     */
+    public void breadthFirstTraversal(SceneGraphVisitor visitor) {
+        Queue<Spatial> queue = new LinkedList<Spatial>();
+        queue.add(this);
+
+        while (!queue.isEmpty()) {
+            Spatial s = queue.poll();
+            visitor.visit(s);
+            s.breadthFirstTraversal(visitor, queue);
+        }
+    }
+
+    protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
+}
diff --git a/engine/src/core/com/jme3/scene/UserData.java b/engine/src/core/com/jme3/scene/UserData.java
new file mode 100644
index 0000000..855a31c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/UserData.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+/**
+ * <code>UserData</code> is used to contain user data objects
+ * set on spatials (primarily primitives) that do not implement
+ * the {@link Savable} interface. Note that attempting
+ * to export any models which have non-savable objects
+ * attached to them will fail.
+ */
+public final class UserData implements Savable {
+
+    /**
+     * Boolean type on Geometries to indicate that physics collision
+     * shape generation should ignore them.
+     */
+    public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore";
+    
+    /**
+     * For geometries using shared mesh, this will specify the shared
+     * mesh reference.
+     */
+    public static final String JME_SHAREDMESH = "JmeSharedMesh";
+    
+    protected byte type;
+    protected Object value;
+
+    public UserData() {
+    }
+
+    /**
+     * Creates a new <code>UserData</code> with the given 
+     * type and value.
+     * 
+     * @param type Type of data, should be between 0 and 4.
+     * @param value Value of the data
+     */
+    public UserData(byte type, Object value) {
+        assert type >= 0 && type <= 4;
+        this.type = type;
+        this.value = value;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return value.toString();
+    }
+
+    public static byte getObjectType(Object type) {
+        if (type instanceof Integer) {
+            return 0;
+        } else if (type instanceof Float) {
+            return 1;
+        } else if (type instanceof Boolean) {
+            return 2;
+        } else if (type instanceof String) {
+            return 3;
+        } else if (type instanceof Long) {
+            return 4;
+        } else {
+            throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(type, "type", (byte)0);
+
+        switch (type) {
+            case 0:
+                int i = (Integer) value;
+                oc.write(i, "intVal", 0);
+                break;
+            case 1:
+                float f = (Float) value;
+                oc.write(f, "floatVal", 0f);
+                break;
+            case 2:
+                boolean b = (Boolean) value;
+                oc.write(b, "boolVal", false);
+                break;
+            case 3:
+                String s = (String) value;
+                oc.write(s, "strVal", null);
+                break;
+            case 4:
+                Long l = (Long) value;
+                oc.write(l, "longVal", 0l);
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        type = ic.readByte("type", (byte) 0);
+
+        switch (type) {
+            case 0:
+                value = ic.readInt("intVal", 0);
+                break;
+            case 1:
+                value = ic.readFloat("floatVal", 0f);
+                break;
+            case 2:
+                value = ic.readBoolean("boolVal", false);
+                break;
+            case 3:
+                value = ic.readString("strVal", null);
+                break;
+            case 4:
+                value = ic.readLong("longVal", 0l);
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/VertexBuffer.java b/engine/src/core/com/jme3/scene/VertexBuffer.java
new file mode 100644
index 0000000..1a844bb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/VertexBuffer.java
@@ -0,0 +1,1025 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.nio.*;
+
+/**
+ * A <code>VertexBuffer</code> contains a particular type of geometry
+ * data used by {@link Mesh}es. Every VertexBuffer set on a <code>Mesh</code>
+ * is sent as an attribute to the vertex shader to be processed.
+ * <p>
+ * Several terms are used throughout the javadoc for this class, explanation:
+ * <ul>
+ * <li>Element - A single element is the largest individual object
+ * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position
+ * data, then an element will be a single 3D vector.</li>
+ * <li>Component - A component represents the parts inside an element. 
+ * For a 3D vector, a single component is one of the dimensions, X, Y or Z.</li>
+ * </ul>
+ */
+public class VertexBuffer extends NativeObject implements Savable, Cloneable {
+  
+    /**
+     * Type of buffer. Specifies the actual attribute it defines.
+     */
+    public static enum Type {
+        /**
+         * Position of the vertex (3 floats)
+         */
+        Position,
+
+        /**
+         * The size of the point when using point buffers (float).
+         */
+        Size,
+
+        /**
+         * Normal vector, normalized (3 floats).
+         */
+        Normal,
+
+        /**
+         * Texture coordinate (2 float)
+         */
+        TexCoord,
+
+        /**
+         * Color and Alpha (4 floats)
+         */
+        Color,
+
+        /**
+         * Tangent vector, normalized (4 floats) (x,y,z,w)
+         * the w component is called the binormal parity, is not normalized and is either 1f or -1f
+         * It's used to compuste the direction on the binormal verctor on the GPU at render time.
+         */
+        Tangent,
+
+        /**
+         * Binormal vector, normalized (3 floats, optional)
+         */
+        Binormal,
+
+        /**
+         * Specifies the source data for various vertex buffers
+         * when interleaving is used. By default the format is
+         * byte.
+         */
+        InterleavedData,
+
+        /**
+         * Do not use.
+         */
+        @Deprecated
+        MiscAttrib,
+
+        /**
+         * Specifies the index buffer, must contain integer data
+         * (ubyte, ushort, or uint).
+         */
+        Index,
+
+        /** 
+         * Initial vertex position, used with animation.
+         * Should have the same format and size as {@link Type#Position}.
+         * If used with software skinning, the usage should be 
+         * {@link Usage#CpuOnly}, and the buffer should be allocated
+         * on the heap.
+         */
+        BindPosePosition,
+
+        /** 
+         * Initial vertex normals, used with animation.
+         * Should have the same format and size as {@link Type#Normal}.
+         * If used with software skinning, the usage should be 
+         * {@link Usage#CpuOnly}, and the buffer should be allocated
+         * on the heap.
+         */
+        BindPoseNormal,      
+         
+        /** 
+         * Bone weights, used with animation (4 floats).
+         * If used with software skinning, the usage should be 
+         * {@link Usage#CpuOnly}, and the buffer should be allocated
+         * on the heap.
+         */
+        BoneWeight,
+
+        /** 
+         * Bone indices, used with animation (4 ubytes).
+         * If used with software skinning, the usage should be 
+         * {@link Usage#CpuOnly}, and the buffer should be allocated
+         * on the heap.
+         */
+        BoneIndex,
+
+        /**
+         * Texture coordinate #2
+         */
+        TexCoord2,
+
+        /**
+         * Texture coordinate #3
+         */
+        TexCoord3,
+
+        /**
+         * Texture coordinate #4
+         */
+        TexCoord4,
+
+        /**
+         * Texture coordinate #5
+         */
+        TexCoord5,
+
+        /**
+         * Texture coordinate #6
+         */
+        TexCoord6,
+
+        /**
+         * Texture coordinate #7
+         */
+        TexCoord7,
+
+        /**
+         * Texture coordinate #8
+         */
+        TexCoord8,
+        
+        /** 
+         * Initial vertex tangents, used with animation.
+         * Should have the same format and size as {@link Type#Tangent}.
+         * If used with software skinning, the usage should be 
+         * {@link Usage#CpuOnly}, and the buffer should be allocated
+         * on the heap.
+         */
+        BindPoseTangent,
+    }
+
+    /**
+     * The usage of the VertexBuffer, specifies how often the buffer
+     * is used. This can determine if a vertex buffer is placed in VRAM
+     * or held in video memory, but no guarantees are made- it's only a hint.
+     */
+    public static enum Usage {
+        
+        /**
+         * Mesh data is sent once and very rarely updated.
+         */
+        Static,
+
+        /**
+         * Mesh data is updated occasionally (once per frame or less).
+         */
+        Dynamic,
+
+        /**
+         * Mesh data is updated every frame.
+         */
+        Stream,
+
+        /**
+         * Mesh data is <em>not</em> sent to GPU at all. It is only
+         * used by the CPU.
+         */
+        CpuOnly;
+    }
+
+    /**
+     * Specifies format of the data stored in the buffer.
+     * This should directly correspond to the buffer's class, for example,
+     * an {@link Format#UnsignedShort} formatted buffer should use the
+     * class {@link ShortBuffer} (e.g. the closest resembling type).
+     * For the {@link Format#Half} type, {@link ByteBuffer}s should
+     * be used.
+     */
+    public static enum Format {
+        /**
+         * Half precision floating point.
+         * 2 bytes, signed.
+         */
+        Half(2),
+        
+        /**
+         * Single precision floating point.
+         * 4 bytes, signed
+         */
+        Float(4),
+        
+        /**
+         * Double precision floating point.
+         * 8 bytes, signed. May not
+         * be supported by all GPUs.
+         */
+        Double(8),
+
+        /**
+         * 1 byte integer, signed.
+         */
+        Byte(1),
+        
+        /**
+         * 1 byte integer, unsigned.
+         */
+        UnsignedByte(1),
+        
+        /**
+         * 2 byte integer, signed.
+         */
+        Short(2),
+        
+        /**
+         * 2 byte integer, unsigned.
+         */
+        UnsignedShort(2),
+        
+        /**
+         * 4 byte integer, signed.
+         */
+        Int(4),
+        
+        /**
+         * 4 byte integer, unsigned.
+         */
+        UnsignedInt(4);
+
+        private int componentSize = 0;
+
+        Format(int componentSize){
+            this.componentSize = componentSize;
+        }
+
+        /**
+         * Returns the size in bytes of this data type.
+         * 
+         * @return Size in bytes of this data type.
+         */
+        public int getComponentSize(){
+            return componentSize;
+        }
+    }
+
+    protected int offset = 0;
+    protected int lastLimit = 0;
+    protected int stride = 0;
+    protected int components = 0;
+
+    /**
+     * derived from components * format.getComponentSize()
+     */
+    protected transient int componentsLength = 0;
+    protected Buffer data = null;
+    protected Usage usage;
+    protected Type bufType;
+    protected Format format;
+    protected boolean normalized = false;
+    protected transient boolean dataSizeChanged = false;
+
+    /**
+     * Creates an empty, uninitialized buffer.
+     * Must call setupData() to initialize.
+     */
+    public VertexBuffer(Type type){
+        super(VertexBuffer.class);
+        this.bufType = type;
+    }
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public VertexBuffer(){
+        super(VertexBuffer.class);
+    }
+
+    protected VertexBuffer(int id){
+        super(VertexBuffer.class, id);
+    }
+
+    /**
+     * @return The offset after which the data is sent to the GPU.
+     * 
+     * @see #setOffset(int) 
+     */
+    public int getOffset() {
+        return offset;
+    }
+
+    /**
+     * @param offset Specify the offset (in bytes) from the start of the buffer
+     * after which the data is sent to the GPU.
+     */
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    /**
+     * @return The stride (in bytes) for the data. 
+     * 
+     * @see #setStride(int) 
+     */
+    public int getStride() {
+        return stride;
+    }
+
+    /**
+     * Set the stride (in bytes) for the data. 
+     * <p>
+     * If the data is packed in the buffer, then stride is 0, if there's other 
+     * data that is between the current component and the next component in the 
+     * buffer, then this specifies the size in bytes of that additional data.
+     * 
+     * @param stride the stride (in bytes) for the data
+     */
+    public void setStride(int stride) {
+        this.stride = stride;
+    }
+
+    /**
+     * Returns the raw internal data buffer used by this VertexBuffer.
+     * This buffer is not safe to call from multiple threads since buffers
+     * have their own internal position state that cannot be shared.
+     * Call getData().duplicate(), getData().asReadOnlyBuffer(), or 
+     * the more convenient getDataReadOnly() if the buffer may be accessed 
+     * from multiple threads.
+     * 
+     * @return A native buffer, in the specified {@link Format format}.
+     */
+    public Buffer getData(){
+        return data;
+    }
+    
+    /** 
+     * Returns a safe read-only version of this VertexBuffer's data.  The
+     * contents of the buffer will reflect whatever changes are made on
+     * other threads (eventually) but these should not be used in that way.
+     * This method provides a read-only buffer that is safe to _read_ from
+     * a separate thread since it has its own book-keeping state (position, limit, etc.)
+     *
+     * @return A rewound native buffer in the specified {@link Format format}
+     *         that is safe to read from a separate thread from other readers. 
+     */
+    public Buffer getDataReadOnly() {
+    
+        if (data == null) {
+            return null;
+        }
+        
+        // Create a read-only duplicate().  Note: this does not copy
+        // the underlying memory, it just creates a new read-only wrapper
+        // with its own buffer position state.
+        
+        // Unfortunately, this is not 100% straight forward since Buffer
+        // does not have an asReadOnlyBuffer() method.
+        Buffer result;
+        if( data instanceof ByteBuffer ) {
+            result = ((ByteBuffer)data).asReadOnlyBuffer();
+        } else if( data instanceof FloatBuffer ) {
+            result = ((FloatBuffer)data).asReadOnlyBuffer();
+        } else if( data instanceof ShortBuffer ) {
+            result = ((ShortBuffer)data).asReadOnlyBuffer();
+        } else if( data instanceof IntBuffer ) {
+            result = ((IntBuffer)data).asReadOnlyBuffer();
+        } else {
+            throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data );
+        }
+        
+        // Make sure the caller gets a consistent view since we may
+        // have grabbed this buffer while another thread was reading
+        // the raw data.
+        result.rewind();
+        
+        return result;
+    }
+
+    /**
+     * @return The usage of this buffer. See {@link Usage} for more
+     * information.
+     */
+    public Usage getUsage(){
+        return usage;
+    }
+
+    /**
+     * @param usage The usage of this buffer. See {@link Usage} for more
+     * information.
+     */
+    public void setUsage(Usage usage){
+//        if (id != -1)
+//            throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
+
+        this.usage = usage;
+    }
+
+    /**
+     * @param normalized Set to true if integer components should be converted
+     * from their maximal range into the range 0.0 - 1.0 when converted to
+     * a floating-point value for the shader.
+     * E.g. if the {@link Format} is {@link Format#UnsignedInt}, then
+     * the components will be converted to the range 0.0 - 1.0 by dividing
+     * every integer by 2^32.
+     */
+    public void setNormalized(boolean normalized){
+        this.normalized = normalized;
+    }
+
+    /**
+     * @return True if integer components should be converted to the range 0-1.
+     * @see VertexBuffer#setNormalized(boolean) 
+     */
+    public boolean isNormalized(){
+        return normalized;
+    }
+
+    /**
+     * @return The type of information that this buffer has.
+     */
+    public Type getBufferType(){
+        return bufType;
+    }
+
+    /**
+     * @return The {@link Format format}, or data type of the data.
+     */
+    public Format getFormat(){
+        return format;
+    }
+
+    /**
+     * @return The number of components of the given {@link Format format} per
+     * element.
+     */
+    public int getNumComponents(){
+        return components;
+    }
+
+    /**
+     * @return The total number of data elements in the data buffer.
+     */
+    public int getNumElements(){
+        int elements = data.capacity() / components;
+        if (format == Format.Half)
+            elements /= 2;
+        return elements;
+    }
+
+    /**
+     * Called to initialize the data in the <code>VertexBuffer</code>. Must only
+     * be called once.
+     * 
+     * @param usage The usage for the data, or how often will the data
+     * be updated per frame. See the {@link Usage} enum.
+     * @param components The number of components per element.
+     * @param format The {@link Format format}, or data-type of a single
+     * component.
+     * @param data A native buffer, the format of which matches the {@link Format}
+     * argument.
+     */
+    public void setupData(Usage usage, int components, Format format, Buffer data){
+        if (id != -1)
+            throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
+
+        if (usage == null || format == null || data == null)
+            throw new IllegalArgumentException("None of the arguments can be null");
+            
+        if (data.isReadOnly()) 
+            throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+
+        if (components < 1 || components > 4)
+            throw new IllegalArgumentException("components must be between 1 and 4");
+
+        this.data = data;
+        this.components = components;
+        this.usage = usage;
+        this.format = format;
+        this.componentsLength = components * format.getComponentSize();
+        this.lastLimit = data.limit();
+        setUpdateNeeded();
+    }
+
+    /**
+     * Called to update the data in the buffer with new data. Can only
+     * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) }
+     * has been called. Note that it is fine to call this method on the
+     * data already set, e.g. vb.updateData(vb.getData()), this will just
+     * set the proper update flag indicating the data should be sent to the GPU
+     * again.
+     * It is allowed to specify a buffer with different capacity than the
+     * originally set buffer.
+     *
+     * @param data The data buffer to set
+     */
+    public void updateData(Buffer data){
+        if (id != -1){
+            // request to update data is okay
+        }
+
+        // Check if the data buffer is read-only which is a sign
+        // of a bug on the part of the caller
+        if (data != null && data.isReadOnly()) {
+            throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+        }
+
+        // will force renderer to call glBufferData again
+        if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){
+            dataSizeChanged = true;
+            lastLimit = data.limit();
+        }
+        
+        this.data = data;
+        setUpdateNeeded();
+    }
+
+    /**
+     * Returns true if the data size of the VertexBuffer has changed.
+     * Internal use only.
+     * @return true if the data size has changed
+     */
+    public boolean hasDataSizeChanged() {
+        return dataSizeChanged;
+    }
+
+    @Override
+    public void clearUpdateNeeded(){
+        super.clearUpdateNeeded();
+        dataSizeChanged = false;
+    }
+
+    /**
+     * Converts single floating-point data to {@link Format#Half half} floating-point data.
+     */
+    public void convertToHalf(){
+        if (id != -1)
+            throw new UnsupportedOperationException("Data has already been sent.");
+
+        if (format != Format.Float)
+            throw new IllegalStateException("Format must be float!");
+
+        int numElements = data.capacity() / components;
+        format = Format.Half;
+        this.componentsLength = components * format.getComponentSize();
+        
+        ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements);
+        halfData.rewind();
+
+        FloatBuffer floatData = (FloatBuffer) data;
+        floatData.rewind();
+
+        for (int i = 0; i < floatData.capacity(); i++){
+            float f = floatData.get(i);
+            short half = FastMath.convertFloatToHalf(f);
+            halfData.putShort(half);
+        }
+        this.data = halfData;
+        setUpdateNeeded();
+        dataSizeChanged = true;
+    }
+
+    /**
+     * Reduces the capacity of the buffer to the given amount
+     * of elements, any elements at the end of the buffer are truncated
+     * as necessary.
+     *
+     * @param numElements The number of elements to reduce to.
+     */
+    public void compact(int numElements){
+        int total = components * numElements;
+        data.clear();
+        switch (format){
+            case Byte:
+            case UnsignedByte:
+            case Half:
+                ByteBuffer bbuf = (ByteBuffer) data;
+                bbuf.limit(total);
+                ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total);
+                bnewBuf.put(bbuf);
+                data = bnewBuf;
+                break;
+            case Short:
+            case UnsignedShort:
+                ShortBuffer sbuf = (ShortBuffer) data;
+                sbuf.limit(total);
+                ShortBuffer snewBuf = BufferUtils.createShortBuffer(total);
+                snewBuf.put(sbuf);
+                data = snewBuf;
+                break;
+            case Int:
+            case UnsignedInt:
+                IntBuffer ibuf = (IntBuffer) data;
+                ibuf.limit(total);
+                IntBuffer inewBuf = BufferUtils.createIntBuffer(total);
+                inewBuf.put(ibuf);
+                data = inewBuf;
+                break;
+            case Float:
+                FloatBuffer fbuf = (FloatBuffer) data;
+                fbuf.limit(total);
+                FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total);
+                fnewBuf.put(fbuf);
+                data = fnewBuf;
+                break;
+            default:
+                throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+        }
+        data.clear();
+        setUpdateNeeded();
+        dataSizeChanged = true;
+    }
+
+    /**
+     * Modify a component inside an element.
+     * The <code>val</code> parameter must be in the buffer's format:
+     * {@link Format}.
+     * 
+     * @param elementIndex The element index to modify
+     * @param componentIndex The component index to modify
+     * @param val The value to set, either byte, short, int or float depending
+     * on the {@link Format}.
+     */
+    public void setElementComponent(int elementIndex, int componentIndex, Object val){
+        int inPos = elementIndex * components;
+        int elementPos = componentIndex;
+
+        if (format == Format.Half){
+            inPos *= 2;
+            elementPos *= 2;
+        }
+
+        data.clear();
+
+        switch (format){
+            case Byte:
+            case UnsignedByte:
+            case Half:
+                ByteBuffer bin = (ByteBuffer) data;
+                bin.put(inPos + elementPos, (Byte)val);
+                break;
+            case Short:
+            case UnsignedShort:
+                ShortBuffer sin = (ShortBuffer) data;
+                sin.put(inPos + elementPos, (Short)val);
+                break;
+            case Int:
+            case UnsignedInt:
+                IntBuffer iin = (IntBuffer) data;
+                iin.put(inPos + elementPos, (Integer)val);
+                break;
+            case Float:
+                FloatBuffer fin = (FloatBuffer) data;
+                fin.put(inPos + elementPos, (Float)val);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+        }
+    }
+
+    /**
+     * Get the component inside an element.
+     * 
+     * @param elementIndex The element index
+     * @param componentIndex The component index
+     * @return The component, as one of the primitive types, byte, short,
+     * int or float.
+     */
+    public Object getElementComponent(int elementIndex, int componentIndex){
+        int inPos = elementIndex * components;
+        int elementPos = componentIndex;
+
+        if (format == Format.Half){
+            inPos *= 2;
+            elementPos *= 2;
+        }
+
+        Buffer srcData = getDataReadOnly();
+
+        switch (format){
+            case Byte:
+            case UnsignedByte:
+            case Half:
+                ByteBuffer bin = (ByteBuffer) srcData;
+                return bin.get(inPos + elementPos);
+            case Short:
+            case UnsignedShort:
+                ShortBuffer sin = (ShortBuffer) srcData;
+                return sin.get(inPos + elementPos);
+            case Int:
+            case UnsignedInt:
+                IntBuffer iin = (IntBuffer) srcData;
+                return iin.get(inPos + elementPos);
+            case Float:
+                FloatBuffer fin = (FloatBuffer) srcData;
+                return fin.get(inPos + elementPos);
+            default:
+                throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+        }
+    }
+
+    /**
+     * Copies a single element of data from this <code>VertexBuffer</code>
+     * to the given output VertexBuffer.
+     * 
+     * @param inIndex The input element index
+     * @param outVb The buffer to copy to
+     * @param outIndex The output element index
+     * 
+     * @throws IllegalArgumentException If the formats of the buffers do not
+     * match.
+     */
+    public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){
+        copyElements(inIndex, outVb, outIndex, 1);
+    }
+
+    /**
+     * Copies a sequence of elements of data from this <code>VertexBuffer</code>
+     * to the given output VertexBuffer.
+     * 
+     * @param inIndex The input element index
+     * @param outVb The buffer to copy to
+     * @param outIndex The output element index
+     * @param len The number of elements to copy
+     * 
+     * @throws IllegalArgumentException If the formats of the buffers do not
+     * match.
+     */
+    public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
+        if (outVb.format != format || outVb.components != components)
+            throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
+
+        int inPos  = inIndex  * components;
+        int outPos = outIndex * components;
+        int elementSz = components;
+        if (format == Format.Half){
+            // because half is stored as bytebuf but its 2 bytes long
+            inPos *= 2;
+            outPos *= 2;
+            elementSz *= 2;
+        }
+
+        // Make sure to grab a read-only copy in case some other
+        // thread is also accessing the buffer and messing with its
+        // position()
+        Buffer srcData = getDataReadOnly();
+        outVb.data.clear();
+
+        switch (format){
+            case Byte:
+            case UnsignedByte:
+            case Half:
+                ByteBuffer bin = (ByteBuffer) srcData;
+                ByteBuffer bout = (ByteBuffer) outVb.data;
+                bin.position(inPos).limit(inPos + elementSz * len);
+                bout.position(outPos).limit(outPos + elementSz * len);
+                bout.put(bin);                        
+                break;
+            case Short:
+            case UnsignedShort:
+                ShortBuffer sin = (ShortBuffer) srcData;
+                ShortBuffer sout = (ShortBuffer) outVb.data;
+                sin.position(inPos).limit(inPos + elementSz * len);
+                sout.position(outPos).limit(outPos + elementSz * len);
+                sout.put(sin);
+                break;
+            case Int:
+            case UnsignedInt:
+                IntBuffer iin = (IntBuffer) srcData;
+                IntBuffer iout = (IntBuffer) outVb.data;
+                iin.position(inPos).limit(inPos + elementSz * len);
+                iout.position(outPos).limit(outPos + elementSz * len);
+                iout.put(iin);
+                break;
+            case Float:
+                FloatBuffer fin = (FloatBuffer) srcData;
+                FloatBuffer fout = (FloatBuffer) outVb.data;
+                fin.position(inPos).limit(inPos + elementSz * len);
+                fout.position(outPos).limit(outPos + elementSz * len);
+                fout.put(fin);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+        }
+
+        // Clear the output buffer to rewind it and reset its
+        // limit from where we shortened it above.
+        outVb.data.clear();
+    }
+
+    /**
+     * Creates a {@link Buffer} that satisfies the given type and size requirements
+     * of the parameters. The buffer will be of the type specified by
+     * {@link Format format} and would be able to contain the given number
+     * of elements with the given number of components in each element.
+     */
+    public static Buffer createBuffer(Format format, int components, int numElements){
+        if (components < 1 || components > 4)
+            throw new IllegalArgumentException("Num components must be between 1 and 4");
+
+        int total = numElements * components;
+
+        switch (format){
+            case Byte:
+            case UnsignedByte:
+                return BufferUtils.createByteBuffer(total);
+            case Half:
+                return BufferUtils.createByteBuffer(total * 2);
+            case Short:
+            case UnsignedShort:
+                return BufferUtils.createShortBuffer(total);
+            case Int:
+            case UnsignedInt:
+                return BufferUtils.createIntBuffer(total);
+            case Float:
+                return BufferUtils.createFloatBuffer(total);
+            case Double:
+                return BufferUtils.createDoubleBuffer(total);
+            default:
+                throw new UnsupportedOperationException("Unrecoginized buffer format: "+format);
+        }
+    }
+
+    /**
+     * Creates a deep clone of the {@link VertexBuffer}.
+     * 
+     * @return Deep clone of this buffer
+     */
+    @Override
+    public VertexBuffer clone(){
+        // NOTE: Superclass GLObject automatically creates shallow clone
+        // e.g re-use ID.
+        VertexBuffer vb = (VertexBuffer) super.clone();
+        vb.handleRef = new Object();
+        vb.id = -1;
+        if (data != null) {
+            // Make sure to pass a read-only buffer to clone so that
+            // the position information doesn't get clobbered by another
+            // reading thread during cloning (and vice versa) since this is
+            // a purely read-only operation.
+            vb.updateData(BufferUtils.clone(getDataReadOnly()));
+        }
+        
+        return vb;
+    }
+
+    /**
+     * Creates a deep clone of this VertexBuffer but overrides the
+     * {@link Type}.
+     * 
+     * @param overrideType The type of the cloned VertexBuffer
+     * @return A deep clone of the buffer
+     */
+    public VertexBuffer clone(Type overrideType){
+        VertexBuffer vb = new VertexBuffer(overrideType);
+        vb.components = components;
+        vb.componentsLength = componentsLength;
+        
+        // Make sure to pass a read-only buffer to clone so that
+        // the position information doesn't get clobbered by another
+        // reading thread during cloning (and vice versa) since this is
+        // a purely read-only operation.
+        vb.data = BufferUtils.clone(getDataReadOnly());
+        vb.format = format;
+        vb.handleRef = new Object();
+        vb.id = -1;
+        vb.normalized = normalized;
+        vb.offset = offset;
+        vb.stride = stride;
+        vb.updateNeeded = true;
+        vb.usage = usage;
+        return vb;
+    }
+
+    @Override
+    public String toString(){
+        String dataTxt = null;
+        if (data != null){
+            dataTxt = ", elements="+data.capacity();
+        }
+        return getClass().getSimpleName() + "[fmt="+format.name()
+                                            +", type="+bufType.name()
+                                            +", usage="+usage.name()
+                                            +dataTxt+"]";
+    }
+
+    @Override
+    public void resetObject() {
+//        assert this.id != -1;
+        this.id = -1;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((Renderer)rendererObject).deleteBuffer(this);
+    }
+
+    @Override
+    public NativeObject createDestructableClone(){
+        return new VertexBuffer(id);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(components, "components", 0);
+        oc.write(usage, "usage", Usage.Dynamic);
+        oc.write(bufType, "buffer_type", null);
+        oc.write(format, "format", Format.Float);
+        oc.write(normalized, "normalized", false);
+        oc.write(offset, "offset", 0);
+        oc.write(stride, "stride", 0);
+
+        String dataName = "data" + format.name();
+        Buffer roData = getDataReadOnly();
+        switch (format){
+            case Float:
+                oc.write((FloatBuffer) roData, dataName, null);
+                break;
+            case Short:
+            case UnsignedShort:
+                oc.write((ShortBuffer) roData, dataName, null);
+                break;
+            case UnsignedByte:
+            case Byte:
+            case Half:
+                oc.write((ByteBuffer) roData, dataName, null);
+                break;
+            case Int:
+            case UnsignedInt:
+                oc.write((IntBuffer) roData, dataName, null);
+                break;
+            default:
+                throw new IOException("Unsupported export buffer format: "+format);
+        }
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        components = ic.readInt("components", 0);
+        usage = ic.readEnum("usage", Usage.class, Usage.Dynamic);
+        bufType = ic.readEnum("buffer_type", Type.class, null);
+        format = ic.readEnum("format", Format.class, Format.Float);
+        normalized = ic.readBoolean("normalized", false);
+        offset = ic.readInt("offset", 0);
+        stride = ic.readInt("stride", 0);
+        componentsLength = components * format.getComponentSize();
+
+        String dataName = "data" + format.name();
+        switch (format){
+            case Float:
+                data = ic.readFloatBuffer(dataName, null);
+                break;
+            case Short:
+            case UnsignedShort:
+                data = ic.readShortBuffer(dataName, null);
+                break;
+            case UnsignedByte:
+            case Byte:
+            case Half:
+                data = ic.readByteBuffer(dataName, null);
+                break;
+            case Int:
+            case UnsignedInt:
+                data = ic.readIntBuffer(dataName, null);
+                break;
+            default:
+                throw new IOException("Unsupported import buffer format: "+format);
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AbstractControl.java b/engine/src/core/com/jme3/scene/control/AbstractControl.java
new file mode 100644
index 0000000..2887ec9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AbstractControl.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * An abstract implementation of the Control interface.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AbstractControl implements Control {
+
+    protected boolean enabled = true;
+    protected Spatial spatial;
+
+    public AbstractControl(){
+    }
+
+    public void setSpatial(Spatial spatial) {
+        if (this.spatial != null && spatial != null) {
+            throw new IllegalStateException("This control has already been added to a Spatial");
+        }   
+        this.spatial = spatial;
+    }
+    
+    public Spatial getSpatial(){
+        return spatial;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * To be implemented in subclass.
+     */
+    protected abstract void controlUpdate(float tpf);
+
+    /**
+     * To be implemented in subclass.
+     */
+    protected abstract void controlRender(RenderManager rm, ViewPort vp);
+
+    public void update(float tpf) {
+        if (!enabled)
+            return;
+
+        controlUpdate(tpf);
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (!enabled)
+            return;
+
+        controlRender(rm, vp);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(spatial, "spatial", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AreaUtils.java b/engine/src/core/com/jme3/scene/control/AreaUtils.java
new file mode 100644
index 0000000..47e7c5f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AreaUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.FastMath;
+
+/**
+ * <code>AreaUtils</code> is used to calculate the area of various objects, such as bounding volumes.  These
+ * functions are very loose approximations.
+ * @author Joshua Slack
+ * @version $Id: AreaUtils.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+
+public class AreaUtils {
+
+  /**
+   * calcScreenArea -- in Pixels
+   * Aproximates the screen area of a bounding volume.  If the volume isn't a
+   * BoundingSphere, BoundingBox, or OrientedBoundingBox 0 is returned.
+   *
+   * @param bound The bounds to calculate the volume from.
+   * @param distance The distance from camera to object.
+   * @param screenWidth The width of the screen.
+   * @return The area in pixels on the screen of the bounding volume.
+   */
+  public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) {
+      if (bound.getType() == BoundingVolume.Type.Sphere){
+          return calcScreenArea((BoundingSphere) bound, distance, screenWidth);
+      }else if (bound.getType() == BoundingVolume.Type.AABB){
+          return calcScreenArea((BoundingBox) bound, distance, screenWidth);
+      }
+      return 0.0f;
+  }
+
+  private static float calcScreenArea(BoundingSphere bound, float distance, float screenWidth) {
+    // Where is the center point and a radius point that lies in a plan parallel to the view plane?
+//    // Calc radius based on these two points and plug into circle area formula.
+//    Vector2f centerSP = null;
+//    Vector2f outerSP = null;
+//    float radiusSq = centerSP.subtract(outerSP).lengthSquared();
+      float radius = (bound.getRadius() * screenWidth) / (distance * 2);
+      return radius * radius * FastMath.PI;
+  }
+
+  private static float calcScreenArea(BoundingBox bound, float distance, float screenWidth) {
+      // Calc as if we are a BoundingSphere for now...
+      float radiusSquare = bound.getXExtent() * bound.getXExtent()
+                         + bound.getYExtent() * bound.getYExtent()
+                         + bound.getZExtent() * bound.getZExtent();
+      return ((radiusSquare * screenWidth * screenWidth) / (distance * distance * 4)) * FastMath.PI;
+  }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/BillboardControl.java b/engine/src/core/com/jme3/scene/control/BillboardControl.java
new file mode 100644
index 0000000..40c2fea
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/BillboardControl.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+public class BillboardControl extends AbstractControl {
+
+    private Matrix3f orient;
+    private Vector3f look;
+    private Vector3f left;
+    private Alignment alignment;
+
+    /**
+     * Determines how the billboard is aligned to the screen/camera.
+     */
+    public enum Alignment {
+        /**
+         * Aligns this Billboard to the screen.
+         */
+        Screen,
+
+        /**
+         * Aligns this Billboard to the camera position.
+         */
+        Camera,
+
+        /**
+          * Aligns this Billboard to the screen, but keeps the Y axis fixed.
+          */
+        AxialY,
+
+        /**
+         * Aligns this Billboard to the screen, but keeps the Z axis fixed.
+         */
+        AxialZ;
+    }
+
+    public BillboardControl() {
+        super();
+        orient = new Matrix3f();
+        look = new Vector3f();
+        left = new Vector3f();
+        alignment = Alignment.Screen;
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        BillboardControl control = new BillboardControl();
+        control.alignment = this.alignment;
+        control.setSpatial(spatial);
+        return control;
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        Camera cam = vp.getCamera();
+        rotateBillboard(cam);
+    }
+    
+    private void fixRefreshFlags(){
+        // force transforms to update below this node
+        spatial.updateGeometricState();
+        
+        // force world bound to update
+        Spatial rootNode = spatial;
+        while (rootNode.getParent() != null){
+            rootNode = rootNode.getParent();
+        }
+        rootNode.getWorldBound(); 
+    }
+
+    /**
+     * rotate the billboard based on the type set
+     *
+     * @param cam
+     *            Camera
+     */
+    private void rotateBillboard(Camera cam) {
+        switch (alignment) {
+            case AxialY:
+                rotateAxial(cam, Vector3f.UNIT_Y);
+                break;
+            case AxialZ:
+                rotateAxial(cam, Vector3f.UNIT_Z);
+                break;
+            case Screen:
+                rotateScreenAligned(cam);
+                break;
+            case Camera:
+                rotateCameraAligned(cam);
+                break;
+        }
+    }
+
+    /**
+     * Aligns this Billboard so that it points to the camera position.
+     *
+     * @param camera
+     *            Camera
+     */
+    private void rotateCameraAligned(Camera camera) {
+        look.set(camera.getLocation()).subtractLocal(
+                spatial.getWorldTranslation());
+        // coopt left for our own purposes.
+        Vector3f xzp = left;
+        // The xzp vector is the projection of the look vector on the xz plane
+        xzp.set(look.x, 0, look.z);
+
+        // check for undefined rotation...
+        if (xzp.equals(Vector3f.ZERO)) {
+            return;
+        }
+
+        look.normalizeLocal();
+        xzp.normalizeLocal();
+        float cosp = look.dot(xzp);
+
+        // compute the local orientation matrix for the billboard
+        orient.set(0, 0, xzp.z);
+        orient.set(0, 1, xzp.x * -look.y);
+        orient.set(0, 2, xzp.x * cosp);
+        orient.set(1, 0, 0);
+        orient.set(1, 1, cosp);
+        orient.set(1, 2, look.y);
+        orient.set(2, 0, -xzp.x);
+        orient.set(2, 1, xzp.z * -look.y);
+        orient.set(2, 2, xzp.z * cosp);
+
+        // The billboard must be oriented to face the camera before it is
+        // transformed into the world.
+        spatial.setLocalRotation(orient);
+        fixRefreshFlags();
+    }
+
+    /**
+     * Rotate the billboard so it points directly opposite the direction the
+     * camera's facing
+     *
+     * @param camera
+     *            Camera
+     */
+    private void rotateScreenAligned(Camera camera) {
+        // coopt diff for our in direction:
+        look.set(camera.getDirection()).negateLocal();
+        // coopt loc for our left direction:
+        left.set(camera.getLeft()).negateLocal();
+        orient.fromAxes(left, camera.getUp(), look);
+        Node parent = spatial.getParent();
+        Quaternion rot=new Quaternion().fromRotationMatrix(orient);
+        if ( parent != null ) {
+            rot =  parent.getWorldRotation().inverse().multLocal(rot);
+            rot.normalizeLocal();
+        }
+        spatial.setLocalRotation(rot);
+        fixRefreshFlags();
+    }
+
+    /**
+     * Rotate the billboard towards the camera, but keeping a given axis fixed.
+     *
+     * @param camera
+     *            Camera
+     */
+    private void rotateAxial(Camera camera, Vector3f axis) {
+        // Compute the additional rotation required for the billboard to face
+        // the camera. To do this, the camera must be inverse-transformed into
+        // the model space of the billboard.
+        look.set(camera.getLocation()).subtractLocal(
+                spatial.getWorldTranslation());   
+        spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own
+        // purposes.
+        left.x *= 1.0f / spatial.getWorldScale().x;
+        left.y *= 1.0f / spatial.getWorldScale().y;
+        left.z *= 1.0f / spatial.getWorldScale().z;
+
+        // squared length of the camera projection in the xz-plane
+        float lengthSquared = left.x * left.x + left.z * left.z;
+        if (lengthSquared < FastMath.FLT_EPSILON) {
+            // camera on the billboard axis, rotation not defined
+            return;
+        }
+
+        // unitize the projection
+        float invLength = FastMath.invSqrt(lengthSquared);
+        if (axis.y == 1) {
+            left.x *= invLength;
+            left.y = 0.0f;
+            left.z *= invLength;
+
+            // compute the local orientation matrix for the billboard
+            orient.set(0, 0, left.z);
+            orient.set(0, 1, 0);
+            orient.set(0, 2, left.x);
+            orient.set(1, 0, 0);
+            orient.set(1, 1, 1);
+            orient.set(1, 2, 0);
+            orient.set(2, 0, -left.x);
+            orient.set(2, 1, 0);
+            orient.set(2, 2, left.z);
+        } else if (axis.z == 1) {
+            left.x *= invLength;
+            left.y *= invLength;
+            left.z = 0.0f;
+
+            // compute the local orientation matrix for the billboard
+            orient.set(0, 0, left.y);
+            orient.set(0, 1, left.x);
+            orient.set(0, 2, 0);
+            orient.set(1, 0, -left.y);
+            orient.set(1, 1, left.x);
+            orient.set(1, 2, 0);
+            orient.set(2, 0, 0);
+            orient.set(2, 1, 0);
+            orient.set(2, 2, 1);
+        }
+
+        // The billboard must be oriented to face the camera before it is
+        // transformed into the world.
+        spatial.setLocalRotation(orient);
+        fixRefreshFlags();
+    }
+
+    /**
+     * Returns the alignment this Billboard is set too.
+     *
+     * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen.
+     */
+    public Alignment getAlignment() {
+        return alignment;
+    }
+
+    /**
+     * Sets the type of rotation this Billboard will have. The alignment can
+     * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will
+     * assume no billboard rotation.
+     */
+    public void setAlignment(Alignment alignment) {
+        this.alignment = alignment;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(orient, "orient", null);
+        capsule.write(look, "look", null);
+        capsule.write(left, "left", null);
+        capsule.write(alignment, "alignment", Alignment.Screen);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        orient = (Matrix3f) capsule.readSavable("orient", null);
+        look = (Vector3f) capsule.readSavable("look", null);
+        left = (Vector3f) capsule.readSavable("left", null);
+        alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen);
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/control/CameraControl.java b/engine/src/core/com/jme3/scene/control/CameraControl.java
new file mode 100644
index 0000000..5dd559c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/CameraControl.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class CameraControl extends AbstractControl {
+
+    public static enum ControlDirection {
+
+        /**
+         * Means, that the Camera's transform is "copied"
+         * to the Transform of the Spatial.
+         */
+        CameraToSpatial,
+        /**
+         * Means, that the Spatial's transform is "copied"
+         * to the Transform of the Camera.
+         */
+        SpatialToCamera;
+    }
+    private Camera camera;
+    private ControlDirection controlDir = ControlDirection.CameraToSpatial;
+
+    /**
+     * Constructor used for Serialization.
+     */
+    public CameraControl() {
+    }
+
+    /**
+     * @param camera The Camera to be synced.
+     */
+    public CameraControl(Camera camera) {
+        this.camera = camera;
+    }
+
+    /**
+     * @param camera The Camera to be synced.
+     */
+    public CameraControl(Camera camera, ControlDirection controlDir) {
+        this.camera = camera;
+        this.controlDir = controlDir;
+    }
+
+    public Camera getCamera() {
+        return camera;
+    }
+
+    public void setCamera(Camera camera) {
+        this.camera = camera;
+    }
+
+    public ControlDirection getControlDir() {
+        return controlDir;
+    }
+
+    public void setControlDir(ControlDirection controlDir) {
+        this.controlDir = controlDir;
+    }
+
+    // fields used, when inversing ControlDirection:
+    @Override
+    protected void controlUpdate(float tpf) {
+        if (spatial != null && camera != null) {
+            switch (controlDir) {
+                case SpatialToCamera:
+                    camera.setLocation(spatial.getWorldTranslation());
+                    camera.setRotation(spatial.getWorldRotation());
+                    break;
+                case CameraToSpatial:
+                    // set the localtransform, so that the worldtransform would be equal to the camera's transform.
+                    // Location:
+                    TempVars vars = TempVars.get();
+
+                    Vector3f vecDiff = vars.vect1.set(camera.getLocation()).subtractLocal(spatial.getWorldTranslation());
+                    spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+
+                    // Rotation:
+                    Quaternion worldDiff = vars.quat1.set(camera.getRotation()).subtractLocal(spatial.getWorldRotation());
+                    spatial.setLocalRotation(worldDiff.addLocal(spatial.getLocalRotation()));
+                    vars.release();
+                    break;
+            }
+        }
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        // nothing to do
+    }
+
+    @Override
+    public Control cloneForSpatial(Spatial newSpatial) {
+        CameraControl control = new CameraControl(camera, controlDir);
+        control.setSpatial(newSpatial);
+        control.setEnabled(isEnabled());
+        return control;
+    }
+    private static final String CONTROL_DIR_NAME = "controlDir";
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+                ControlDirection.class, ControlDirection.SpatialToCamera);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+                ControlDirection.SpatialToCamera);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/Control.java b/engine/src/core/com/jme3/scene/control/Control.java
new file mode 100644
index 0000000..b5b0057
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/Control.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.Savable;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+
+/**
+ * An interface for scene-graph controls. 
+ * <p>
+ * <code>Control</code>s are used to specify certain update and render logic
+ * for a {@link Spatial}. 
+ *
+ * @author Kirill Vainer
+ */
+public interface Control extends Savable {
+
+    /**
+     * Creates a clone of the Control, the given Spatial is the cloned
+     * version of the spatial to which this control is attached to.
+     * @param spatial
+     * @return A clone of this control for the spatial
+     */
+    public Control cloneForSpatial(Spatial spatial);
+
+    /**
+     * @param spatial the spatial to be controlled. This should not be called
+     * from user code.
+     */
+    public void setSpatial(Spatial spatial);
+
+    /**
+     * @param enabled Enable or disable the control. If disabled, update()
+     * should do nothing.
+     */
+    public void setEnabled(boolean enabled);
+
+    /**
+     * @return True if enabled, false otherwise.
+     * @see Control#setEnabled(boolean)
+     */
+    public boolean isEnabled();
+
+    /**
+     * Updates the control. This should not be called from user code.
+     * @param tpf Time per frame.
+     */
+    public void update(float tpf);
+
+    /**
+     * Should be called prior to queuing the spatial by the RenderManager. This
+     * should not be called from user code.
+     *
+     * @param rm
+     * @param vp
+     */
+    public void render(RenderManager rm, ViewPort vp);
+}
diff --git a/engine/src/core/com/jme3/scene/control/LightControl.java b/engine/src/core/com/jme3/scene/control/LightControl.java
new file mode 100644
index 0000000..2859c9b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LightControl.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class LightControl extends AbstractControl {
+
+    public static enum ControlDirection {
+
+        /**
+         * Means, that the Light's transform is "copied"
+         * to the Transform of the Spatial.
+         */
+        LightToSpatial,
+        /**
+         * Means, that the Spatial's transform is "copied"
+         * to the Transform of the light.
+         */
+        SpatialToLight;
+    }
+    private Light light;
+    private ControlDirection controlDir = ControlDirection.SpatialToLight;
+
+    /**
+     * Constructor used for Serialization.
+     */
+    public LightControl() {
+    }
+
+    /**
+     * @param light The light to be synced.
+     */
+    public LightControl(Light light) {
+        this.light = light;
+    }
+
+    /**
+     * @param light The light to be synced.
+     */
+    public LightControl(Light light, ControlDirection controlDir) {
+        this.light = light;
+        this.controlDir = controlDir;
+    }
+
+    public Light getLight() {
+        return light;
+    }
+
+    public void setLight(Light light) {
+        this.light = light;
+    }
+
+    public ControlDirection getControlDir() {
+        return controlDir;
+    }
+
+    public void setControlDir(ControlDirection controlDir) {
+        this.controlDir = controlDir;
+    }
+
+    // fields used, when inversing ControlDirection:
+    @Override
+    protected void controlUpdate(float tpf) {
+        if (spatial != null && light != null) {
+            switch (controlDir) {
+                case SpatialToLight:
+                    spatialTolight(light);
+                    break;
+                case LightToSpatial:
+                    lightToSpatial(light);
+                    break;
+            }
+        }
+    }
+
+    private void spatialTolight(Light light) {
+        if (light instanceof PointLight) {
+            ((PointLight) light).setPosition(spatial.getWorldTranslation());
+        }
+        TempVars vars = TempVars.get();
+
+        if (light instanceof DirectionalLight) {
+            ((DirectionalLight) light).setDirection(vars.vect1.set(spatial.getWorldTranslation()).multLocal(-1.0f));
+        }
+        vars.release();
+        //TODO add code for Spot light here when it's done
+//        if( light instanceof SpotLight){
+//            ((SpotLight)light).setPosition(spatial.getWorldTranslation());                
+//            ((SpotLight)light).setRotation(spatial.getWorldRotation());
+//        }
+
+    }
+
+    private void lightToSpatial(Light light) {
+        TempVars vars = TempVars.get();
+        if (light instanceof PointLight) {
+
+            PointLight pLight = (PointLight) light;
+
+            Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation());
+            spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+        }
+
+        if (light instanceof DirectionalLight) {
+            DirectionalLight dLight = (DirectionalLight) light;
+            vars.vect1.set(dLight.getDirection()).multLocal(-1.0f);
+            Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation());
+            spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+        }
+        vars.release();
+        //TODO add code for Spot light here when it's done
+
+
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        // nothing to do
+    }
+
+    @Override
+    public Control cloneForSpatial(Spatial newSpatial) {
+        LightControl control = new LightControl(light, controlDir);
+        control.setSpatial(newSpatial);
+        control.setEnabled(isEnabled());
+        return control;
+    }
+    private static final String CONTROL_DIR_NAME = "controlDir";
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+                ControlDirection.class, ControlDirection.SpatialToLight);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+                ControlDirection.SpatialToLight);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/LodControl.java b/engine/src/core/com/jme3/scene/control/LodControl.java
new file mode 100644
index 0000000..6cbfefb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LodControl.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Determines what Level of Detail a spatial should be, based on how many pixels
+ * on the screen the spatial is taking up. The more pixels covered, the more detailed
+ * the spatial should be.
+ * It calculates the area of the screen that the spatial covers by using its bounding box.
+ * When initializing, it will ask the spatial for how many triangles it has for each LOD.
+ * It then uses that, along with the trisPerPixel value to determine what LOD it should be at.
+ * It requires the camera to do this.
+ * The controlRender method is called each frame and will update the spatial's LOD
+ * if the camera has moved by a specified amount.
+ */
+public class LodControl extends AbstractControl implements Cloneable {
+
+    private float trisPerPixel = 1f;
+    private float distTolerance = 1f;
+    private float lastDistance = 0f;
+    private int lastLevel = 0;
+    private int numLevels;
+    private int[] numTris;
+
+    /**
+     * Creates a new <code>LodControl</code>.
+     */
+    public LodControl(){
+    }
+
+    /**
+     * Returns the distance tolerance for changing LOD.
+     * 
+     * @return the distance tolerance for changing LOD.
+     * 
+     * @see #setDistTolerance(float) 
+     */
+    public float getDistTolerance() {
+        return distTolerance;
+    }
+
+    /**
+     * Specifies the distance tolerance for changing the LOD level on the geometry.
+     * The LOD level will only get changed if the geometry has moved this 
+     * distance beyond the current LOD level.
+     * 
+     * @param distTolerance distance tolerance for changing LOD
+     */
+    public void setDistTolerance(float distTolerance) {
+        this.distTolerance = distTolerance;
+    }
+
+    /**
+     * Returns the triangles per pixel value.
+     * 
+     * @return the triangles per pixel value.
+     * 
+     * @see #setTrisPerPixel(float) 
+     */
+    public float getTrisPerPixel() {
+        return trisPerPixel;
+    }
+
+    /**
+     * Sets the triangles per pixel value.
+     * The <code>LodControl</code> will use this value as an error metric
+     * to determine which LOD level to use based on the geometry's
+     * area on the screen.
+     * 
+     * @param trisPerPixel triangles per pixel
+     */
+    public void setTrisPerPixel(float trisPerPixel) {
+        this.trisPerPixel = trisPerPixel;
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial){
+        if (!(spatial instanceof Geometry))
+            throw new IllegalArgumentException("LodControl can only be attached to Geometry!");
+
+        super.setSpatial(spatial);
+        Geometry geom = (Geometry) spatial;
+        Mesh mesh = geom.getMesh();
+        numLevels = mesh.getNumLodLevels();
+        numTris = new int[numLevels];
+        for (int i = numLevels - 1; i >= 0; i--)
+            numTris[i] = mesh.getTriangleCount(i);
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        try {
+            LodControl clone = (LodControl) super.clone();
+            clone.lastDistance = 0;
+            clone.lastLevel = 0;
+            clone.numTris = numTris != null ? numTris.clone() : null;
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+    }
+
+    protected void controlRender(RenderManager rm, ViewPort vp){
+        BoundingVolume bv = spatial.getWorldBound();
+
+        Camera cam = vp.getCamera();
+        float atanNH = FastMath.atan(cam.getFrustumNear() * cam.getFrustumTop());
+        float ratio = (FastMath.PI / (8f * atanNH));
+        float newDistance = bv.distanceTo(vp.getCamera().getLocation()) / ratio;
+        int level;
+
+        if (Math.abs(newDistance - lastDistance) <= distTolerance)
+            level = lastLevel; // we haven't moved relative to the model, send the old measurement back.
+        else if (lastDistance > newDistance && lastLevel == 0)
+            level = lastLevel; // we're already at the lowest setting and we just got closer to the model, no need to keep trying.
+        else if (lastDistance < newDistance && lastLevel == numLevels - 1)
+            level = lastLevel; // we're already at the highest setting and we just got further from the model, no need to keep trying.
+        else{
+            lastDistance = newDistance;
+
+            // estimate area of polygon via bounding volume
+            float area = AreaUtils.calcScreenArea(bv, lastDistance, cam.getWidth());
+            float trisToDraw = area * trisPerPixel;
+            level = numLevels - 1;
+            for (int i = numLevels; --i >= 0;){
+                if (trisToDraw - numTris[i] < 0){
+                    break;
+                }
+                level = i;
+            }
+            lastLevel = level;
+        }
+
+        spatial.setLodLevel(level);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException{
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(trisPerPixel, "trisPerPixel", 1f);
+        oc.write(distTolerance, "distTolerance", 1f);
+        oc.write(numLevels, "numLevels", 0);
+        oc.write(numTris, "numTris", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException{
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        trisPerPixel = ic.readFloat("trisPerPixel", 1f);
+        distTolerance = ic.readFloat("distTolerance", 1f);
+        numLevels = ic.readInt("numLevels", 0);
+        numTris = ic.readIntArray("numTris", null);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/UpdateControl.java b/engine/src/core/com/jme3/scene/control/UpdateControl.java
new file mode 100644
index 0000000..7fbca01
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/UpdateControl.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.app.AppTask;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+
+/**
+ * Allows for enqueueing tasks onto the update loop / rendering thread.
+ * 
+ * Usage:
+ * mySpatial.addControl(new UpdateControl()); // add it once
+ * mySpatial.getControl(UpdateControl.class).enqueue(new Callable() {
+ *        public Object call() throws Exception {
+ *            // do stuff here
+ *            return null;
+ *        }
+ *    });
+ * 
+ * @author Brent Owens
+ */
+public class UpdateControl extends AbstractControl {
+
+    private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+
+    /**
+     * Enqueues a task/callable object to execute in the jME3
+     * rendering thread.
+     */
+    public <V> Future<V> enqueue(Callable<V> callable) {
+        AppTask<V> task = new AppTask<V>(callable);
+        taskQueue.add(task);
+        return task;
+    }
+    
+    @Override
+    protected void controlUpdate(float tpf) {
+        AppTask<?> task = taskQueue.poll();
+        toploop: do {
+            if (task == null) break;
+            while (task.isCancelled()) {
+                task = taskQueue.poll();
+                if (task == null) break toploop;
+            }
+            task.invoke();
+        } while (((task = taskQueue.poll()) != null));
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        
+    }
+
+    public Control cloneForSpatial(Spatial newSpatial) {
+        UpdateControl control = new UpdateControl(); 
+        control.setSpatial(newSpatial);
+        control.setEnabled(isEnabled());
+        control.taskQueue.addAll(taskQueue);
+        return control;
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/scene/control/package.html b/engine/src/core/com/jme3/scene/control/package.html
new file mode 100644
index 0000000..a387840
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.control</code> package provides 
+{@link com.jme3.scene.control.Control controls}. 
+Controls represent the "logical" programming of scene graph elements, containing
+callbacks for when a {@link com.jme3.scene.Spatial} is rendered or updated
+by the engine.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/debug/Arrow.java b/engine/src/core/com/jme3/scene/debug/Arrow.java
new file mode 100644
index 0000000..1c3dd94
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Arrow.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.debug;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.FloatBuffer;
+
+/**
+ * The <code>Arrow</code> debug shape represents an arrow.
+ * An arrow is simply a line going from the original toward an extent
+ * and at the tip there will be triangle-like shape.
+ * 
+ * @author Kirill Vainer
+ */
+public class Arrow extends Mesh {
+    
+    private Quaternion tempQuat = new Quaternion();
+    private Vector3f tempVec = new Vector3f();
+
+    private static final float[] positions = new float[]{
+        0, 0, 0,
+        0, 0, 1, // tip
+        0.05f, 0, 0.9f, // tip right
+        -0.05f, 0, 0.9f, // tip left
+        0, 0.05f, 0.9f, // tip top
+        0, -0.05f, 0.9f, // tip buttom
+    };
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Arrow() {
+    }
+
+    /**
+     * Creates an arrow mesh with the given extent.
+     * The arrow will start at the origin (0,0,0) and finish
+     * at the given extent.
+     * 
+     * @param extent Extent of the arrow from origin
+     */
+    public Arrow(Vector3f extent) {
+        float len = extent.length();
+        Vector3f dir = extent.normalize();
+
+        tempQuat.lookAt(dir, Vector3f.UNIT_Y);
+        tempQuat.normalizeLocal();
+
+        float[] newPositions = new float[positions.length];
+        for (int i = 0; i < positions.length; i += 3) {
+            Vector3f vec = tempVec.set(positions[i],
+                    positions[i + 1],
+                    positions[i + 2]);
+            vec.multLocal(len);
+            tempQuat.mult(vec, vec);
+
+            newPositions[i] = vec.getX();
+            newPositions[i + 1] = vec.getY();
+            newPositions[i + 2] = vec.getZ();
+        }
+
+        setBuffer(Type.Position, 3, newPositions);
+        setBuffer(Type.Index, 2,
+                new short[]{
+                    0, 1,
+                    1, 2,
+                    1, 3,
+                    1, 4,
+                    1, 5,});
+        setMode(Mode.Lines);
+
+        updateBound();
+        updateCounts();
+    }
+
+    /**
+     * Sets the arrow's extent.
+     * This will modify the buffers on the mesh.
+     * 
+     * @param extent the arrow's extent.
+     */
+    public void setArrowExtent(Vector3f extent) {
+        float len = extent.length();
+//        Vector3f dir = extent.normalize();
+
+        tempQuat.lookAt(extent, Vector3f.UNIT_Y);
+        tempQuat.normalizeLocal();
+
+        VertexBuffer pvb = getBuffer(Type.Position);
+        FloatBuffer buffer = (FloatBuffer)pvb.getData(); 
+        buffer.rewind();
+        for (int i = 0; i < positions.length; i += 3) {
+            Vector3f vec = tempVec.set(positions[i],
+                    positions[i + 1],
+                    positions[i + 2]);
+            vec.multLocal(len);
+            tempQuat.mult(vec, vec);
+
+            buffer.put(vec.x);
+            buffer.put(vec.y);
+            buffer.put(vec.z);
+        }
+        
+        pvb.updateData(buffer);
+
+        updateBound();
+        updateCounts();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/Grid.java b/engine/src/core/com/jme3/scene/debug/Grid.java
new file mode 100644
index 0000000..ea6225c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Grid.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Simple grid shape.
+ * 
+ * @author Kirill Vainer
+ */
+public class Grid extends Mesh {
+
+    /**
+     * Creates a grid debug shape.
+     * @param xLines
+     * @param yLines
+     * @param lineDist 
+     */
+    public Grid(int xLines, int yLines, float lineDist){
+        xLines -= 2;
+        yLines -= 2;
+        int lineCount = xLines + yLines + 4;
+
+        FloatBuffer fpb = BufferUtils.createFloatBuffer(6 * lineCount);
+        ShortBuffer sib = BufferUtils.createShortBuffer(2 * lineCount);
+
+        float xLineLen = (yLines + 1) * lineDist;
+        float yLineLen = (xLines + 1) * lineDist;
+        int curIndex = 0;
+
+        // add lines along X
+        for (int i = 0; i < xLines + 2; i++){
+            float y = (i) * lineDist;
+
+            // positions
+            fpb.put(0)       .put(0).put(y);
+            fpb.put(xLineLen).put(0).put(y);
+
+            // indices
+            sib.put( (short) (curIndex++) );
+            sib.put( (short) (curIndex++) );
+        }
+
+        // add lines along Y
+        for (int i = 0; i < yLines + 2; i++){
+            float x = (i) * lineDist;
+
+            // positions
+            fpb.put(x).put(0).put(0);
+            fpb.put(x).put(0).put(yLineLen);
+
+            // indices
+            sib.put( (short) (curIndex++) );
+            sib.put( (short) (curIndex++) );
+        }
+
+        fpb.flip();
+        sib.flip();
+
+        setBuffer(Type.Position, 3, fpb);
+        setBuffer(Type.Index, 2, sib);
+        
+        setMode(Mode.Lines);
+
+        updateBound();
+        updateCounts();
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
new file mode 100644
index 0000000..07efdc9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Skeleton;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+
+public class SkeletonDebugger extends Node {
+
+    private SkeletonWire wires;
+    private SkeletonPoints points;
+    private Skeleton skeleton;
+
+    public SkeletonDebugger(String name, Skeleton skeleton){
+        super(name);
+
+        this.skeleton = skeleton;
+        wires = new SkeletonWire(skeleton);
+        points = new SkeletonPoints(skeleton);
+
+        attachChild(new Geometry(name+"_wires", wires));
+        attachChild(new Geometry(name+"_points", points));
+
+        setQueueBucket(Bucket.Transparent);
+    }
+
+    public SkeletonDebugger(){
+    }
+
+    @Override
+    public void updateLogicalState(float tpf){
+        super.updateLogicalState(tpf);
+
+//        skeleton.resetAndUpdate();
+        wires.updateGeometry();
+        points.updateGeometry();
+    }
+
+    public SkeletonPoints getPoints() {
+        return points;
+    }
+
+    public SkeletonWire getWires() {
+        return wires;
+    }
+    
+    
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
new file mode 100644
index 0000000..2e49ce5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class SkeletonPoints extends Mesh {
+
+    private Skeleton skeleton;
+
+    public SkeletonPoints(Skeleton skeleton){
+        this.skeleton = skeleton;
+
+        setMode(Mode.Points);
+
+        VertexBuffer pb = new VertexBuffer(Type.Position);
+        FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+        pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+        setBuffer(pb);
+
+        setPointSize(7);
+
+        updateCounts();
+    }
+
+    public void updateGeometry(){
+        VertexBuffer vb = getBuffer(Type.Position);
+        FloatBuffer posBuf = getFloatBuffer(Type.Position);
+        posBuf.clear();
+        for (int i = 0; i < skeleton.getBoneCount(); i++){
+            Bone bone = skeleton.getBone(i);
+            Vector3f bonePos = bone.getModelSpacePosition();
+
+            posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+        }
+        posBuf.flip();
+        vb.updateData(posBuf);
+
+        updateBound();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonWire.java b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
new file mode 100644
index 0000000..0796334
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class SkeletonWire extends Mesh {
+
+    private int numConnections = 0;
+    private Skeleton skeleton;
+
+    private void countConnections(Bone bone){
+        for (Bone child : bone.getChildren()){
+            numConnections ++;
+            countConnections(child);
+        }
+    }
+
+    private void writeConnections(ShortBuffer indexBuf, Bone bone){
+        for (Bone child : bone.getChildren()){
+            // write myself
+            indexBuf.put( (short) skeleton.getBoneIndex(bone) );
+            // write the child
+            indexBuf.put( (short) skeleton.getBoneIndex(child) );
+
+            writeConnections(indexBuf, child);
+        }
+    }
+
+    public SkeletonWire(Skeleton skeleton){
+        this.skeleton = skeleton;
+        for (Bone bone : skeleton.getRoots())
+            countConnections(bone);
+
+        setMode(Mode.Lines);
+
+        VertexBuffer pb = new VertexBuffer(Type.Position);
+        FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+        pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+        setBuffer(pb);
+
+        VertexBuffer ib = new VertexBuffer(Type.Index);
+        ShortBuffer sib = BufferUtils.createShortBuffer(numConnections * 2);
+        ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib);
+        setBuffer(ib);
+
+        for (Bone bone : skeleton.getRoots())
+            writeConnections(sib, bone);
+        sib.flip();
+
+        updateCounts();
+    }
+
+    public void updateGeometry(){
+        VertexBuffer vb = getBuffer(Type.Position);
+        FloatBuffer posBuf = getFloatBuffer(Type.Position);
+        posBuf.clear();
+        for (int i = 0; i < skeleton.getBoneCount(); i++){
+            Bone bone = skeleton.getBone(i);
+            Vector3f bonePos = bone.getModelSpacePosition();
+
+            posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+        }
+        posBuf.flip();
+        vb.updateData(posBuf);
+
+        updateBound();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireBox.java b/engine/src/core/com/jme3/scene/debug/WireBox.java
new file mode 100644
index 0000000..50af28a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireBox.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireBox extends Mesh {
+
+    public WireBox(){
+        this(1,1,1);
+    }
+    
+    public WireBox(float xExt, float yExt, float zExt){
+        updatePositions(xExt,yExt,zExt);
+        setBuffer(Type.Index, 2,
+                new short[]{
+                     0, 1,
+                     1, 2,
+                     2, 3,
+                     3, 0,
+
+                     4, 5,
+                     5, 6,
+                     6, 7,
+                     7, 4,
+
+                     0, 4,
+                     1, 5,
+                     2, 6,
+                     3, 7,
+                }
+        );
+        setMode(Mode.Lines);
+
+        updateCounts();
+    }
+
+    public void updatePositions(float xExt, float yExt, float zExt){
+        VertexBuffer pvb = getBuffer(Type.Position);
+        FloatBuffer pb;
+        if (pvb == null){
+            pvb = new VertexBuffer(Type.Position);
+            pb = BufferUtils.createVector3Buffer(8);
+            pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);
+            setBuffer(pvb);
+        }else{
+            pb = (FloatBuffer) pvb.getData();
+            pvb.updateData(pb);
+        }
+        pb.rewind();
+        pb.put(
+            new float[]{
+                -xExt, -yExt,  zExt,
+                 xExt, -yExt,  zExt,
+                 xExt,  yExt,  zExt,
+                -xExt,  yExt,  zExt,
+
+                -xExt, -yExt, -zExt,
+                 xExt, -yExt, -zExt,
+                 xExt,  yExt, -zExt,
+                -xExt,  yExt, -zExt,
+            }
+        );
+        updateBound();
+    }
+
+    public void fromBoundingBox(BoundingBox bbox){
+        updatePositions(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireFrustum.java b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
new file mode 100644
index 0000000..7a86d90
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireFrustum extends Mesh {
+
+    public WireFrustum(Vector3f[] points){
+        if (points != null)
+            setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+
+        setBuffer(Type.Index, 2,
+                new short[]{
+                     0, 1,
+                     1, 2,
+                     2, 3,
+                     3, 0,
+
+                     4, 5,
+                     5, 6,
+                     6, 7,
+                     7, 4,
+
+                     0, 4,
+                     1, 5,
+                     2, 6,
+                     3, 7,
+                }
+        );
+        setMode(Mode.Lines);
+    }
+
+    public void update(Vector3f[] points){
+        VertexBuffer vb = getBuffer(Type.Position);
+        if (vb == null){
+            setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+            return;
+        }
+
+        FloatBuffer b = BufferUtils.createFloatBuffer(points);
+        FloatBuffer a = (FloatBuffer) vb.getData();
+        b.rewind();
+        a.rewind();
+        a.put(b);
+        a.rewind();
+
+        vb.updateData(a);
+        
+        updateBound();
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireSphere.java b/engine/src/core/com/jme3/scene/debug/WireSphere.java
new file mode 100644
index 0000000..df863e2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireSphere.java
@@ -0,0 +1,159 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.scene.debug;

+

+import com.jme3.bounding.BoundingSphere;

+import com.jme3.math.FastMath;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Mesh.Mode;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Format;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.VertexBuffer.Usage;

+import com.jme3.util.BufferUtils;

+import java.nio.FloatBuffer;

+import java.nio.ShortBuffer;

+

+public class WireSphere extends Mesh {

+

+    private static final int samples = 30;

+    private static final int zSamples = 10;

+

+    public WireSphere() {

+        this(1);

+    }

+

+    public WireSphere(float radius) {

+        updatePositions(radius);

+        ShortBuffer ib = BufferUtils.createShortBuffer(samples * 2 * 2 + zSamples * samples * 2 /*+ 3 * 2*/);

+        setBuffer(Type.Index, 2, ib);

+

+//        ib.put(new byte[]{

+//            (byte) 0, (byte) 1,

+//            (byte) 2, (byte) 3,

+//            (byte) 4, (byte) 5,

+//        });

+

+//        int curNum = 3 * 2;

+        int curNum = 0;

+        for (int j = 0; j < 2 + zSamples; j++) {

+            for (int i = curNum; i < curNum + samples - 1; i++) {

+                ib.put((short) i).put((short) (i + 1));

+            }

+            ib.put((short) (curNum + samples - 1)).put((short) curNum);

+            curNum += samples;

+        }

+

+        setMode(Mode.Lines);

+

+        updateBound();

+        updateCounts();

+    }

+

+    public void updatePositions(float radius) {

+        VertexBuffer pvb = getBuffer(Type.Position);

+        FloatBuffer pb;

+

+        if (pvb == null) {

+            pvb = new VertexBuffer(Type.Position);

+            pb = BufferUtils.createVector3Buffer(samples * 2 + samples * zSamples /*+ 6 * 3*/);

+            pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);

+            setBuffer(pvb);

+        } else {

+            pb = (FloatBuffer) pvb.getData();

+        }

+

+        pb.rewind();

+

+        // X axis

+//        pb.put(radius).put(0).put(0);

+//        pb.put(-radius).put(0).put(0);

+//

+//        // Y axis

+//        pb.put(0).put(radius).put(0);

+//        pb.put(0).put(-radius).put(0);

+//

+//        // Z axis

+//        pb.put(0).put(0).put(radius);

+//        pb.put(0).put(0).put(-radius);

+

+        float rate = FastMath.TWO_PI / (float) samples;

+        float angle = 0;

+        for (int i = 0; i < samples; i++) {

+            float x = radius * FastMath.cos(angle);

+            float y = radius * FastMath.sin(angle);

+            pb.put(x).put(y).put(0);

+            angle += rate;

+        }

+

+        angle = 0;

+        for (int i = 0; i < samples; i++) {

+            float x = radius * FastMath.cos(angle);

+            float y = radius * FastMath.sin(angle);

+            pb.put(0).put(x).put(y);

+            angle += rate;

+        }

+

+        float zRate = (radius * 2) / (float) (zSamples);

+        float zHeight = -radius + (zRate / 2f);

+

+

+        float rb = 1f / zSamples;

+        float b = rb / 2f;

+

+        for (int k = 0; k < zSamples; k++) {

+            angle = 0;

+            float scale = FastMath.sin(b * FastMath.PI);

+            for (int i = 0; i < samples; i++) {

+                float x = radius * FastMath.cos(angle);

+                float y = radius * FastMath.sin(angle);

+

+                pb.put(x * scale).put(zHeight).put(y * scale);

+

+                angle += rate;

+            }

+            zHeight += zRate;

+            b += rb;

+        }

+    }

+

+    /**

+     * Create a WireSphere from a BoundingSphere

+     *

+     * @param bsph

+     *     BoundingSphere used to create the WireSphere

+     *

+     */

+    public void fromBoundingSphere(BoundingSphere bsph) {

+        updatePositions(bsph.getRadius());

+    }

+}

diff --git a/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
new file mode 100644
index 0000000..5c41c04
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import com.jme3.util.BufferUtils;
+import java.nio.Buffer;
+
+/**
+ * <code>IndexBuffer</code> is an abstraction for integer index buffers,
+ * it is used to retrieve indices without knowing in which format they 
+ * are stored (ushort or uint).
+ *
+ * @author lex
+ */
+public abstract class IndexBuffer {
+    
+    /**
+     * Creates an index buffer that can contain the given amount
+     * of vertices.
+     * Returns {@link IndexShortBuffer}
+     * 
+     * @param vertexCount The amount of vertices to contain
+     * @param indexCount The amount of indices
+     * to contain.
+     * @return A new index buffer
+     */
+    public static IndexBuffer createIndexBuffer(int vertexCount, int indexCount){
+        if (vertexCount > 65535){
+            return new IndexIntBuffer(BufferUtils.createIntBuffer(indexCount));
+        }else{
+            return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount));
+        }
+    }
+    
+    /**
+     * Returns the vertex index for the given index in the index buffer.
+     * 
+     * @param i The index inside the index buffer
+     * @return 
+     */
+    public abstract int get(int i);
+    
+    /**
+     * Puts the vertex index at the index buffer's index.
+     * Implementations may throw an {@link UnsupportedOperationException}
+     * if modifying the IndexBuffer is not supported (e.g. virtual index
+     * buffers).
+     */
+    public abstract void put(int i, int value);
+    
+    /**
+     * Returns the size of the index buffer.
+     * 
+     * @return the size of the index buffer.
+     */
+    public abstract int size();
+    
+    /**
+     * Returns the underlying data-type specific {@link Buffer}.
+     * Implementations may return null if there's no underlying
+     * buffer.
+     * 
+     * @return the underlying {@link Buffer}.
+     */
+    public abstract Buffer getBuffer();
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
new file mode 100644
index 0000000..fd3bec4
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ByteBuffer}s.
+ * 
+ * @author lex
+ */
+public class IndexByteBuffer extends IndexBuffer {
+
+    private ByteBuffer buf;
+
+    public IndexByteBuffer(ByteBuffer buffer) {
+        this.buf = buffer;
+    }
+    
+    @Override
+    public int get(int i) {
+        return buf.get(i) & 0x000000FF;
+    }
+
+    @Override
+    public void put(int i, int value) {
+        buf.put(i, (byte) value);
+    }
+
+    @Override
+    public int size() {
+        return buf.limit();
+    }
+
+    @Override
+    public Buffer getBuffer() {
+        return buf;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
new file mode 100644
index 0000000..3369301
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+/**
+ * IndexBuffer implementation for {@link IntBuffer}s.
+ * 
+ * @author lex
+ */
+public class IndexIntBuffer extends IndexBuffer {
+
+    private IntBuffer buf;
+
+    public IndexIntBuffer(IntBuffer buffer) {
+        this.buf = buffer;
+    }
+
+    @Override
+    public int get(int i) {
+        return buf.get(i);
+    }
+
+    @Override
+    public void put(int i, int value) {
+        buf.put(i, value);
+    }
+
+    @Override
+    public int size() {
+        return buf.limit();
+    }
+
+    @Override
+    public Buffer getBuffer() {
+        return buf;
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
new file mode 100644
index 0000000..5017e6f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ShortBuffer}s.
+ * 
+ * @author lex
+ */
+public class IndexShortBuffer extends IndexBuffer {
+
+    private ShortBuffer buf;
+
+    public IndexShortBuffer(ShortBuffer buffer) {
+        this.buf = buffer;
+    }
+
+    @Override
+    public int get(int i) {
+        return buf.get(i) & 0x0000FFFF;
+    }
+
+    @Override
+    public void put(int i, int value) {
+        buf.put(i, (short) value);
+    }
+
+    @Override
+    public int size() {
+        return buf.limit();
+    }
+
+    @Override
+    public Buffer getBuffer() {
+        return buf;
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
new file mode 100644
index 0000000..c1499cc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
@@ -0,0 +1,106 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh.Mode;
+import java.nio.Buffer;
+
+/**
+ * IndexBuffer implementation that generates vertex indices sequentially
+ * based on a specific Mesh {@link Mode}.
+ * The generated indices are as if the mesh is in the given mode
+ * but contains no index buffer, thus this implementation will
+ * return the indices if the index buffer was there and contained sequential
+ * triangles.
+ * Example:
+ * <ul>
+ * <li>{@link Mode#Triangles}: 0, 1, 2 | 3, 4, 5 | 6, 7, 8 | ...</li>
+ * <li>{@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...</li>
+ * <li>{@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...</li>
+ * </ul>
+ * 
+ * @author Kirill Vainer
+ */
+public class VirtualIndexBuffer extends IndexBuffer {
+
+    protected int numVerts = 0;
+    protected int numIndices = 0;
+    protected Mode meshMode;
+ 
+    public VirtualIndexBuffer(int numVerts, Mode meshMode){
+        this.numVerts = numVerts;
+        this.meshMode = meshMode;
+        switch (meshMode) {
+            case Points:
+                numIndices = numVerts;
+                return;
+            case LineLoop:
+                numIndices = (numVerts - 1) * 2 + 1;
+                return;
+            case LineStrip:
+                numIndices = (numVerts - 1) * 2;
+                return;
+            case Lines:
+                numIndices = numVerts;
+                return;
+            case TriangleFan:
+                numIndices = (numVerts - 2) * 3;
+                return;
+            case TriangleStrip:
+                numIndices = (numVerts - 2) * 3;
+                return;
+            case Triangles:
+                numIndices = numVerts;
+                return;
+            case Hybrid:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public int get(int i) {
+        if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){
+            return i;
+        }else if (meshMode == Mode.LineStrip){
+            return (i + 1) / 2;
+        }else if (meshMode == Mode.LineLoop){
+            return (i == (numVerts-1)) ? 0 : ((i + 1) / 2);
+        }else if (meshMode == Mode.TriangleStrip){
+           int triIndex   = i/3;
+           int vertIndex  = i%3;
+           boolean isBack = (i/3)%2==1;
+           if (!isBack){
+                return triIndex + vertIndex;
+           }else{
+               switch (vertIndex){
+                   case 0: return triIndex + 1;
+                   case 1: return triIndex;
+                   case 2: return triIndex + 2;
+                   default: throw new AssertionError();
+               }
+            }
+        }else if (meshMode == Mode.TriangleFan){
+            int vertIndex = i%3;
+            if (vertIndex == 0)
+                return 0;
+            else
+                return (i / 3) + vertIndex;
+        }else{
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public void put(int i, int value) {
+        throw new UnsupportedOperationException("Does not represent index buffer");
+    }
+
+    @Override
+    public int size() {
+        return numIndices;
+    }
+
+    @Override
+    public Buffer getBuffer() {
+        return null;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
new file mode 100644
index 0000000..056a1cd
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
@@ -0,0 +1,86 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * <code>WrappedIndexBuffer</code> converts vertex indices from a non list based
+ * mesh mode such as {@link Mode#TriangleStrip} or {@link Mode#LineLoop}
+ * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}.
+ * As it is often more convenient to read vertex data in list format
+ * than in a non-list format, using this class is recommended to avoid
+ * convoluting classes used to process mesh data from an external source.
+ * 
+ * @author Kirill Vainer
+ */
+public class WrappedIndexBuffer extends VirtualIndexBuffer {
+
+    private final IndexBuffer ib;
+
+    public WrappedIndexBuffer(Mesh mesh){
+        super(mesh.getVertexCount(), mesh.getMode());
+        this.ib = mesh.getIndexBuffer();
+        switch (meshMode){
+            case Points:
+                numIndices = mesh.getTriangleCount();
+                break;
+            case Lines:
+            case LineLoop:
+            case LineStrip:
+                numIndices = mesh.getTriangleCount() * 2;
+                break;
+            case Triangles:
+            case TriangleStrip:
+            case TriangleFan:
+                numIndices = mesh.getTriangleCount() * 3;
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public int get(int i) {
+        int superIdx = super.get(i);
+        return ib.get(superIdx);
+    }
+
+    @Override
+    public Buffer getBuffer() {
+        return ib.getBuffer();
+    }
+    
+    public static void convertToList(Mesh mesh){
+        IndexBuffer inBuf = mesh.getIndicesAsList();
+        IndexBuffer outBuf = IndexBuffer.createIndexBuffer(mesh.getVertexCount(),
+                                                           inBuf.size());
+
+        for (int i = 0; i < inBuf.size(); i++){
+            outBuf.put(i, inBuf.get(i));
+        }
+
+        mesh.clearBuffer(Type.Index);
+        switch (mesh.getMode()){
+            case LineLoop:
+            case LineStrip:
+                mesh.setMode(Mode.Lines);
+                break;
+            case TriangleStrip:
+            case TriangleFan:
+                mesh.setMode(Mode.Triangles);
+                break;
+            default:
+                break;
+        }
+        if (outBuf instanceof IndexIntBuffer){
+            mesh.setBuffer(Type.Index, 3, (IntBuffer)outBuf.getBuffer());
+        }else{
+            mesh.setBuffer(Type.Index, 3, (ShortBuffer)outBuf.getBuffer());
+        }
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/package.html b/engine/src/core/com/jme3/scene/mesh/package.html
new file mode 100644
index 0000000..5362c52
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.scene.mesh</code> package contains utilities
+for reading from {@link com.jme3.scene.mesh.IndexBuffer index buffers}.
+Several implementations are provided of the {@link com.jme3.scene.mesh.IndexBuffer}
+class:
+<ul>
+    <li>{@link com.jme3.scene.mesh.IndexByteBuffer} - For reading 8-bit index buffers</li>
+    <li>{@link com.jme3.scene.mesh.IndexShortBuffer} - For reading 16-bit index buffers</li>
+    <li>{@link com.jme3.scene.mesh.IndexIntBuffer} - For reading 32-bit index buffers</li>
+    <li>{@link com.jme3.scene.mesh.VirtualIndexBuffer} - For reading "virtual indices", for
+    those meshes that do not have an index buffer</li>
+    <li>{@link com.jme3.scene.mesh.WrappedIndexBuffer} - For converting from 
+    non-list based mode indices to list based</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/package.html b/engine/src/core/com/jme3/scene/package.html
new file mode 100644
index 0000000..53f8105
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/package.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input</code> package contains the scene graph implementation
+in jMonkeyEngine.
+
+<p>
+    The scene graph is the most important package in jME, as it is the API
+    used to manage scene elements so that they can be rendered. 
+    The {@link com.jme3.scene.Spatial} class provides a common base class
+    for all scene graph elements. The {@link com.jme3.scene.Node} class provides
+    the "branches" in the graph, used to organize elements in a tree
+    hierarchy. The {@link com.jme3.scene.Geometry} is the leaf class that
+    will contain a {@link com.jme3.scene.Mesh} object (geometry data
+    such as vertex positions, normals, etc) and a {@link com.jme3.scene.Material}
+    object containing information on how the geometry should be shaded.
+</p>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/shape/AbstractBox.java b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
new file mode 100644
index 0000000..6c68dc5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+
+/**
+ * An eight sided box.
+ * <p>
+ * A {@code Box} is defined by a minimal point and a maximal point. The eight
+ * vertices that make the box are then computed, they are computed in such
+ * a way as to generate an axis-aligned box.
+ * <p>
+ * This class does not control how the geometry data is generated, see {@link Box}
+ * for that.
+ *
+ * @author <a href="mailto:ianp@ianp.org">Ian Phillips</a>
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public abstract class AbstractBox extends Mesh {
+
+    public final Vector3f center = new Vector3f(0f, 0f, 0f);
+
+    public float xExtent, yExtent, zExtent;
+    
+    public AbstractBox() {
+        super();
+    }
+
+    /**
+     * Gets the array or vectors representing the 8 vertices of the box.
+     *
+     * @return a newly created array of vertex vectors.
+     */
+    protected final Vector3f[] computeVertices() {
+        Vector3f[] axes = {
+                Vector3f.UNIT_X.mult(xExtent),
+                Vector3f.UNIT_Y.mult(yExtent),
+                Vector3f.UNIT_Z.mult(zExtent)
+        };
+        return new Vector3f[] {
+                center.subtract(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+                center.add(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+                center.add(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+                center.subtract(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+                center.add(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+                center.subtract(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+                center.add(axes[0]).addLocal(axes[1]).addLocal(axes[2]),
+                center.subtract(axes[0]).addLocal(axes[1]).addLocal(axes[2])
+        };
+    }
+
+    /**
+     * Convert the indices into the list of vertices that define the box's geometry.
+     */
+    protected abstract void duUpdateGeometryIndices();
+    
+    /**
+     * Update the normals of each of the box's planes.
+     */
+    protected abstract void duUpdateGeometryNormals();
+
+    /**
+     * Update the points that define the texture of the box.
+     * <p>
+     * It's a one-to-one ratio, where each plane of the box has it's own copy
+     * of the texture. That is, the texture is repeated one time for each face.
+     */
+    protected abstract void duUpdateGeometryTextures();
+
+    /**
+     * Update the position of the vertices that define the box.
+     * <p>
+     * These eight points are determined from the minimum and maximum point.
+     */
+    protected abstract void duUpdateGeometryVertices();
+
+    /** 
+     * Get the center point of this box. 
+     */
+    public final Vector3f getCenter() {
+        return center;
+    }
+
+    /** 
+     * Get the x-axis size (extent) of this box. 
+     */
+    public final float getXExtent() {
+        return xExtent;
+    }
+
+    /** 
+     * Get the y-axis size (extent) of this box. 
+     */
+    public final float getYExtent() {
+        return yExtent;
+    }
+
+    /** 
+     * Get the z-axis size (extent) of this box.
+     */
+    public final float getZExtent() {
+        return zExtent;
+    }
+    
+    /**
+     * Rebuilds the box after a property has been directly altered.
+     * <p>
+     * For example, if you call {@code getXExtent().x = 5.0f} then you will
+     * need to call this method afterwards in order to update the box.
+     */
+    public final void updateGeometry() {
+        duUpdateGeometryVertices();
+        duUpdateGeometryNormals();
+        duUpdateGeometryTextures();
+        duUpdateGeometryIndices();
+    }
+
+    /**
+     * Rebuilds this box based on a new set of parameters.
+     * <p>
+     * Note that the actual sides will be twice the given extent values because
+     * the box extends in both directions from the center for each extent.
+     * 
+     * @param center the center of the box.
+     * @param x the x extent of the box, in each directions.
+     * @param y the y extent of the box, in each directions.
+     * @param z the z extent of the box, in each directions.
+     */
+    public final void updateGeometry(Vector3f center, float x, float y, float z) {
+        if (center != null) {this.center.set(center); }
+        this.xExtent = x;
+        this.yExtent = y;
+        this.zExtent = z;
+        updateGeometry();
+    }
+
+    /**
+     * Rebuilds this box based on a new set of parameters.
+     * <p>
+     * The box is updated so that the two opposite corners are {@code minPoint}
+     * and {@code maxPoint}, the other corners are created from those two positions.
+     * 
+     * @param minPoint the new minimum point of the box.
+     * @param maxPoint the new maximum point of the box.
+     */
+    public final void updateGeometry(Vector3f minPoint, Vector3f maxPoint) {
+        center.set(maxPoint).addLocal(minPoint).multLocal(0.5f);
+        float x = maxPoint.x - center.x;
+        float y = maxPoint.y - center.y;
+        float z = maxPoint.z - center.z;
+        updateGeometry(center, x, y, z);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        xExtent = capsule.readFloat("xExtent", 0);
+        yExtent = capsule.readFloat("yExtent", 0);
+        zExtent = capsule.readFloat("zExtent", 0);
+        center.set((Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone()));
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(xExtent, "xExtent", 0);
+        capsule.write(yExtent, "yExtent", 0);
+        capsule.write(zExtent, "zExtent", 0);
+        capsule.write(center, "center", Vector3f.ZERO);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Box.java b/engine/src/core/com/jme3/scene/shape/Box.java
new file mode 100644
index 0000000..307547e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Box.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ * 
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Box extends AbstractBox {
+    
+    private static final short[] GEOMETRY_INDICES_DATA = {
+         2,  1,  0,  3,  2,  0, // back
+         6,  5,  4,  7,  6,  4, // right
+        10,  9,  8, 11, 10,  8, // front
+        14, 13, 12, 15, 14, 12, // left
+        18, 17, 16, 19, 18, 16, // top
+        22, 21, 20, 23, 22, 20  // bottom
+    };
+
+    private static final float[] GEOMETRY_NORMALS_DATA = {
+        0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1, // back
+        1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0, // right
+        0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1, // front
+       -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, // left
+        0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0, // top
+        0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0  // bottom
+    };
+
+    private static final float[] GEOMETRY_TEXTURE_DATA = {
+        1, 0, 0, 0, 0, 1, 1, 1, // back
+        1, 0, 0, 0, 0, 1, 1, 1, // right
+        1, 0, 0, 0, 0, 1, 1, 1, // front
+        1, 0, 0, 0, 0, 1, 1, 1, // left
+        1, 0, 0, 0, 0, 1, 1, 1, // top
+        1, 0, 0, 0, 0, 1, 1, 1  // bottom
+    };
+
+    /**
+     * Creates a new box.
+     * <p>
+     * The box has a center of 0,0,0 and extends in the out from the center by
+     * the given amount in <em>each</em> direction. So, for example, a box
+     * with extent of 0.5 would be the unit cube.
+     *
+     * @param x the size of the box along the x axis, in both directions.
+     * @param y the size of the box along the y axis, in both directions.
+     * @param z the size of the box along the z axis, in both directions.
+     */
+    public Box(float x, float y, float z) {
+        super();
+        updateGeometry(Vector3f.ZERO, x, y, z);
+    }
+
+    /**
+     * Creates a new box.
+     * <p>
+     * The box has the given center and extends in the out from the center by
+     * the given amount in <em>each</em> direction. So, for example, a box
+     * with extent of 0.5 would be the unit cube.
+     * 
+     * @param center the center of the box.
+     * @param x the size of the box along the x axis, in both directions.
+     * @param y the size of the box along the y axis, in both directions.
+     * @param z the size of the box along the z axis, in both directions.
+     */
+    public Box(Vector3f center, float x, float y, float z) {
+        super();
+        updateGeometry(center, x, y, z);
+    }
+
+    /**
+     * Constructor instantiates a new <code>Box</code> object.
+     * <p>
+     * The minimum and maximum point are provided, these two points define the
+     * shape and size of the box but not it's orientation or position. You should
+     * use the {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+     * methods to define those properties.
+     * 
+     * @param min the minimum point that defines the box.
+     * @param max the maximum point that defines the box.
+     */
+    public Box(Vector3f min, Vector3f max) {
+        super();
+        updateGeometry(min, max);
+    }
+
+    /**
+     * Empty constructor for serialization only. Do not use.
+     */
+    public Box(){
+        super();
+    }
+
+    /**
+     * Creates a clone of this box.
+     * <p>
+     * The cloned box will have '_clone' appended to it's name, but all other
+     * properties will be the same as this box.
+     */
+    @Override
+    public Box clone() {
+        return new Box(center.clone(), xExtent, yExtent, zExtent);
+    }
+
+    protected void duUpdateGeometryIndices() {
+        if (getBuffer(Type.Index) == null){
+            setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+        }
+    }
+
+    protected void duUpdateGeometryNormals() {
+        if (getBuffer(Type.Normal) == null){
+            setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
+        }
+    }
+
+    protected void duUpdateGeometryTextures() {
+        if (getBuffer(Type.TexCoord) == null){
+            setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+        }
+    }
+
+    protected void duUpdateGeometryVertices() {
+        FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
+        Vector3f[] v = computeVertices();
+        fpb.put(new float[] {
+                v[0].x, v[0].y, v[0].z, v[1].x, v[1].y, v[1].z, v[2].x, v[2].y, v[2].z, v[3].x, v[3].y, v[3].z, // back
+                v[1].x, v[1].y, v[1].z, v[4].x, v[4].y, v[4].z, v[6].x, v[6].y, v[6].z, v[2].x, v[2].y, v[2].z, // right
+                v[4].x, v[4].y, v[4].z, v[5].x, v[5].y, v[5].z, v[7].x, v[7].y, v[7].z, v[6].x, v[6].y, v[6].z, // front
+                v[5].x, v[5].y, v[5].z, v[0].x, v[0].y, v[0].z, v[3].x, v[3].y, v[3].z, v[7].x, v[7].y, v[7].z, // left
+                v[2].x, v[2].y, v[2].z, v[6].x, v[6].y, v[6].z, v[7].x, v[7].y, v[7].z, v[3].x, v[3].y, v[3].z, // top
+                v[0].x, v[0].y, v[0].z, v[5].x, v[5].y, v[5].z, v[4].x, v[4].y, v[4].z, v[1].x, v[1].y, v[1].z  // bottom
+        });
+        setBuffer(Type.Position, 3, fpb);
+        updateBound();
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Curve.java b/engine/src/core/com/jme3/scene/shape/Curve.java
new file mode 100644
index 0000000..fbeda59
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Curve.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.shape;
+
+import com.jme3.math.Spline;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A <code>Curve</code> is a visual, line-based representation of a {@link Spline}.
+ * The underlying Spline will be sampled N times where N is the number of 
+ * segments as specified in the constructor. Each segment will represent
+ * one line in the generated mesh.
+ * 
+ * @author Nehon
+ */
+public class Curve extends Mesh {
+
+    private Spline spline;
+    private Vector3f temp = new Vector3f();
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Curve(){
+    }
+    
+    /**
+     * Create a curve mesh.
+     * Use a CatmullRom spline model that does not cycle.
+     * 
+     * @param controlPoints the control points to use to create this curve
+     * @param nbSubSegments the number of subsegments between the control points
+     */
+    public Curve(Vector3f[] controlPoints, int nbSubSegments) {
+        this(new Spline(Spline.SplineType.CatmullRom, controlPoints, 10, false), nbSubSegments);
+    }
+
+    /**
+     * Create a curve mesh from a Spline
+     * 
+     * @param spline the spline to use
+     * @param nbSubSegments the number of subsegments between the control points
+     */
+    public Curve(Spline spline, int nbSubSegments) {
+        super();
+        this.spline = spline;
+        switch (spline.getType()) {
+            case CatmullRom:
+            	this.createCatmullRomMesh(nbSubSegments);
+                break;
+            case Bezier:
+            	this.createBezierMesh(nbSubSegments);
+            	break;
+            case Nurb:
+            	this.createNurbMesh(nbSubSegments);
+            	break;
+            case Linear:
+            default:
+            	this.createLinearMesh();
+                break;
+        }
+    }
+
+    private void createCatmullRomMesh(int nbSubSegments) {
+        float[] array = new float[((spline.getControlPoints().size() - 1) * nbSubSegments + 1) * 3];
+        short[] indices = new short[(spline.getControlPoints().size() - 1) * nbSubSegments * 2];
+        int i = 0;
+        int cptCP = 0;
+        for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+            Vector3f vector3f = it.next();
+            array[i] = vector3f.x;
+            i++;
+            array[i] = vector3f.y;
+            i++;
+            array[i] = vector3f.z;
+            i++;
+            if (it.hasNext()) {
+                for (int j = 1; j < nbSubSegments; j++) {
+                    spline.interpolate((float) j / nbSubSegments, cptCP, temp);
+                    array[i] = temp.getX();
+                    i++;
+                    array[i] = temp.getY();
+                    i++;
+                    array[i] = temp.getZ();
+                    i++;
+                }
+            }
+            cptCP++;
+        }
+
+        i = 0;
+        int k = 0;
+        for (int j = 0; j < (spline.getControlPoints().size() - 1) * nbSubSegments; j++) {
+            k = j;
+            indices[i] = (short) k;
+            i++;
+            k++;
+            indices[i] = (short) k;
+            i++;
+        }
+
+        this.setMode(Mesh.Mode.Lines);
+        this.setBuffer(VertexBuffer.Type.Position, 3, array);
+        this.setBuffer(VertexBuffer.Type.Index, 2, indices);//(spline.getControlPoints().size() - 1) * nbSubSegments * 2
+        this.updateBound();
+        this.updateCounts();
+    }
+
+    /**
+	 * This method creates the Bezier path for this curve.
+	 * 
+	 * @param nbSubSegments
+	 *            amount of subsegments between position control points
+	 */
+	private void createBezierMesh(int nbSubSegments) {
+		if(nbSubSegments==0) {
+			nbSubSegments = 1;
+		}
+		int centerPointsAmount = (spline.getControlPoints().size() + 2) / 3;
+		
+		//calculating vertices
+		float[] array = new float[((centerPointsAmount - 1) * nbSubSegments + 1) * 3];
+		int currentControlPoint = 0;
+		List<Vector3f> controlPoints = spline.getControlPoints();
+		int lineIndex = 0;
+		for (int i = 0; i < centerPointsAmount - 1; ++i) {
+			Vector3f vector3f = controlPoints.get(currentControlPoint);
+			array[lineIndex++] = vector3f.x;
+			array[lineIndex++] = vector3f.y;
+			array[lineIndex++] = vector3f.z;
+			for (int j = 1; j < nbSubSegments; ++j) {
+				spline.interpolate((float) j / nbSubSegments, currentControlPoint, temp);
+				array[lineIndex++] = temp.getX();
+				array[lineIndex++] = temp.getY();
+				array[lineIndex++] = temp.getZ();
+			}
+			currentControlPoint += 3;
+		}
+		Vector3f vector3f = controlPoints.get(currentControlPoint);
+		array[lineIndex++] = vector3f.x;
+		array[lineIndex++] = vector3f.y;
+		array[lineIndex++] = vector3f.z;
+
+		//calculating indexes
+		int i = 0, k = 0;
+		short[] indices = new short[(centerPointsAmount - 1) * nbSubSegments << 1];
+		for (int j = 0; j < (centerPointsAmount - 1) * nbSubSegments; ++j) {
+			k = j;
+			indices[i++] = (short) k;
+			++k;
+			indices[i++] = (short) k;
+		}
+
+		this.setMode(Mesh.Mode.Lines);
+		this.setBuffer(VertexBuffer.Type.Position, 3, array);
+		this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+		this.updateBound();
+		this.updateCounts();
+	}
+	
+	/**
+	 * This method creates the Nurb path for this curve.
+	 * @param nbSubSegments
+	 *            amount of subsegments between position control points
+	 */
+	private void createNurbMesh(int nbSubSegments) {
+		float minKnot = spline.getMinNurbKnot();
+		float maxKnot = spline.getMaxNurbKnot();
+		float deltaU = (maxKnot - minKnot)/nbSubSegments;
+		
+		float[] array = new float[(nbSubSegments + 1) * 3];
+		
+		float u = minKnot;
+		Vector3f interpolationResult = new Vector3f();
+		for(int i=0;i<array.length;i+=3) {
+			spline.interpolate(u, 0, interpolationResult);
+			array[i] = interpolationResult.x;
+			array[i + 1] = interpolationResult.y;
+			array[i + 2] = interpolationResult.z;
+			u += deltaU;
+		}
+		
+		//calculating indexes
+		int i = 0;
+		short[] indices = new short[nbSubSegments << 1];
+		for (int j = 0; j < nbSubSegments; ++j) {
+			indices[i++] = (short) j;
+			indices[i++] = (short) (j + 1);
+		}
+
+		this.setMode(Mesh.Mode.Lines);
+		this.setBuffer(VertexBuffer.Type.Position, 3, array);
+		this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+		this.updateBound();
+		this.updateCounts();
+	}
+    
+    private void createLinearMesh() {
+        float[] array = new float[spline.getControlPoints().size() * 3];
+        short[] indices = new short[(spline.getControlPoints().size() - 1) * 2];
+        int i = 0;
+        int cpt = 0;
+        int k = 0;
+        int j = 0;
+        for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+            Vector3f vector3f = it.next();
+            array[i] = vector3f.getX();
+            i++;
+            array[i] = vector3f.getY();
+            i++;
+            array[i] = vector3f.getZ();
+            i++;
+            if (it.hasNext()) {
+                k = j;
+                indices[cpt] = (short) k;
+                cpt++;
+                k++;
+                indices[cpt] = (short) k;
+                cpt++;
+                j++;
+            }
+        }
+
+        this.setMode(Mesh.Mode.Lines);
+        this.setBuffer(VertexBuffer.Type.Position, 3, array);
+        this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+        this.updateBound();
+        this.updateCounts();
+    }
+    
+    /**
+     * This method returns the length of the curve.
+     * @return the length of the curve
+     */
+    public float getLength() {
+    	return spline.getTotalLength();
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Cylinder.java b/engine/src/core/com/jme3/scene/shape/Cylinder.java
new file mode 100644
index 0000000..85bb970
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Cylinder.java
@@ -0,0 +1,420 @@
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+

+// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $

+package com.jme3.scene.shape;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.mesh.IndexBuffer;

+import com.jme3.util.BufferUtils;

+import static com.jme3.util.BufferUtils.*;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+

+/**

+ * A simple cylinder, defined by it's height and radius.

+ * (Ported to jME3)

+ *

+ * @author Mark Powell

+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $

+ */

+public class Cylinder extends Mesh {

+

+    private int axisSamples;

+

+    private int radialSamples;

+

+    private float radius;

+    private float radius2;

+

+    private float height;

+    private boolean closed;

+    private boolean inverted;

+

+    /**

+     * Default constructor for serialization only. Do not use.

+     */

+    public Cylinder() {

+    }

+

+    /**

+     * Creates a new Cylinder. By default its center is the origin. Usually, a

+     * higher sample number creates a better looking cylinder, but at the cost

+     * of more vertex information.

+     *

+     * @param axisSamples

+     *            Number of triangle samples along the axis.

+     * @param radialSamples

+     *            Number of triangle samples along the radial.

+     * @param radius

+     *            The radius of the cylinder.

+     * @param height

+     *            The cylinder's height.

+     */

+    public Cylinder(int axisSamples, int radialSamples,

+            float radius, float height) {

+        this(axisSamples, radialSamples, radius, height, false);

+    }

+

+    /**

+     * Creates a new Cylinder. By default its center is the origin. Usually, a

+     * higher sample number creates a better looking cylinder, but at the cost

+     * of more vertex information. <br>

+     * If the cylinder is closed the texture is split into axisSamples parts:

+     * top most and bottom most part is used for top and bottom of the cylinder,

+     * rest of the texture for the cylinder wall. The middle of the top is

+     * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need

+     * a suited distorted texture.

+     *

+     * @param axisSamples

+     *            Number of triangle samples along the axis.

+     * @param radialSamples

+     *            Number of triangle samples along the radial.

+     * @param radius

+     *            The radius of the cylinder.

+     * @param height

+     *            The cylinder's height.

+     * @param closed

+     *            true to create a cylinder with top and bottom surface

+     */

+    public Cylinder(int axisSamples, int radialSamples,

+            float radius, float height, boolean closed) {

+        this(axisSamples, radialSamples, radius, height, closed, false);

+    }

+

+    /**

+     * Creates a new Cylinder. By default its center is the origin. Usually, a

+     * higher sample number creates a better looking cylinder, but at the cost

+     * of more vertex information. <br>

+     * If the cylinder is closed the texture is split into axisSamples parts:

+     * top most and bottom most part is used for top and bottom of the cylinder,

+     * rest of the texture for the cylinder wall. The middle of the top is

+     * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need

+     * a suited distorted texture.

+     *

+     * @param axisSamples

+     *            Number of triangle samples along the axis.

+     * @param radialSamples

+     *            Number of triangle samples along the radial.

+     * @param radius

+     *            The radius of the cylinder.

+     * @param height

+     *            The cylinder's height.

+     * @param closed

+     *            true to create a cylinder with top and bottom surface

+     * @param inverted

+     *            true to create a cylinder that is meant to be viewed from the

+     *            interior.

+     */

+    public Cylinder(int axisSamples, int radialSamples,

+            float radius, float height, boolean closed, boolean inverted) {

+        this(axisSamples, radialSamples, radius, radius, height, closed, inverted);

+    }

+

+    public Cylinder(int axisSamples, int radialSamples,

+            float radius, float radius2, float height, boolean closed, boolean inverted) {

+        super();

+        updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);

+    }

+

+    /**

+     * @return the number of samples along the cylinder axis

+     */

+    public int getAxisSamples() {

+        return axisSamples;

+    }

+

+    /**

+     * @return Returns the height.

+     */

+    public float getHeight() {

+        return height;

+    }

+

+    /**

+     * @return number of samples around cylinder

+     */

+    public int getRadialSamples() {

+        return radialSamples;

+    }

+

+    /**

+     * @return Returns the radius.

+     */

+    public float getRadius() {

+        return radius;

+    }

+

+    public float getRadius2() {

+        return radius2;

+    }

+

+    /**

+     * @return true if end caps are used.

+     */

+    public boolean isClosed() {

+        return closed;

+    }

+

+    /**

+     * @return true if normals and uvs are created for interior use

+     */

+    public boolean isInverted() {

+        return inverted;

+    }

+

+    /**

+     * Rebuilds the cylinder based on a new set of parameters.

+     *

+     * @param axisSamples the number of samples along the axis.

+     * @param radialSamples the number of samples around the radial.

+     * @param radius the radius of the bottom of the cylinder.

+     * @param radius2 the radius of the top of the cylinder.

+     * @param height the cylinder's height.

+     * @param closed should the cylinder have top and bottom surfaces.

+     * @param inverted is the cylinder is meant to be viewed from the inside.

+     */

+    public void updateGeometry(int axisSamples, int radialSamples,

+            float radius, float radius2, float height, boolean closed, boolean inverted) {

+        this.axisSamples = axisSamples + (closed ? 2 : 0);

+        this.radialSamples = radialSamples;

+        this.radius = radius;

+        this.radius2 = radius2;

+        this.height = height;

+        this.closed = closed;

+        this.inverted = inverted;

+

+//        VertexBuffer pvb = getBuffer(Type.Position);

+//        VertexBuffer nvb = getBuffer(Type.Normal);

+//        VertexBuffer tvb = getBuffer(Type.TexCoord);

+

+        // Vertices

+        int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);

+

+        setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));

+

+        // Normals

+        setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));

+

+        // Texture co-ordinates

+        setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));

+

+        int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;

+        

+        setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));

+

+        // generate geometry

+        float inverseRadial = 1.0f / radialSamples;

+        float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);

+        float inverseAxisLessTexture = 1.0f / (axisSamples - 1);

+        float halfHeight = 0.5f * height;

+

+        // Generate points on the unit circle to be used in computing the mesh

+        // points on a cylinder slice.

+        float[] sin = new float[radialSamples + 1];

+        float[] cos = new float[radialSamples + 1];

+

+        for (int radialCount = 0; radialCount < radialSamples; radialCount++) {

+            float angle = FastMath.TWO_PI * inverseRadial * radialCount;

+            cos[radialCount] = FastMath.cos(angle);

+            sin[radialCount] = FastMath.sin(angle);

+        }

+        sin[radialSamples] = sin[0];

+        cos[radialSamples] = cos[0];

+

+        // calculate normals

+        Vector3f[] vNormals = null;

+        Vector3f vNormal = Vector3f.UNIT_Z;

+

+        if ((height != 0.0f) && (radius != radius2)) {

+            vNormals = new Vector3f[radialSamples];

+            Vector3f vHeight = Vector3f.UNIT_Z.mult(height);

+            Vector3f vRadial = new Vector3f();

+

+            for (int radialCount = 0; radialCount < radialSamples; radialCount++) {

+                vRadial.set(cos[radialCount], sin[radialCount], 0.0f);

+                Vector3f vRadius = vRadial.mult(radius);

+                Vector3f vRadius2 = vRadial.mult(radius2);

+                Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));

+                Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);

+                vNormals[radialCount] = vMantle.cross(vTangent).normalize();

+            }

+        }

+

+        FloatBuffer nb = getFloatBuffer(Type.Normal);

+        FloatBuffer pb = getFloatBuffer(Type.Position);

+        FloatBuffer tb = getFloatBuffer(Type.TexCoord);

+

+        // generate the cylinder itself

+        Vector3f tempNormal = new Vector3f();

+        for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {

+            float axisFraction;

+            float axisFractionTexture;

+            int topBottom = 0;

+            if (!closed) {

+                axisFraction = axisCount * inverseAxisLess; // in [0,1]

+                axisFractionTexture = axisFraction;

+            } else {

+                if (axisCount == 0) {

+                    topBottom = -1; // bottom

+                    axisFraction = 0;

+                    axisFractionTexture = inverseAxisLessTexture;

+                } else if (axisCount == axisSamples - 1) {

+                    topBottom = 1; // top

+                    axisFraction = 1;

+                    axisFractionTexture = 1 - inverseAxisLessTexture;

+                } else {

+                    axisFraction = (axisCount - 1) * inverseAxisLess;

+                    axisFractionTexture = axisCount * inverseAxisLessTexture;

+                }

+            }

+

+            // compute center of slice

+            float z = -halfHeight + height * axisFraction;

+            Vector3f sliceCenter = new Vector3f(0, 0, z);

+

+            // compute slice vertices with duplication at end point

+            int save = i;

+            for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {

+                float radialFraction = radialCount * inverseRadial; // in [0,1)

+                tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);

+

+                if (vNormals != null) {

+                    vNormal = vNormals[radialCount];

+                } else if (radius == radius2) {

+                    vNormal = tempNormal;

+                }

+

+                if (topBottom == 0) {

+                    if (!inverted)

+                        nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);

+                    else

+                        nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);

+                } else {

+                    nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));

+                }

+

+                tempNormal.multLocal((radius - radius2) * axisFraction + radius2)

+                        .addLocal(sliceCenter);

+                pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);

+

+                tb.put((inverted ? 1 - radialFraction : radialFraction))

+                        .put(axisFractionTexture);

+            }

+

+            BufferUtils.copyInternalVector3(pb, save, i);

+            BufferUtils.copyInternalVector3(nb, save, i);

+

+            tb.put((inverted ? 0.0f : 1.0f))

+                    .put(axisFractionTexture);

+        }

+

+        if (closed) {

+            pb.put(0).put(0).put(-halfHeight); // bottom center

+            nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));

+            tb.put(0.5f).put(0);

+            pb.put(0).put(0).put(halfHeight); // top center

+            nb.put(0).put(0).put(1 * (inverted ? -1 : 1));

+            tb.put(0.5f).put(1);

+        }

+

+        IndexBuffer ib = getIndexBuffer();

+        int index = 0;

+        // Connectivity

+        for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {

+            int i0 = axisStart;

+            int i1 = i0 + 1;

+            axisStart += radialSamples + 1;

+            int i2 = axisStart;

+            int i3 = i2 + 1;

+            for (int i = 0; i < radialSamples; i++) {

+                if (closed && axisCount == 0) {

+                    if (!inverted) {

+                        ib.put(index++, i0++);

+                        ib.put(index++, vertCount - 2);

+                        ib.put(index++, i1++);

+                    } else {

+                        ib.put(index++, i0++);

+                        ib.put(index++, i1++);

+                        ib.put(index++, vertCount - 2);

+                    }

+                } else if (closed && axisCount == axisSamples - 2) {

+                    ib.put(index++, i2++);

+                    ib.put(index++, inverted ? vertCount - 1 : i3++);

+                    ib.put(index++, inverted ? i3++ : vertCount - 1);

+                } else {

+                    ib.put(index++, i0++);

+                    ib.put(index++, inverted ? i2 : i1);

+                    ib.put(index++, inverted ? i1 : i2);

+                    ib.put(index++, i1++);

+                    ib.put(index++, inverted ? i2++ : i3++);

+                    ib.put(index++, inverted ? i3++ : i2++);

+                }

+            }

+        }

+

+        updateBound();

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        super.read(e);

+        InputCapsule capsule = e.getCapsule(this);

+        axisSamples = capsule.readInt("axisSamples", 0);

+        radialSamples = capsule.readInt("radialSamples", 0);

+        radius = capsule.readFloat("radius", 0);

+        radius2 = capsule.readFloat("radius2", 0);

+        height = capsule.readFloat("height", 0);

+        closed = capsule.readBoolean("closed", false);

+        inverted = capsule.readBoolean("inverted", false);

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        super.write(e);

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(axisSamples, "axisSamples", 0);

+        capsule.write(radialSamples, "radialSamples", 0);

+        capsule.write(radius, "radius", 0);

+        capsule.write(radius2, "radius2", 0);

+        capsule.write(height, "height", 0);

+        capsule.write(closed, "closed", false);

+        capsule.write(inverted, "inverted", false);

+    }

+

+

+}

diff --git a/engine/src/core/com/jme3/scene/shape/Dome.java b/engine/src/core/com/jme3/scene/shape/Dome.java
new file mode 100644
index 0000000..6f429fc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Dome.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+// $Id: Dome.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A hemisphere.
+ * 
+ * @author Peter Andersson
+ * @author Joshua Slack (Original sphere code that was adapted)
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Dome extends Mesh {
+
+    private int planes;
+    private int radialSamples;
+    /** The radius of the dome */
+    private float radius;
+    /** The center of the dome */
+    private Vector3f center;
+    private boolean insideView = true;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Dome() {
+    }
+
+    /**
+     * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin 
+     * and only visible from the inside. 
+     * @param planes
+     *            The number of planes along the Z-axis. Must be >= 2.
+     *            Influences how round the arch of the dome is.
+     * @param radialSamples
+     *            The number of samples along the radial.
+     *            Influences how round the base of the dome is.
+     * @param radius
+     *            Radius of the dome.
+     * @see #Dome(java.lang.String, com.jme.math.Vector3f, int, int, float)
+     */
+    public Dome(int planes, int radialSamples, float radius) {
+        this(new Vector3f(0, 0, 0), planes, radialSamples, radius);
+    }
+
+    /**
+     * Constructs a dome visible from the inside, e.g. for use as a SkyDome. 
+     * All geometry data buffers are updated automatically. <br>
+     * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+     * Increasing planes and radialSamples increase the quality of the dome.
+     * 
+     * @param center
+     *            Center of the dome.
+     * @param planes
+     *            The number of planes along the Z-axis. Must be >= 2.
+     *            Influences how round the arch of the dome is.
+     * @param radialSamples
+     *            The number of samples along the radial. 
+     *            Influences how round the base of the dome is.
+     * @param radius
+     *            The radius of the dome.
+     */
+    public Dome(Vector3f center, int planes, int radialSamples,
+            float radius) {
+        super();
+        updateGeometry(center, planes, radialSamples, radius, true);
+    }
+
+    /**
+     * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones. 
+     * All geometry data buffers are updated automatically. <br>
+     * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+     * Setting higher values for planes and radialSamples increases 
+     * the quality of the half-sphere.
+     * 
+     * @param center
+     *            Center of the dome.
+     * @param planes
+     *            The number of planes along the Z-axis. Must be >= 2.
+     *            Influences how round the arch of the dome is.
+     * @param radialSamples
+     *            The number of samples along the radial.
+     *            Influences how round the base of the dome is.
+     * @param radius
+     *            The radius of the dome.
+     * @param insideView
+     *            If true, the dome is only visible from the inside, like a SkyDome.
+     *            If false, the dome is only visible from the outside.
+     */
+    public Dome(Vector3f center, int planes, int radialSamples,
+            float radius, boolean insideView) {
+        super();
+        updateGeometry(center, planes, radialSamples, radius, insideView);
+    }
+
+    public Vector3f getCenter() {
+        return center;
+    }
+
+    /** 
+     * Get the number of planar segments along the z-axis of the dome. 
+     */
+    public int getPlanes() {
+        return planes;
+    }
+
+    /** 
+     * Get the number of samples radially around the main axis of the dome. 
+     */
+    public int getRadialSamples() {
+        return radialSamples;
+    }
+
+    /** 
+     * Get the radius of the dome. 
+     */
+    public float getRadius() {
+        return radius;
+    }
+
+    /**
+     * Are the triangles connected in such a way as to present a view out from the dome or not.
+     */
+    public boolean isInsideView() {
+        return insideView;
+    }
+
+    /**
+     * Rebuilds the dome with a new set of parameters.
+     * 
+     * @param center the new center of the dome.
+     * @param planes the number of planes along the Z-axis.
+     * @param radialSamples the new number of radial samples of the dome.
+     * @param radius the new radius of the dome.
+     * @param insideView should the dome be set up to be viewed from the inside looking out.
+     */
+    public void updateGeometry(Vector3f center, int planes,
+            int radialSamples, float radius, boolean insideView) {
+        this.insideView = insideView;
+        this.center = center != null ? center : new Vector3f(0, 0, 0);
+        this.planes = planes;
+        this.radialSamples = radialSamples;
+        this.radius = radius;
+
+        int vertCount = ((planes - 1) * (radialSamples + 1)) + 1;
+
+        // Allocate vertices, allocating one extra in each radial to get the
+        // correct texture coordinates
+//        setVertexCount();
+//        setVertexBuffer(createVector3Buffer(getVertexCount()));
+
+        // allocate normals
+//        setNormalBuffer(createVector3Buffer(getVertexCount()));
+
+        // allocate texture coordinates
+//        getTextureCoords().set(0, new TexCoords(createVector2Buffer(getVertexCount())));
+
+        FloatBuffer vb = BufferUtils.createVector3Buffer(vertCount);
+        FloatBuffer nb = BufferUtils.createVector3Buffer(vertCount);
+        FloatBuffer tb = BufferUtils.createVector2Buffer(vertCount);
+        setBuffer(Type.Position, 3, vb);
+        setBuffer(Type.Normal, 3, nb);
+        setBuffer(Type.TexCoord, 2, tb);
+
+        // generate geometry
+        float fInvRS = 1.0f / radialSamples;
+        float fYFactor = 1.0f / (planes - 1);
+
+        // Generate points on the unit circle to be used in computing the mesh
+        // points on a dome slice.
+        float[] afSin = new float[(radialSamples)];
+        float[] afCos = new float[(radialSamples)];
+        for (int iR = 0; iR < radialSamples; iR++) {
+            float fAngle = FastMath.TWO_PI * fInvRS * iR;
+            afCos[iR] = FastMath.cos(fAngle);
+            afSin[iR] = FastMath.sin(fAngle);
+        }
+
+        TempVars vars = TempVars.get();
+        Vector3f tempVc = vars.vect3;
+        Vector3f tempVb = vars.vect2;
+        Vector3f tempVa = vars.vect1;
+
+        // generate the dome itself
+        int i = 0;
+        for (int iY = 0; iY < (planes - 1); iY++, i++) {
+            float fYFraction = fYFactor * iY; // in (0,1)
+            float fY = radius * fYFraction;
+            // compute center of slice
+            Vector3f kSliceCenter = tempVb.set(center);
+            kSliceCenter.y += fY;
+
+            // compute radius of slice
+            float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius - fY * fY));
+
+            // compute slice vertices
+            Vector3f kNormal;
+            int iSave = i;
+            for (int iR = 0; iR < radialSamples; iR++, i++) {
+                float fRadialFraction = iR * fInvRS; // in [0,1)
+                Vector3f kRadial = tempVc.set(afCos[iR], 0, afSin[iR]);
+                kRadial.mult(fSliceRadius, tempVa);
+                vb.put(kSliceCenter.x + tempVa.x).put(
+                        kSliceCenter.y + tempVa.y).put(
+                        kSliceCenter.z + tempVa.z);
+
+                BufferUtils.populateFromBuffer(tempVa, vb, i);
+                kNormal = tempVa.subtractLocal(center);
+                kNormal.normalizeLocal();
+                if (insideView) {
+                    nb.put(kNormal.x).put(kNormal.y).put(kNormal.z);
+                } else {
+                    nb.put(-kNormal.x).put(-kNormal.y).put(-kNormal.z);
+                }
+
+                tb.put(fRadialFraction).put(fYFraction);
+            }
+            BufferUtils.copyInternalVector3(vb, iSave, i);
+            BufferUtils.copyInternalVector3(nb, iSave, i);
+            tb.put(1.0f).put(fYFraction);
+        }
+
+        vars.release();
+
+        // pole
+        vb.put(center.x).put(center.y + radius).put(center.z);
+        nb.put(0).put(insideView ? 1 : -1).put(0);
+        tb.put(0.5f).put(1.0f);
+
+        // allocate connectivity
+        int triCount = (planes - 2) * radialSamples * 2 + radialSamples;
+        ShortBuffer ib = BufferUtils.createShortBuffer(3 * triCount);
+        setBuffer(Type.Index, 3, ib);
+
+        // generate connectivity
+        int index = 0;
+        // Generate only for middle planes
+        for (int plane = 1; plane < (planes - 1); plane++) {
+            int bottomPlaneStart = ((plane - 1) * (radialSamples + 1));
+            int topPlaneStart = (plane * (radialSamples + 1));
+            for (int sample = 0; sample < radialSamples; sample++, index += 6) {
+                if (insideView){
+                    ib.put((short) (bottomPlaneStart + sample));
+                    ib.put((short) (bottomPlaneStart + sample + 1));
+                    ib.put((short) (topPlaneStart + sample));
+                    ib.put((short) (bottomPlaneStart + sample + 1));
+                    ib.put((short) (topPlaneStart + sample + 1));
+                    ib.put((short) (topPlaneStart + sample));
+                }else{
+                    ib.put((short) (bottomPlaneStart + sample));
+                    ib.put((short) (topPlaneStart + sample));
+                    ib.put((short) (bottomPlaneStart + sample + 1));
+                    ib.put((short) (bottomPlaneStart + sample + 1));
+                    ib.put((short) (topPlaneStart + sample));
+                    ib.put((short) (topPlaneStart + sample + 1));
+                }
+            }
+        }
+
+        // pole triangles
+        int bottomPlaneStart = (planes - 2) * (radialSamples + 1);
+        for (int samples = 0; samples < radialSamples; samples++, index += 3) {
+            if (insideView){
+                ib.put((short) (bottomPlaneStart + samples));
+                ib.put((short) (bottomPlaneStart + samples + 1));
+                ib.put((short) (vertCount - 1));
+            }else{
+                ib.put((short) (bottomPlaneStart + samples));
+                ib.put((short) (vertCount - 1));
+                ib.put((short) (bottomPlaneStart + samples + 1));
+            }
+        }
+
+        updateBound();
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        planes = capsule.readInt("planes", 0);
+        radialSamples = capsule.readInt("radialSamples", 0);
+        radius = capsule.readFloat("radius", 0);
+        center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone());
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(planes, "planes", 0);
+        capsule.write(radialSamples, "radialSamples", 0);
+        capsule.write(radius, "radius", 0);
+        capsule.write(center, "center", Vector3f.ZERO);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Line.java b/engine/src/core/com/jme3/scene/shape/Line.java
new file mode 100644
index 0000000..1edbba7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Line.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple line implementation with a start and an end.
+ * 
+ * @author Brent Owens
+ */
+public class Line extends Mesh {
+
+    private Vector3f start;
+    private Vector3f end;
+    
+    public Line() {
+    }
+
+    public Line(Vector3f start, Vector3f end) {
+        setMode(Mode.Lines);
+        updateGeometry(start, end);
+    }
+
+    protected void updateGeometry(Vector3f start, Vector3f end) {
+        this.start = start;
+        this.end = end;
+        setBuffer(Type.Position, 3, new float[]{start.x,    start.y,    start.z,
+                                                end.x,      end.y,      end.z,});
+
+
+        setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+                                                1, 1});
+
+        setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+                                              0, 0, 1});
+
+        setBuffer(Type.Index, 3, new short[]{0, 1});
+
+        updateBound();
+    }
+
+    /**
+     * Update the start and end points of the line.
+     */
+    public void updatePoints(Vector3f start, Vector3f end) {
+        VertexBuffer posBuf = getBuffer(Type.Position);
+
+        FloatBuffer fb = (FloatBuffer) posBuf.getData();
+
+        fb.put(start.x).put(start.y).put(start.z);
+        fb.put(end.x).put(end.y).put(end.z);
+        
+        posBuf.updateData(fb);
+        
+        updateBound();
+    }
+
+    public Vector3f getEnd() {
+        return end;
+    }
+
+    public Vector3f getStart() {
+        return start;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule out = ex.getCapsule(this);
+
+        out.write(start, "startVertex", null);
+        out.write(end, "endVertex", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule in = im.getCapsule(this);
+
+        start = (Vector3f) in.readSavable("startVertex", null);
+        end = (Vector3f) in.readSavable("endVertex", null);
+    }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/PQTorus.java b/engine/src/core/com/jme3/scene/shape/PQTorus.java
new file mode 100644
index 0000000..26baed7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/PQTorus.java
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+// $Id: PQTorus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import static com.jme3.util.BufferUtils.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A parameterized torus, also known as a <em>pq</em> torus.
+ * 
+ * @author Joshua Slack, Eric Woroshow
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class PQTorus extends Mesh {
+
+    private float p, q;
+
+    private float radius, width;
+
+    private int steps, radialSamples;
+
+    public PQTorus() {
+    }
+
+    /**
+     * Creates a parameterized torus.
+     * <p>
+     * Steps and radialSamples are both degree of accuracy values.
+     * 
+     * @param p the x/z oscillation.
+     * @param q the y oscillation.
+     * @param radius the radius of the PQTorus.
+     * @param width the width of the torus.
+     * @param steps the steps along the torus.
+     * @param radialSamples radial samples for the torus.
+     */
+    public PQTorus(float p, float q, float radius, float width,
+            int steps, int radialSamples) {
+        super();
+        updateGeometry(p, q, radius, width, steps, radialSamples);
+    }
+
+    public float getP() {
+        return p;
+    }
+
+    public float getQ() {
+        return q;
+    }
+
+    public int getRadialSamples() {
+        return radialSamples;
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public int getSteps() {
+        return steps;
+    }
+
+    public float getWidth() {
+        return width;
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        p = capsule.readFloat("p", 0);
+        q = capsule.readFloat("q", 0);
+        radius = capsule.readFloat("radius", 0);
+        width = capsule.readFloat("width", 0);
+        steps = capsule.readInt("steps", 0);
+        radialSamples = capsule.readInt("radialSamples", 0);
+    }
+
+    /**
+     * Rebuilds this torus based on a new set of parameters.
+     * 
+     * @param p the x/z oscillation.
+     * @param q the y oscillation.
+     * @param radius the radius of the PQTorus.
+     * @param width the width of the torus.
+     * @param steps the steps along the torus.
+     * @param radialSamples radial samples for the torus.
+     */
+    public void updateGeometry(float p, float q, float radius, float width, int steps, int radialSamples) {
+        this.p = p;
+        this.q = q;
+        this.radius = radius;
+        this.width = width;
+        this.steps = steps;
+        this.radialSamples = radialSamples;
+
+        final float thetaStep = (FastMath.TWO_PI / steps);
+        final float betaStep = (FastMath.TWO_PI / radialSamples);
+        Vector3f[] torusPoints = new Vector3f[steps];
+
+        // Allocate all of the required buffers
+        int vertCount = radialSamples * steps;
+        
+        FloatBuffer fpb = createVector3Buffer(vertCount);
+        FloatBuffer fnb = createVector3Buffer(vertCount);
+        FloatBuffer ftb = createVector2Buffer(vertCount);
+
+        Vector3f pointB = new Vector3f(), T = new Vector3f(), N = new Vector3f(), B = new Vector3f();
+        Vector3f tempNorm = new Vector3f();
+        float r, x, y, z, theta = 0.0f, beta = 0.0f;
+        int nvertex = 0;
+
+        // Move along the length of the pq torus
+        for (int i = 0; i < steps; i++) {
+            theta += thetaStep;
+            float circleFraction = ((float) i) / (float) steps;
+
+            // Find the point on the torus
+            r = (0.5f * (2.0f + FastMath.sin(q * theta)) * radius);
+            x = (r * FastMath.cos(p * theta) * radius);
+            y = (r * FastMath.sin(p * theta) * radius);
+            z = (r * FastMath.cos(q * theta) * radius);
+            torusPoints[i] = new Vector3f(x, y, z);
+
+            // Now find a point slightly farther along the torus
+            r = (0.5f * (2.0f + FastMath.sin(q * (theta + 0.01f))) * radius);
+            x = (r * FastMath.cos(p * (theta + 0.01f)) * radius);
+            y = (r * FastMath.sin(p * (theta + 0.01f)) * radius);
+            z = (r * FastMath.cos(q * (theta + 0.01f)) * radius);
+            pointB = new Vector3f(x, y, z);
+
+            // Approximate the Frenet Frame
+            T = pointB.subtract(torusPoints[i]);
+            N = torusPoints[i].add(pointB);
+            B = T.cross(N);
+            N = B.cross(T);
+
+            // Normalise the two vectors and then use them to create an oriented circle
+            N = N.normalize();
+            B = B.normalize();
+            beta = 0.0f;
+            for (int j = 0; j < radialSamples; j++, nvertex++) {
+                beta += betaStep;
+                float cx = FastMath.cos(beta) * width;
+                float cy = FastMath.sin(beta) * width;
+                float radialFraction = ((float) j) / radialSamples;
+                tempNorm.x = (cx * N.x + cy * B.x);
+                tempNorm.y = (cx * N.y + cy * B.y);
+                tempNorm.z = (cx * N.z + cy * B.z);
+                fnb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+                tempNorm.addLocal(torusPoints[i]);
+                fpb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+                ftb.put(radialFraction).put(circleFraction);
+            }
+        }
+
+        // Update the indices data
+        ShortBuffer sib = createShortBuffer(6 * vertCount);
+        for (int i = 0; i < vertCount; i++) {
+            sib.put(new short[] {
+                    (short)(i),
+                    (short)(i - radialSamples),
+                    (short)(i + 1),
+                    (short)(i + 1),
+                    (short)(i - radialSamples),
+                    (short)(i - radialSamples + 1)
+            });
+        }
+        for (int i = 0, len = sib.capacity(); i < len; i++) {
+            int ind = sib.get(i);
+            if (ind < 0) {
+                ind += vertCount;
+                sib.put(i, (short) ind);
+            } else if (ind >= vertCount) {
+                ind -= vertCount;
+                sib.put(i, (short) ind);
+            }
+        }
+        sib.rewind();
+
+        setBuffer(Type.Position, 3, fpb);
+        setBuffer(Type.Normal,   3, fnb);
+        setBuffer(Type.TexCoord, 2, ftb);
+        setBuffer(Type.Index,    3, sib);
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(p, "p", 0);
+        capsule.write(q, "q", 0);
+        capsule.write(radius, "radius", 0);
+        capsule.write(width, "width", 0);
+        capsule.write(steps, "steps", 0);
+        capsule.write(radialSamples, "radialSamples", 0);
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Quad.java b/engine/src/core/com/jme3/scene/shape/Quad.java
new file mode 100644
index 0000000..1945358
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Quad.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+
+/**
+ * <code>Quad</code> represents a rectangular plane in space
+ * defined by 4 vertices. The quad's lower-left side is contained
+ * at the local space origin (0, 0, 0), while the upper-right
+ * side is located at the width/height coordinates (width, height, 0).
+ * 
+ * @author Kirill Vainer
+ */
+public class Quad extends Mesh {
+
+    private float width;
+    private float height;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public Quad(){
+    }
+
+    /**
+     * Create a quad with the given width and height. The quad
+     * is always created in the XY plane.
+     * 
+     * @param width The X extent or width
+     * @param height The Y extent or width
+     */
+    public Quad(float width, float height){
+        updateGeometry(width, height);
+    }
+
+    /**
+     * Create a quad with the given width and height. The quad
+     * is always created in the XY plane.
+     * 
+     * @param width The X extent or width
+     * @param height The Y extent or width
+     * @param flipCoords If true, the texture coordinates will be flipped
+     * along the Y axis.
+     */
+    public Quad(float width, float height, boolean flipCoords){
+        updateGeometry(width, height, flipCoords);
+    }
+
+    public float getHeight() {
+        return height;
+    }
+
+    public float getWidth() {
+        return width;
+    }
+
+    public void updateGeometry(float width, float height){
+        updateGeometry(width, height, false);
+    }
+
+    public void updateGeometry(float width, float height, boolean flipCoords) {
+        this.width = width;
+        this.height = height;
+        setBuffer(Type.Position, 3, new float[]{0,      0,      0,
+                                                width,  0,      0,
+                                                width,  height, 0,
+                                                0,      height, 0
+                                                });
+        
+
+        if (flipCoords){
+            setBuffer(Type.TexCoord, 2, new float[]{0, 1,
+                                                    1, 1,
+                                                    1, 0,
+                                                    0, 0});
+        }else{
+            setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+                                                    1, 0,
+                                                    1, 1,
+                                                    0, 1});
+        }
+        setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+                                              0, 0, 1,
+                                              0, 0, 1,
+                                              0, 0, 1});
+        if (height < 0){
+            setBuffer(Type.Index, 3, new short[]{0, 2, 1,
+                                                 0, 3, 2});
+        }else{
+            setBuffer(Type.Index, 3, new short[]{0, 1, 2,
+                                                 0, 2, 3});
+        }
+        
+        updateBound();
+    }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Sphere.java b/engine/src/core/com/jme3/scene/shape/Sphere.java
new file mode 100644
index 0000000..f8a5281
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Sphere.java
@@ -0,0 +1,420 @@
+/*

+ * Copyright (c) 2009-2010 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.

+ */

+// $Id: Sphere.java 4163 2009-03-25 01:14:55Z matt.yellen $

+package com.jme3.scene.shape;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+import java.nio.ShortBuffer;

+

+/**

+ * <code>Sphere</code> represents a 3D object with all points equidistance

+ * from a center point.

+ * 

+ * @author Joshua Slack

+ * @version $Revision: 4163 $, $Date: 2009-03-24 21:14:55 -0400 (Tue, 24 Mar 2009) $

+ */

+public class Sphere extends Mesh {

+

+    public enum TextureMode {

+

+        /** 

+         * Wrap texture radially and along z-axis 

+         */

+        Original,

+        /** 

+         * Wrap texure radially, but spherically project along z-axis 

+         */

+        Projected,

+        /** 

+         * Apply texture to each pole.  Eliminates polar distortion,

+         * but mirrors the texture across the equator 

+         */

+        Polar

+    }

+    protected int vertCount;

+    protected int triCount;

+    protected int zSamples;

+    protected int radialSamples;

+    protected boolean useEvenSlices;

+    protected boolean interior;

+    /** the distance from the center point each point falls on */

+    public float radius;

+    protected TextureMode textureMode = TextureMode.Original;

+

+    /**

+     * Serialization only. Do not use.

+     */

+    public Sphere() {

+    }

+

+    /**

+     * Constructs a sphere. All geometry data buffers are updated automatically.

+     * Both zSamples and radialSamples increase the quality of the generated

+     * sphere.

+     * 

+     * @param zSamples

+     *            The number of samples along the Z.

+     * @param radialSamples

+     *            The number of samples along the radial.

+     * @param radius

+     *            The radius of the sphere.

+     */

+    public Sphere(int zSamples, int radialSamples, float radius) {

+        this(zSamples, radialSamples, radius, false, false);

+    }

+

+    /**

+     * Constructs a sphere. Additional arg to evenly space latitudinal slices

+     * 

+     * @param zSamples

+     *            The number of samples along the Z.

+     * @param radialSamples

+     *            The number of samples along the radial.

+     * @param radius

+     *            The radius of the sphere.

+     * @param useEvenSlices

+     *            Slice sphere evenly along the Z axis

+     * @param interior

+     *            Not yet documented

+     */

+    public Sphere(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {

+        updateGeometry(zSamples, radialSamples, radius, useEvenSlices, interior);

+    }

+

+    public int getRadialSamples() {

+        return radialSamples;

+    }

+

+    public float getRadius() {

+        return radius;

+    }

+

+    /**

+     * @return Returns the textureMode.

+     */

+    public TextureMode getTextureMode() {

+        return textureMode;

+    }

+

+    public int getZSamples() {

+        return zSamples;

+    }

+

+    /**

+     * builds the vertices based on the radius, radial and zSamples.

+     */

+    private void setGeometryData() {

+        // allocate vertices

+        vertCount = (zSamples - 2) * (radialSamples + 1) + 2;

+

+        FloatBuffer posBuf = BufferUtils.createVector3Buffer(vertCount);

+

+        // allocate normals if requested

+        FloatBuffer normBuf = BufferUtils.createVector3Buffer(vertCount);

+

+        // allocate texture coordinates

+        FloatBuffer texBuf = BufferUtils.createVector2Buffer(vertCount);

+

+        setBuffer(Type.Position, 3, posBuf);

+        setBuffer(Type.Normal, 3, normBuf);

+        setBuffer(Type.TexCoord, 2, texBuf);

+

+        // generate geometry

+        float fInvRS = 1.0f / radialSamples;

+        float fZFactor = 2.0f / (zSamples - 1);

+

+        // Generate points on the unit circle to be used in computing the mesh

+        // points on a sphere slice.

+        float[] afSin = new float[(radialSamples + 1)];

+        float[] afCos = new float[(radialSamples + 1)];

+        for (int iR = 0; iR < radialSamples; iR++) {

+            float fAngle = FastMath.TWO_PI * fInvRS * iR;

+            afCos[iR] = FastMath.cos(fAngle);

+            afSin[iR] = FastMath.sin(fAngle);

+        }

+        afSin[radialSamples] = afSin[0];

+        afCos[radialSamples] = afCos[0];

+

+        TempVars vars = TempVars.get();

+        Vector3f tempVa = vars.vect1;

+        Vector3f tempVb = vars.vect2;

+        Vector3f tempVc = vars.vect3;

+

+        // generate the sphere itself

+        int i = 0;

+        for (int iZ = 1; iZ < (zSamples - 1); iZ++) {

+            float fAFraction = FastMath.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2)

+            float fZFraction;

+            if (useEvenSlices) {

+                fZFraction = -1.0f + fZFactor * iZ; // in (-1, 1)

+            } else {

+                fZFraction = FastMath.sin(fAFraction); // in (-1,1)

+            }

+            float fZ = radius * fZFraction;

+

+            // compute center of slice

+            Vector3f kSliceCenter = tempVb.set(Vector3f.ZERO);

+            kSliceCenter.z += fZ;

+

+            // compute radius of slice

+            float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius

+                    - fZ * fZ));

+

+            // compute slice vertices with duplication at end point

+            Vector3f kNormal;

+            int iSave = i;

+            for (int iR = 0; iR < radialSamples; iR++) {

+                float fRadialFraction = iR * fInvRS; // in [0,1)

+                Vector3f kRadial = tempVc.set(afCos[iR], afSin[iR], 0);

+                kRadial.mult(fSliceRadius, tempVa);

+                posBuf.put(kSliceCenter.x + tempVa.x).put(

+                        kSliceCenter.y + tempVa.y).put(

+                        kSliceCenter.z + tempVa.z);

+

+                BufferUtils.populateFromBuffer(tempVa, posBuf, i);

+                kNormal = tempVa;

+                kNormal.normalizeLocal();

+                if (!interior) // allow interior texture vs. exterior

+                {

+                    normBuf.put(kNormal.x).put(kNormal.y).put(

+                            kNormal.z);

+                } else {

+                    normBuf.put(-kNormal.x).put(-kNormal.y).put(

+                            -kNormal.z);

+                }

+

+                if (textureMode == TextureMode.Original) {

+                    texBuf.put(fRadialFraction).put(

+                            0.5f * (fZFraction + 1.0f));

+                } else if (textureMode == TextureMode.Projected) {

+                    texBuf.put(fRadialFraction).put(

+                            FastMath.INV_PI

+                            * (FastMath.HALF_PI + FastMath.asin(fZFraction)));

+                } else if (textureMode == TextureMode.Polar) {

+                    float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;

+                    float u = r * afCos[iR] + 0.5f;

+                    float v = r * afSin[iR] + 0.5f;

+                    texBuf.put(u).put(v);

+                }

+

+                i++;

+            }

+

+            BufferUtils.copyInternalVector3(posBuf, iSave, i);

+            BufferUtils.copyInternalVector3(normBuf, iSave, i);

+

+            if (textureMode == TextureMode.Original) {

+                texBuf.put(1.0f).put(

+                        0.5f * (fZFraction + 1.0f));

+            } else if (textureMode == TextureMode.Projected) {

+                texBuf.put(1.0f).put(

+                        FastMath.INV_PI

+                        * (FastMath.HALF_PI + FastMath.asin(fZFraction)));

+            } else if (textureMode == TextureMode.Polar) {

+                float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;

+                texBuf.put(r + 0.5f).put(0.5f);

+            }

+

+            i++;

+        }

+

+        vars.release();

+

+        // south pole

+        posBuf.position(i * 3);

+        posBuf.put(0f).put(0f).put(-radius);

+

+        normBuf.position(i * 3);

+        if (!interior) {

+            normBuf.put(0).put(0).put(-1); // allow for inner

+        } // texture orientation

+        // later.

+        else {

+            normBuf.put(0).put(0).put(1);

+        }

+

+        texBuf.position(i * 2);

+

+        if (textureMode == TextureMode.Polar) {

+            texBuf.put(0.5f).put(0.5f);

+        } else {

+            texBuf.put(0.5f).put(0.0f);

+        }

+

+        i++;

+

+        // north pole

+        posBuf.put(0).put(0).put(radius);

+

+        if (!interior) {

+            normBuf.put(0).put(0).put(1);

+        } else {

+            normBuf.put(0).put(0).put(-1);

+        }

+

+        if (textureMode == TextureMode.Polar) {

+            texBuf.put(0.5f).put(0.5f);

+        } else {

+            texBuf.put(0.5f).put(1.0f);

+        }

+

+        updateBound();

+        setStatic();

+    }

+

+    /**

+     * sets the indices for rendering the sphere.

+     */

+    private void setIndexData() {

+        // allocate connectivity

+        triCount = 2 * (zSamples - 2) * radialSamples;

+        ShortBuffer idxBuf = BufferUtils.createShortBuffer(3 * triCount);

+        setBuffer(Type.Index, 3, idxBuf);

+

+        // generate connectivity

+        int index = 0;

+        for (int iZ = 0, iZStart = 0; iZ < (zSamples - 3); iZ++) {

+            int i0 = iZStart;

+            int i1 = i0 + 1;

+            iZStart += (radialSamples + 1);

+            int i2 = iZStart;

+            int i3 = i2 + 1;

+            for (int i = 0; i < radialSamples; i++, index += 6) {

+                if (!interior) {

+                    idxBuf.put((short) i0++);

+                    idxBuf.put((short) i1);

+                    idxBuf.put((short) i2);

+                    idxBuf.put((short) i1++);

+                    idxBuf.put((short) i3++);

+                    idxBuf.put((short) i2++);

+                } else { // inside view

+                    idxBuf.put((short) i0++);

+                    idxBuf.put((short) i2);

+                    idxBuf.put((short) i1);

+                    idxBuf.put((short) i1++);

+                    idxBuf.put((short) i2++);

+                    idxBuf.put((short) i3++);

+                }

+            }

+        }

+

+        // south pole triangles

+        for (int i = 0; i < radialSamples; i++, index += 3) {

+            if (!interior) {

+                idxBuf.put((short) i);

+                idxBuf.put((short) (vertCount - 2));

+                idxBuf.put((short) (i + 1));

+            } else { // inside view

+                idxBuf.put((short) i);

+                idxBuf.put((short) (i + 1));

+                idxBuf.put((short) (vertCount - 2));

+            }

+        }

+

+        // north pole triangles

+        int iOffset = (zSamples - 3) * (radialSamples + 1);

+        for (int i = 0; i < radialSamples; i++, index += 3) {

+            if (!interior) {

+                idxBuf.put((short) (i + iOffset));

+                idxBuf.put((short) (i + 1 + iOffset));

+                idxBuf.put((short) (vertCount - 1));

+            } else { // inside view

+                idxBuf.put((short) (i + iOffset));

+                idxBuf.put((short) (vertCount - 1));

+                idxBuf.put((short) (i + 1 + iOffset));

+            }

+        }

+    }

+

+    /**

+     * @param textureMode

+     *            The textureMode to set.

+     */

+    public void setTextureMode(TextureMode textureMode) {

+        this.textureMode = textureMode;

+        setGeometryData();

+    }

+

+    /**

+     * Changes the information of the sphere into the given values.

+     * 

+     * @param zSamples the number of zSamples of the sphere.

+     * @param radialSamples the number of radial samples of the sphere.

+     * @param radius the radius of the sphere.

+     */

+    public void updateGeometry(int zSamples, int radialSamples, float radius) {

+        updateGeometry(zSamples, radialSamples, radius, false, false);

+    }

+

+    public void updateGeometry(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {

+        this.zSamples = zSamples;

+        this.radialSamples = radialSamples;

+        this.radius = radius;

+        this.useEvenSlices = useEvenSlices;

+        this.interior = interior;

+        setGeometryData();

+        setIndexData();

+    }

+

+    public void read(JmeImporter e) throws IOException {

+        super.read(e);

+        InputCapsule capsule = e.getCapsule(this);

+        zSamples = capsule.readInt("zSamples", 0);

+        radialSamples = capsule.readInt("radialSamples", 0);

+        radius = capsule.readFloat("radius", 0);

+        useEvenSlices = capsule.readBoolean("useEvenSlices", false);

+        textureMode = capsule.readEnum("textureMode", TextureMode.class, TextureMode.Original);

+        interior = capsule.readBoolean("interior", false);

+    }

+

+    public void write(JmeExporter e) throws IOException {

+        super.write(e);

+        OutputCapsule capsule = e.getCapsule(this);

+        capsule.write(zSamples, "zSamples", 0);

+        capsule.write(radialSamples, "radialSamples", 0);

+        capsule.write(radius, "radius", 0);

+        capsule.write(useEvenSlices, "useEvenSlices", false);

+        capsule.write(textureMode, "textureMode", TextureMode.Original);

+        capsule.write(interior, "interior", false);

+    }

+}

diff --git a/engine/src/core/com/jme3/scene/shape/StripBox.java b/engine/src/core/com/jme3/scene/shape/StripBox.java
new file mode 100644
index 0000000..81abff9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/StripBox.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ * 
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class StripBox extends AbstractBox {
+    
+    private static final short[] GEOMETRY_INDICES_DATA = 
+    { 1, 0, 4,
+      5, 
+      7, 
+      0, 
+      3, 
+      1, 
+      2, 
+      4, 
+      6, 
+      7, 
+      2, 
+      3 };
+    
+    private static final float[] GEOMETRY_TEXTURE_DATA = {
+        1, 0,
+        0, 0,
+        0, 1,
+        1, 1,
+        
+        1, 0,
+        0, 0,
+        1, 1,
+        0, 1
+    };
+    
+    /**
+     * Creates a new box.
+     * <p>
+     * The box has a center of 0,0,0 and extends in the out from the center by
+     * the given amount in <em>each</em> direction. So, for example, a box
+     * with extent of 0.5 would be the unit cube.
+     *
+     * @param x the size of the box along the x axis, in both directions.
+     * @param y the size of the box along the y axis, in both directions.
+     * @param z the size of the box along the z axis, in both directions.
+     */
+    public StripBox(float x, float y, float z) {
+        super();
+        updateGeometry(Vector3f.ZERO, x, y, z);
+    }
+
+    /**
+     * Creates a new box.
+     * <p>
+     * The box has the given center and extends in the out from the center by
+     * the given amount in <em>each</em> direction. So, for example, a box
+     * with extent of 0.5 would be the unit cube.
+     * 
+     * @param center the center of the box.
+     * @param x the size of the box along the x axis, in both directions.
+     * @param y the size of the box along the y axis, in both directions.
+     * @param z the size of the box along the z axis, in both directions.
+     */
+    public StripBox(Vector3f center, float x, float y, float z) {
+        super();
+        updateGeometry(center, x, y, z);
+    }
+
+    /**
+     * Constructor instantiates a new <code>Box</code> object.
+     * <p>
+     * The minimum and maximum point are provided, these two points define the
+     * shape and size of the box but not it’s orientation or position. You should
+     * use the {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+     * methods to define those properties.
+     * 
+     * @param min the minimum point that defines the box.
+     * @param max the maximum point that defines the box.
+     */
+    public StripBox(Vector3f min, Vector3f max) {
+        super();
+        updateGeometry(min, max);
+    }
+
+    /**
+     * Empty constructor for serialization only. Do not use.
+     */
+    public StripBox(){
+        super();
+    }
+
+    /**
+     * Creates a clone of this box.
+     * <p>
+     * The cloned box will have ‘_clone’ appended to it’s name, but all other
+     * properties will be the same as this box.
+     */
+    @Override
+    public StripBox clone() {
+        return new StripBox(center.clone(), xExtent, yExtent, zExtent);
+    }
+
+    protected void duUpdateGeometryIndices() {
+        if (getBuffer(Type.Index) == null){
+            setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+        }
+    }
+
+    protected void duUpdateGeometryNormals() {
+        if (getBuffer(Type.Normal) == null){
+            float[] normals = new float[8 * 3];
+            
+            Vector3f[] vert = computeVertices();
+            Vector3f norm = new Vector3f();
+            
+            for (int i = 0; i < 8; i++) {
+                norm.set(vert[i]).normalizeLocal();
+                
+                normals[i * 3 + 0] = norm.x;
+                normals[i * 3 + 1] = norm.x;
+                normals[i * 3 + 2] = norm.x;
+            }
+            
+            setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
+        }
+    }
+
+    protected void duUpdateGeometryTextures() {
+        if (getBuffer(Type.TexCoord) == null){
+            setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+        }
+    }
+
+    protected void duUpdateGeometryVertices() {
+        FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3);
+        Vector3f[] v = computeVertices();
+        fpb.put(new float[] {
+                v[0].x, v[0].y, v[0].z, 
+                v[1].x, v[1].y, v[1].z, 
+                v[2].x, v[2].y, v[2].z, 
+                v[3].x, v[3].y, v[3].z,
+                v[4].x, v[4].y, v[4].z, 
+                v[5].x, v[5].y, v[5].z, 
+                v[6].x, v[6].y, v[6].z, 
+                v[7].x, v[7].y, v[7].z, 
+        });
+        setBuffer(Type.Position, 3, fpb);
+        setMode(Mode.TriangleStrip);
+        updateBound();
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Surface.java b/engine/src/core/com/jme3/scene/shape/Surface.java
new file mode 100644
index 0000000..77d259b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Surface.java
@@ -0,0 +1,283 @@
+package com.jme3.scene.shape;

+

+import com.jme3.math.CurveAndSurfaceMath;

+import com.jme3.math.FastMath;

+import com.jme3.math.Spline.SplineType;

+import com.jme3.math.Vector3f;

+import com.jme3.math.Vector4f;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.util.BufferUtils;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+/**

+ * This class represents a surface described by knots, weights and control points.

+ * Currently the following types are supported:

+ * a) NURBS

+ * @author Marcin Roguski (Kealthas)

+ */

+public class Surface extends Mesh {

+

+    private SplineType type;						//the type of the surface

+    private List<List<Vector4f>> controlPoints;		//space control points and their weights

+    private List<Float>[] knots;					//knots of the surface

+    private int basisUFunctionDegree;				//the degree of basis U function

+    private int basisVFunctionDegree;				//the degree of basis V function

+    private int uSegments;							//the amount of U segments

+    private int vSegments;							//the amount of V segments

+

+    /**

+     * Constructor. Constructs required surface.

+     * @param controlPoints space control points

+     * @param nurbKnots knots of the surface

+     * @param uSegments the amount of U segments

+     * @param vSegments the amount of V segments

+     * @param basisUFunctionDegree the degree of basis U function

+     * @param basisVFunctionDegree the degree of basis V function

+     */

+    private Surface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,

+            int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {

+        this.validateInputData(controlPoints, nurbKnots, uSegments, vSegments);

+        this.type = SplineType.Nurb;

+        this.uSegments = uSegments;

+        this.vSegments = vSegments;

+        this.controlPoints = controlPoints;

+        this.knots = nurbKnots;

+        this.basisUFunctionDegree = basisUFunctionDegree;

+        CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[0], basisUFunctionDegree);

+        if (nurbKnots[1] != null) {

+            this.basisVFunctionDegree = basisVFunctionDegree;

+            CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[1], basisVFunctionDegree);

+        }

+

+        this.buildSurface();

+    }

+

+    /**

+     * This method creates a NURBS surface.

+     * @param controlPoints space control points

+     * @param nurbKnots knots of the surface

+     * @param uSegments the amount of U segments

+     * @param vSegments the amount of V segments

+     * @param basisUFunctionDegree the degree of basis U function

+     * @param basisVFunctionDegree the degree of basis V function

+     * @return an instance of NURBS surface

+     */

+    public static final Surface createNurbsSurface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,

+            int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {

+        Surface result = new Surface(controlPoints, nurbKnots, uSegments, vSegments, basisUFunctionDegree, basisVFunctionDegree);

+        result.type = SplineType.Nurb;

+        return result;

+    }

+

+    /**

+     * This method creates the surface.

+     */

+    private void buildSurface() {

+        boolean smooth = true;//TODO: take smoothing into consideration

+        float minUKnot = this.getMinUNurbKnot();

+        float maxUKnot = this.getMaxUNurbKnot();

+        float deltaU = (maxUKnot - minUKnot) / uSegments;

+

+        float minVKnot = this.getMinVNurbKnot();

+        float maxVKnot = this.getMaxVNurbKnot();

+        float deltaV = (maxVKnot - minVKnot) / vSegments;

+

+        Vector3f[] vertices = new Vector3f[(uSegments + 1) * (vSegments + 1)];

+

+        float u = minUKnot, v = minVKnot;

+        int arrayIndex = 0;

+

+        for (int i = 0; i <= vSegments; ++i) {

+            for (int j = 0; j <= uSegments; ++j) {

+                Vector3f interpolationResult = new Vector3f();

+                CurveAndSurfaceMath.interpolate(u, v, controlPoints, knots, basisUFunctionDegree, basisVFunctionDegree, interpolationResult);

+                vertices[arrayIndex++] = interpolationResult;

+                u += deltaU;

+            }

+            u = minUKnot;

+            v += deltaV;

+        }

+

+        //adding indexes

+        int uVerticesAmount = uSegments + 1;

+        int[] indices = new int[uSegments * vSegments * 6];

+        arrayIndex = 0;

+        for (int i = 0; i < vSegments; ++i) {

+            for (int j = 0; j < uSegments; ++j) {

+                indices[arrayIndex++] = j + i * uVerticesAmount;

+                indices[arrayIndex++] = j + i * uVerticesAmount + 1;

+                indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;

+                indices[arrayIndex++] = j + i * uVerticesAmount + 1;

+                indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount + 1;

+                indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;

+            }

+        }

+

+        //normalMap merges normals of faces that will be rendered smooth

+        Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(vertices.length);

+        for (int i = 0; i < indices.length; i += 3) {

+            Vector3f n = FastMath.computeNormal(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);

+            this.addNormal(n, normalMap, smooth, vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);

+        }

+        //preparing normal list (the order of normals must match the order of vertices)

+        float[] normals = new float[vertices.length * 3];

+        arrayIndex = 0;

+        for (int i = 0; i < vertices.length; ++i) {

+            Vector3f n = normalMap.get(vertices[i]);

+            normals[arrayIndex++] = n.x;

+            normals[arrayIndex++] = n.y;

+            normals[arrayIndex++] = n.z;

+        }

+

+        this.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));

+        this.setBuffer(VertexBuffer.Type.Index, 3, indices);

+        this.setBuffer(VertexBuffer.Type.Normal, 3, normals);

+        this.updateBound();

+        this.updateCounts();

+    }

+

+    public List<List<Vector4f>> getControlPoints() {

+        return controlPoints;

+    }

+

+    /**

+     * This method returns the amount of U control points.

+     * @return the amount of U control points

+     */

+    public int getUControlPointsAmount() {

+        return controlPoints.size();

+    }

+

+    /**

+     * This method returns the amount of V control points.

+     * @return the amount of V control points

+     */

+    public int getVControlPointsAmount() {

+        return controlPoints.get(0) == null ? 0 : controlPoints.get(0).size();

+    }

+

+    /**

+     * This method returns the degree of basis U function.

+     * @return the degree of basis U function

+     */

+    public int getBasisUFunctionDegree() {

+        return basisUFunctionDegree;

+    }

+

+    /**

+     * This method returns the degree of basis V function.

+     * @return the degree of basis V function

+     */

+    public int getBasisVFunctionDegree() {

+        return basisVFunctionDegree;

+    }

+

+    /**

+     * This method returns the knots for specified dimension (U knots - value: '0',

+     * V knots - value: '1').

+     * @param dim an integer specifying if the U or V knots are required

+     * @return an array of knots

+     */

+    public List<Float> getKnots(int dim) {

+        return knots[dim];

+    }

+

+    /**

+     * This method returns the type of the surface.

+     * @return the type of the surface

+     */

+    public SplineType getType() {

+        return type;

+    }

+

+    /**

+     * This method returns the minimum nurb curve U knot value.

+     * @return the minimum nurb curve knot value

+     */

+    private float getMinUNurbKnot() {

+        return knots[0].get(basisUFunctionDegree - 1);

+    }

+

+    /**

+     * This method returns the maximum nurb curve U knot value.

+     * @return the maximum nurb curve knot value

+     */

+    private float getMaxUNurbKnot() {

+        return knots[0].get(knots[0].size() - basisUFunctionDegree);

+    }

+

+    /**

+     * This method returns the minimum nurb curve U knot value.

+     * @return the minimum nurb curve knot value

+     */

+    private float getMinVNurbKnot() {

+        return knots[1].get(basisVFunctionDegree - 1);

+    }

+

+    /**

+     * This method returns the maximum nurb curve U knot value.

+     * @return the maximum nurb curve knot value

+     */

+    private float getMaxVNurbKnot() {

+        return knots[1].get(knots[1].size() - basisVFunctionDegree);

+    }

+

+    /**

+     * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.

+     * @param normalToAdd

+     *            a normal to be added

+     * @param normalMap

+     *            merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector

+     * @param smooth

+     *            the variable that indicates wheather to merge normals (creating the smooth mesh) or not

+     * @param vertices

+     *            a list of vertices read from the blender file

+     */

+    private void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {

+        for (Vector3f v : vertices) {

+            Vector3f n = normalMap.get(v);

+            if (!smooth || n == null) {

+                normalMap.put(v, normalToAdd.clone());

+            } else {

+                n.addLocal(normalToAdd).normalizeLocal();

+            }

+        }

+    }

+

+    /**

+     * This method validates the input data. It throws {@link IllegalArgumentException} if

+     * the data is invalid.

+     * @param controlPoints space control points

+     * @param nurbKnots knots of the surface

+     * @param uSegments the amount of U segments

+     * @param vSegments the amount of V segments

+     */

+    private void validateInputData(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,

+            int uSegments, int vSegments) {

+        int uPointsAmount = controlPoints.get(0).size();

+        for (int i = 1; i < controlPoints.size(); ++i) {

+            if (controlPoints.get(i).size() != uPointsAmount) {

+                throw new IllegalArgumentException("The amount of 'U' control points is invalid!");

+            }

+        }

+        if (uSegments <= 0) {

+            throw new IllegalArgumentException("U segments amount should be positive!");

+        }

+        if (vSegments < 0) {

+            throw new IllegalArgumentException("V segments amount cannot be negative!");

+        }

+        if (nurbKnots.length != 2) {

+            throw new IllegalArgumentException("Nurb surface should have two rows of knots!");

+        }

+        for (int i = 0; i < nurbKnots.length; ++i) {

+            for (int j = 0; j < nurbKnots[i].size() - 1; ++j) {

+                if (nurbKnots[i].get(j) > nurbKnots[i].get(j + 1)) {

+                    throw new IllegalArgumentException("The knots' values cannot decrease!");

+                }

+            }

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/scene/shape/Torus.java b/engine/src/core/com/jme3/scene/shape/Torus.java
new file mode 100644
index 0000000..8b9285a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Torus.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+// $Id: Torus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * An ordinary (single holed) torus.
+ * <p>
+ * The center is by default the origin.
+ * 
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Torus extends Mesh {
+
+    private int circleSamples;
+
+    private int radialSamples;
+
+    private float innerRadius;
+
+    private float outerRadius;
+
+    public Torus() {
+    }
+
+    /**
+     * Constructs a new Torus. Center is the origin, but the Torus may be
+     * transformed.
+     * 
+     * @param circleSamples
+     *            The number of samples along the circles.
+     * @param radialSamples
+     *            The number of samples along the radial.
+     * @param innerRadius
+     *            The radius of the inner begining of the Torus.
+     * @param outerRadius
+     *            The radius of the outter end of the Torus.
+     */
+    public Torus(int circleSamples, int radialSamples,
+            float innerRadius, float outerRadius) {
+        super();
+        updateGeometry(circleSamples, radialSamples, innerRadius, outerRadius);
+    }
+
+    public int getCircleSamples() {
+        return circleSamples;
+    }
+
+    public float getInnerRadius() {
+        return innerRadius;
+    }
+
+    public float getOuterRadius() {
+        return outerRadius;
+    }
+
+    public int getRadialSamples() {
+        return radialSamples;
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        circleSamples = capsule.readInt("circleSamples", 0);
+        radialSamples = capsule.readInt("radialSamples", 0);
+        innerRadius = capsule.readFloat("innerRadius", 0);
+        outerRadius = capsule.readFloat("outerRaidus", 0);
+    }
+
+    private void setGeometryData() {
+        // allocate vertices
+        int vertCount = (circleSamples + 1) * (radialSamples + 1);
+        FloatBuffer fpb = BufferUtils.createVector3Buffer(vertCount);
+        setBuffer(Type.Position, 3, fpb);
+
+        // allocate normals if requested
+        FloatBuffer fnb = BufferUtils.createVector3Buffer(vertCount);
+        setBuffer(Type.Normal, 3, fnb);
+
+        // allocate texture coordinates
+        FloatBuffer ftb = BufferUtils.createVector2Buffer(vertCount);
+        setBuffer(Type.TexCoord, 2, ftb);
+
+        // generate geometry
+        float inverseCircleSamples = 1.0f / circleSamples;
+        float inverseRadialSamples = 1.0f / radialSamples;
+        int i = 0;
+        // generate the cylinder itself
+        Vector3f radialAxis = new Vector3f(), torusMiddle = new Vector3f(), tempNormal = new Vector3f();
+        for (int circleCount = 0; circleCount < circleSamples; circleCount++) {
+            // compute center point on torus circle at specified angle
+            float circleFraction = circleCount * inverseCircleSamples;
+            float theta = FastMath.TWO_PI * circleFraction;
+            float cosTheta = FastMath.cos(theta);
+            float sinTheta = FastMath.sin(theta);
+            radialAxis.set(cosTheta, sinTheta, 0);
+            radialAxis.mult(outerRadius, torusMiddle);
+
+            // compute slice vertices with duplication at end point
+            int iSave = i;
+            for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+                float radialFraction = radialCount * inverseRadialSamples;
+                // in [0,1)
+                float phi = FastMath.TWO_PI * radialFraction;
+                float cosPhi = FastMath.cos(phi);
+                float sinPhi = FastMath.sin(phi);
+                tempNormal.set(radialAxis).multLocal(cosPhi);
+                tempNormal.z += sinPhi;
+                fnb.put(tempNormal.x).put(tempNormal.y).put(
+                        tempNormal.z);
+       
+                tempNormal.multLocal(innerRadius).addLocal(torusMiddle);
+                fpb.put(tempNormal.x).put(tempNormal.y).put(
+                        tempNormal.z);
+
+                ftb.put(radialFraction).put(circleFraction);
+                i++;
+            }
+
+            BufferUtils.copyInternalVector3(fpb, iSave, i);
+            BufferUtils.copyInternalVector3(fnb, iSave, i);
+
+            ftb.put(1.0f).put(circleFraction);
+
+            i++;
+        }
+
+        // duplicate the cylinder ends to form a torus
+        for (int iR = 0; iR <= radialSamples; iR++, i++) {
+            BufferUtils.copyInternalVector3(fpb, iR, i);
+            BufferUtils.copyInternalVector3(fnb, iR, i);
+            BufferUtils.copyInternalVector2(ftb, iR, i);
+            ftb.put(i * 2 + 1, 1.0f);
+        }
+    }
+
+    private void setIndexData() {
+        // allocate connectivity
+        int triCount = 2 * circleSamples * radialSamples;
+
+        ShortBuffer sib = BufferUtils.createShortBuffer(3 * triCount);
+        setBuffer(Type.Index, 3, sib);
+
+        int i;
+        // generate connectivity
+        int connectionStart = 0;
+        int index = 0;
+        for (int circleCount = 0; circleCount < circleSamples; circleCount++) {
+            int i0 = connectionStart;
+            int i1 = i0 + 1;
+            connectionStart += radialSamples + 1;
+            int i2 = connectionStart;
+            int i3 = i2 + 1;
+            for (i = 0; i < radialSamples; i++, index += 6) {
+//                if (true) {
+                    sib.put((short)i0++);
+                    sib.put((short)i2);
+                    sib.put((short)i1);
+                    sib.put((short)i1++);
+                    sib.put((short)i2++);
+                    sib.put((short)i3++);
+
+//                    getIndexBuffer().put(i0++);
+//                    getIndexBuffer().put(i2);
+//                    getIndexBuffer().put(i1);
+//                    getIndexBuffer().put(i1++);
+//                    getIndexBuffer().put(i2++);
+//                    getIndexBuffer().put(i3++);
+//                } else {
+//                    getIndexBuffer().put(i0++);
+//                    getIndexBuffer().put(i1);
+//                    getIndexBuffer().put(i2);
+//                    getIndexBuffer().put(i1++);
+//                    getIndexBuffer().put(i3++);
+//                    getIndexBuffer().put(i2++);
+//                }
+            }
+        }
+    }
+
+    /**
+     * Rebuilds this torus based on a new set of parameters.
+     * 
+     * @param circleSamples the number of samples along the circles.
+     * @param radialSamples the number of samples along the radial.
+     * @param innerRadius the radius of the inner begining of the Torus.
+     * @param outerRadius the radius of the outter end of the Torus.
+     */
+    public void updateGeometry(int circleSamples, int radialSamples, float innerRadius, float outerRadius) {
+        this.circleSamples = circleSamples;
+        this.radialSamples = radialSamples;
+        this.innerRadius = innerRadius;
+        this.outerRadius = outerRadius;
+        setGeometryData();
+        setIndexData();
+        updateBound();
+        updateCounts();
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(circleSamples, "circleSamples", 0);
+        capsule.write(radialSamples, "radialSamples", 0);
+        capsule.write(innerRadius, "innerRadius", 0);
+        capsule.write(outerRadius, "outerRadius", 0);
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/shader/Attribute.java b/engine/src/core/com/jme3/shader/Attribute.java
new file mode 100644
index 0000000..4b6a095
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Attribute.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+/**
+ * An attribute is a shader variable mapping to a VertexBuffer data
+ * on the CPU.
+ *
+ * @author Kirill Vainer
+ */
+public class Attribute extends ShaderVariable {
+}
diff --git a/engine/src/core/com/jme3/shader/DefineList.java b/engine/src/core/com/jme3/shader/DefineList.java
new file mode 100644
index 0000000..abd3661
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/DefineList.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class DefineList implements Savable {
+
+    private final SortedMap<String, String> defines = new TreeMap<String, String>();
+    private String compiled = null;
+
+    public void write(JmeExporter ex) throws IOException{
+        OutputCapsule oc = ex.getCapsule(this);
+
+        String[] keys = new String[defines.size()];
+        String[] vals = new String[defines.size()];
+
+        int i = 0;
+        for (Map.Entry<String, String> define : defines.entrySet()){
+            keys[i] = define.getKey();
+            vals[i] = define.getValue();
+            i++;
+        }
+
+        oc.write(keys, "keys", null);
+        oc.write(vals, "vals", null);
+
+        // for compatability only with older versions
+        oc.write(compiled, "compiled", null);
+    }
+
+    public void read(JmeImporter im) throws IOException{
+        InputCapsule ic = im.getCapsule(this);
+
+        String[] keys = ic.readStringArray("keys", null);
+        String[] vals = ic.readStringArray("vals", null);
+        for (int i = 0; i < keys.length; i++){
+            defines.put(keys[i], vals[i]);
+        }
+
+        compiled = ic.readString("compiled", null);
+    }
+
+    public void clear() {
+        defines.clear();
+        compiled = "";
+    }
+
+    public String get(String key){
+        // I do not see the point of forcing a rebuild on get()
+        // so I'm commenting it out. -pspeed
+        //compiled = null;
+        return defines.get(key);
+    }
+
+//    public void set(String key, String val){
+//        compiled = null;
+//        defines.put(key, val);
+//    }
+
+    public boolean set(String key, VarType type, Object val){    
+        if (val == null){
+            defines.remove(key);
+            compiled = null;
+            return true;
+        }
+
+        switch (type){
+            case Boolean:
+                if ( ((Boolean) val).booleanValue() ) {
+                    // same literal, != should work
+                    if( defines.put(key, "1") != "1" ) {  
+                        compiled = null;
+                        return true;
+                    }                    
+                } else if (defines.containsKey(key)) {
+                    defines.remove(key);
+                    compiled = null;
+                    return true;            
+                }
+                
+                break;
+            case Float:
+            case Int:
+                String original = defines.put(key, val.toString());
+                if (!val.equals(original)) {
+                    compiled = null;
+                    return true;            
+                }
+                break;
+            default:
+                // same literal, != should work
+                if (defines.put(key, "1") != "1") {  
+                    compiled = null;
+                    return true;            
+                }
+                break;
+        }
+        
+        return false;
+    }
+
+    public boolean remove(String key){   
+        if (defines.remove(key) != null) {
+            compiled = null;
+            return true;
+        }
+        
+        return false;
+    }
+
+    public void addFrom(DefineList other){    
+        if (other == null)
+            return;
+        
+        compiled = null;
+        defines.putAll(other.defines);
+    }
+
+    public String getCompiled(){
+        if (compiled == null){
+            StringBuilder sb = new StringBuilder();
+            for (Map.Entry<String, String> entry : defines.entrySet()){
+                sb.append("#define ").append(entry.getKey()).append(" ");
+                sb.append(entry.getValue()).append('\n');
+            }
+            compiled = sb.toString();
+        }
+        return compiled;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        int i = 0;
+        for (Map.Entry<String, String> entry : defines.entrySet()) {
+            sb.append(entry.getKey());
+            if (i != defines.size() - 1)
+                sb.append(", ");
+
+            i++;
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shader/Shader.java b/engine/src/core/com/jme3/shader/Shader.java
new file mode 100644
index 0000000..265eed6
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Shader.java
@@ -0,0 +1,443 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.shader;

+

+import com.jme3.export.*;

+import com.jme3.renderer.Renderer;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.util.IntMap;

+import com.jme3.util.IntMap.Entry;

+import com.jme3.util.ListMap;

+import com.jme3.util.NativeObject;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.HashMap;

+

+public final class Shader extends NativeObject implements Savable {

+

+    private String language;

+

+    /**

+     * True if the shader is fully compiled & linked.

+     * (e.g no GL error will be invoked if used).

+     */

+    private boolean usable = false;

+

+    /**

+     * A list of all shaders currently attached.

+     */

+    private ArrayList<ShaderSource> shaderList;

+

+    /**

+     * Maps uniform name to the uniform variable.

+     */

+//    private HashMap<String, Uniform> uniforms;

+    private ListMap<String, Uniform> uniforms;

+

+    /**

+     * Maps attribute name to the location of the attribute in the shader.

+     */

+    private IntMap<Attribute> attribs;

+

+    /**

+     * Type of shader. The shader will control the pipeline of it's type.

+     */

+    public static enum ShaderType {

+        /**

+         * Control fragment rasterization. (e.g color of pixel).

+         */

+        Fragment,

+

+        /**

+         * Control vertex processing. (e.g transform of model to clip space)

+         */

+        Vertex,

+

+        /**

+         * Control geometry assembly. (e.g compile a triangle list from input data)

+         */

+        Geometry;

+    }

+

+    /**

+     * Shader source describes a shader object in OpenGL. Each shader source

+     * is assigned a certain pipeline which it controls (described by it's type).

+     */

+    public static class ShaderSource extends NativeObject implements Savable {

+

+        ShaderType shaderType;

+

+        boolean usable = false;

+        String name = null;

+        String source = null;

+        String defines = null;

+

+        public ShaderSource(ShaderType type){

+            super(ShaderSource.class);

+            this.shaderType = type;

+            if (type == null)

+                throw new NullPointerException("The shader type must be specified");

+        }

+        

+        protected ShaderSource(ShaderSource ss){

+            super(ShaderSource.class, ss.id);

+            this.shaderType = ss.shaderType;

+            usable = false;

+            name = ss.name;

+            // forget source & defines

+        }

+

+        public ShaderSource(){

+            super(ShaderSource.class);

+        }

+

+        public void write(JmeExporter ex) throws IOException{

+            OutputCapsule oc = ex.getCapsule(this);

+            oc.write(shaderType, "shaderType", null);

+            oc.write(name, "name", null);

+            oc.write(source, "source", null);

+            oc.write(defines, "defines", null);

+        }

+

+        public void read(JmeImporter im) throws IOException{

+            InputCapsule ic = im.getCapsule(this);

+            shaderType = ic.readEnum("shaderType", ShaderType.class, null);

+            name = ic.readString("name", null);

+            source = ic.readString("source", null);

+            defines = ic.readString("defines", null);

+        }

+

+        public void setName(String name){

+            this.name = name;

+        }

+

+        public String getName(){

+            return name;

+        }

+

+        public ShaderType getType() {

+            return shaderType;

+        }

+

+        public void setSource(String source){

+            if (source == null)

+                throw new NullPointerException("Shader source cannot be null");

+

+            this.source = source;

+            setUpdateNeeded();

+        }

+

+        public void setDefines(String defines){

+            if (defines == null)

+                throw new NullPointerException("Shader defines cannot be null");

+

+            this.defines = defines;

+            setUpdateNeeded();

+        }

+

+        public String getSource(){

+            return source;

+        }

+

+        public String getDefines(){

+            return defines;

+        }

+        

+        public boolean isUsable(){

+            return usable;

+        }

+

+        public void setUsable(boolean usable){

+            this.usable = usable;

+        }

+

+        @Override

+        public String toString(){

+            String nameTxt = "";

+            if (name != null)

+                nameTxt = "name="+name+", ";

+            if (defines != null)

+                nameTxt += "defines, ";

+            

+

+            return getClass().getSimpleName() + "["+nameTxt+"type="

+                                              + shaderType.name()+"]";

+        }

+

+        public void resetObject(){

+            id = -1;

+            usable = false;

+            setUpdateNeeded();

+        }

+

+        public void deleteObject(Object rendererObject){

+            ((Renderer)rendererObject).deleteShaderSource(ShaderSource.this);

+        }

+

+        public NativeObject createDestructableClone(){

+            return new ShaderSource(ShaderSource.this);

+        }

+    }

+

+    /**

+     * Create an empty shader.

+     */

+    public Shader(String language){

+        super(Shader.class);

+        this.language = language;

+        shaderList = new ArrayList<ShaderSource>();

+//        uniforms = new HashMap<String, Uniform>();

+        uniforms = new ListMap<String, Uniform>();

+        attribs = new IntMap<Attribute>();

+    }

+

+    /**

+     * Do not use this constructor. Serialization purposes only.

+     */

+    public Shader(){

+        super(Shader.class);

+    }

+

+    protected Shader(Shader s){

+        super(Shader.class, s.id);

+        shaderList = new ArrayList<ShaderSource>();

+        //uniforms = new ListMap<String, Uniform>();

+        //attribs = new IntMap<Attribute>();

+        

+        // NOTE: Because ShaderSources are registered separately with

+        // the GLObjectManager

+        for (ShaderSource source : s.shaderList){

+            shaderList.add( (ShaderSource)source.createDestructableClone() );

+        }

+    }

+

+    public void write(JmeExporter ex) throws IOException{

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(language, "language", null);

+        oc.writeSavableArrayList(shaderList, "shaderList", null);

+        oc.writeIntSavableMap(attribs, "attribs", null);

+        oc.writeStringSavableMap(uniforms, "uniforms", null);

+    }

+

+    public void read(JmeImporter im) throws IOException{

+        InputCapsule ic = im.getCapsule(this);

+        language = ic.readString("language", null);

+        shaderList = ic.readSavableArrayList("shaderList", null);

+        attribs = (IntMap<Attribute>) ic.readIntSavableMap("attribs", null);

+

+        HashMap<String, Uniform> uniMap = (HashMap<String, Uniform>) ic.readStringSavableMap("uniforms", null);

+        uniforms = new ListMap<String, Uniform>(uniMap);

+    }

+

+    /**

+     * Creates a deep clone of the shader, where the sources are available

+     * but have not been compiled yet. Does not copy the uniforms or attribs.

+     * @return

+     */

+//    public Shader createDeepClone(String defines){

+//        Shader newShader = new Shader(language);

+//        for (ShaderSource source : shaderList){

+//            if (!source.getDefines().equals(defines)){

+//                // need to clone the shadersource so

+//                // the correct defines can be placed

+//                ShaderSource newSource = new ShaderSource(source.getType());

+//                newSource.setSource(source.getSource());

+//                newSource.setDefines(defines);

+//                newShader.addSource(newSource);

+//            }else{

+//                // no need to clone source, also saves

+//                // having to compile the shadersource

+//                newShader.addSource(source);

+//            }

+//        }

+//        return newShader;

+//    }

+

+    /**

+     * Adds source code to a certain pipeline.

+     *

+     * @param type The pipeline to control

+     * @param source The shader source code (in GLSL).

+     */

+    public void addSource(ShaderType type, String name, String source, String defines){

+        ShaderSource shader = new ShaderSource(type);

+        shader.setSource(source);

+        shader.setName(name);

+        if (defines != null)

+            shader.setDefines(defines);

+        

+        shaderList.add(shader);

+        setUpdateNeeded();

+    }

+

+    public void addSource(ShaderType type, String source, String defines){

+        addSource(type, null, source, defines);

+    }

+

+    public void addSource(ShaderType type, String source){

+        addSource(type, source, null);

+    }

+

+    /**

+     * Adds an existing shader source to this shader.

+     * @param source

+     */

+    private void addSource(ShaderSource source){

+        shaderList.add(source);

+        setUpdateNeeded();

+    }

+

+    public Uniform getUniform(String name){

+        Uniform uniform = uniforms.get(name);

+        if (uniform == null){

+            uniform = new Uniform();

+            uniform.name = name;

+            uniforms.put(name, uniform);

+        }

+        return uniform;

+    }

+

+    public void removeUniform(String name){

+        uniforms.remove(name);

+    }

+

+    public Attribute getAttribute(VertexBuffer.Type attribType){

+        int ordinal = attribType.ordinal();

+        Attribute attrib = attribs.get(ordinal);

+        if (attrib == null){

+            attrib = new Attribute();

+            attrib.name = attribType.name();

+            attribs.put(ordinal, attrib);

+        }

+        return attrib;

+    }

+

+//    public Collection<Uniform> getUniforms(){

+//        return uniforms.values();

+//    }

+

+    public ListMap<String, Uniform> getUniformMap(){

+        return uniforms;

+    }

+

+//    public Collection<Attribute> getAttributes() {

+//        return attribs.

+//    }

+

+    public Collection<ShaderSource> getSources(){

+        return shaderList;

+    }

+

+    public String getLanguage(){

+        return language;

+    }

+

+    @Override

+    public String toString(){

+        return getClass().getSimpleName() + "[language="+language

+                                           + ", numSources="+shaderList.size()

+                                           + ", numUniforms="+uniforms.size()

+                                           + ", shaderSources="+getSources()+"]";

+    }

+

+    /**

+     * Clears all sources. Assuming that they have already been detached and

+     * removed on the GL side.

+     */

+    public void resetSources(){

+        shaderList.clear();

+    }

+

+    /**

+     * Returns true if this program and all it's shaders have been compiled,

+     * linked and validated successfuly.

+     */

+    public boolean isUsable(){

+        return usable;

+    }

+

+    /**

+     * Sets if the program can be used. Should only be called by the Renderer.

+     * @param usable

+     */

+    public void setUsable(boolean usable){

+        this.usable = usable;

+    }

+

+    /**

+     * Usually called when the shader itself changes or during any

+     * time when the var locations need to be refreshed.

+     */

+    public void resetLocations(){

+        // NOTE: Shader sources will be reset seperately from the shader itself.

+        for (Uniform uniform : uniforms.values()){

+            uniform.reset(); // fixes issue with re-initialization

+        }

+        for (Entry<Attribute> entry : attribs){

+            entry.getValue().location = -2;

+        }

+    }

+

+    @Override

+    public void setUpdateNeeded(){

+        super.setUpdateNeeded();

+        resetLocations();

+    }

+

+    /**

+     * Called by the object manager to reset all object IDs. This causes

+     * the shader to be reuploaded to the GPU incase the display was restarted.

+     */

+    @Override

+    public void resetObject() {

+        this.id = -1;

+        this.usable = false;

+        

+        for (ShaderSource source : shaderList){

+            source.resetObject();

+        }

+        

+        setUpdateNeeded();

+    }

+

+    @Override

+    public void deleteObject(Object rendererObject) {

+        ((Renderer)rendererObject).deleteShader(this);

+    }

+

+    public NativeObject createDestructableClone(){

+        return new Shader(this);

+    }

+

+}

diff --git a/engine/src/core/com/jme3/shader/ShaderKey.java b/engine/src/core/com/jme3/shader/ShaderKey.java
new file mode 100644
index 0000000..a4c18eb
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderKey.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+public class ShaderKey extends AssetKey<Shader> {
+
+    protected String fragName;
+    protected DefineList defines;
+    protected String language;
+
+    public ShaderKey(){
+    }
+
+    public ShaderKey(String vertName, String fragName, DefineList defines, String lang){
+        super(vertName);
+        this.fragName = fragName;
+        this.defines = defines;
+        this.language = lang;
+    }
+
+    @Override
+    public String toString(){
+        return "V="+name + " F=" + fragName + (defines != null ? defines : "");
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null){
+            return false;
+        }
+        if (getClass() != obj.getClass()){
+            return false;
+        }
+
+        final ShaderKey other = (ShaderKey) obj;
+        if (name.equals(other.name) && fragName.equals(other.fragName)){
+//            return true;
+            if (defines != null && other.defines != null)
+                return defines.getCompiled().equals(other.defines.getCompiled());
+            else if (defines != null || other.defines != null)
+                return false;
+            else
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 41 * hash + name.hashCode();
+        hash = 41 * hash + fragName.hashCode();
+        hash = 41 * hash + (defines != null ? defines.getCompiled().hashCode() : 0);
+        return hash;
+    }
+
+    public DefineList getDefines() {
+        return defines;
+    }
+
+    public String getVertName(){
+        return name;
+    }
+
+    public String getFragName() {
+        return fragName;
+    }
+
+    public String getLanguage() {
+        return language;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException{
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(fragName, "fragment_name", null);
+        oc.write(language, "language", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException{
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        fragName = ic.readString("fragment_name", null);
+        language = ic.readString("language", null);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shader/ShaderUtils.java b/engine/src/core/com/jme3/shader/ShaderUtils.java
new file mode 100644
index 0000000..8aecdeb
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public class ShaderUtils {
+
+    public static String convertToGLSL130(String input, boolean isFrag){
+        StringBuilder sb = new StringBuilder();
+        sb.append("#version 130\n");
+        if (isFrag){
+            input = input.replaceAll("varying", "in");
+        }else{
+            input = input.replaceAll("attribute", "in");
+            input = input.replaceAll("varying", "out");
+        }
+        sb.append(input);
+        return sb.toString();
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shader/ShaderVariable.java b/engine/src/core/com/jme3/shader/ShaderVariable.java
new file mode 100644
index 0000000..7dbd2e2
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderVariable.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+public class ShaderVariable implements Savable {
+
+    // if -2, location not known
+    // if -1, not defined in shader
+    // if >= 0, uniform defined and available.
+    protected int location = -2;
+
+    /**
+     * Name of the uniform as was declared in the shader.
+     * E.g name = "g_WorldMatrix" if the decleration was
+     * "uniform mat4 g_WorldMatrix;".
+     */
+    protected String name = null;
+
+    /**
+     * True if the shader value was changed.
+     */
+    protected boolean updateNeeded = true;;
+
+    public void write(JmeExporter ex) throws IOException{
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(name, "name", null);
+    }
+
+    public void read(JmeImporter im) throws IOException{
+        InputCapsule ic = im.getCapsule(this);
+        name = ic.readString("name", null);
+    }
+
+    public void setLocation(int location){
+        this.location = location;
+    }
+
+    public int getLocation(){
+        return location;
+    }
+
+    public void setName(String name){
+        this.name = name;
+    }
+
+    public String getName(){
+        return name;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shader/Uniform.java b/engine/src/core/com/jme3/shader/Uniform.java
new file mode 100644
index 0000000..228e097
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Uniform.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.*;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+public class Uniform extends ShaderVariable {
+
+    private static final Integer ZERO_INT = Integer.valueOf(0);
+    private static final Float ZERO_FLT = Float.valueOf(0);
+    private static final FloatBuffer ZERO_BUF = BufferUtils.createFloatBuffer(4*4);
+
+    /**
+     * Currently set value of the uniform.
+     */
+    protected Object value = null;
+    protected FloatBuffer multiData = null;
+
+    /**
+     * Type of uniform
+     */
+    protected VarType varType;
+
+    /**
+     * Binding to a renderer value, or null if user-defined uniform
+     */
+    protected UniformBinding binding;
+
+    protected boolean setByCurrentMaterial = false;
+//    protected Object lastChanger = null;
+
+    @Override
+    public void write(JmeExporter ex) throws IOException{
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(varType, "varType", null);
+        oc.write(binding, "binding", null);
+        switch (varType){
+            case Boolean:
+                oc.write( ((Boolean)value).booleanValue(), "valueBoolean", false );
+                break;
+            case Float:
+                oc.write( ((Float)value).floatValue(), "valueFloat", 0);
+                break;
+            case FloatArray:
+                oc.write( (FloatBuffer)value, "valueFloatArray", null);
+                break;
+            case Int:
+                oc.write( ((Integer)value).intValue(), "valueInt", 0);
+                break;
+            case Matrix3:
+                oc.write( (Matrix3f)value, "valueMatrix3", null);
+                break;
+            case Matrix3Array:
+            case Matrix4Array:
+            case Vector2Array:
+                throw new UnsupportedOperationException("Come again?");
+            case Matrix4:
+                oc.write( (Matrix4f)value, "valueMatrix4", null);
+                break;
+            case Vector2:
+                oc.write( (Vector2f)value, "valueVector2", null);
+                break;
+            case Vector3:
+                oc.write( (Vector3f)value, "valueVector3", null);
+                break;
+            case Vector3Array:
+                oc.write( (FloatBuffer)value, "valueVector3Array", null);
+                break;
+            case Vector4:
+                oc.write( (ColorRGBA)value, "valueVector4", null);
+                break;
+            case Vector4Array:
+                oc.write( (FloatBuffer)value, "valueVector4Array", null);
+                break;
+        }
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException{
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        varType = ic.readEnum("varType", VarType.class, null);
+        binding = ic.readEnum("binding", UniformBinding.class, null);
+        switch (varType){
+            case Boolean:
+                value = ic.readBoolean("valueBoolean", false);
+                break;
+            case Float:
+                value = ic.readFloat("valueFloat", 0);
+                break;
+            case FloatArray:
+                value = ic.readFloatBuffer("valueFloatArray", null);
+                break;
+            case Int:
+                value = ic.readInt("valueInt", 0);
+                break;
+            case Matrix3:
+                multiData = ic.readFloatBuffer("valueMatrix3", null);
+                value = multiData;
+                break;
+            case Matrix4:
+                multiData = ic.readFloatBuffer("valueMatrix4", null);
+                value = multiData;
+                break;
+            case Vector2:
+                value = ic.readSavable("valueVector2", null);
+                break;
+            case Vector3:
+                value = ic.readSavable("valueVector3", null);
+                break;
+            case Vector3Array:
+                value = ic.readFloatBuffer("valueVector3Array", null);
+                break;
+            case Vector4:
+                value = ic.readSavable("valueVector4", null);
+                break;
+            case Vector4Array:
+                value = ic.readFloatBuffer("valueVector4Array", null);
+                break;
+        }
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        if (name != null){
+            sb.append("Uniform[name=");
+            sb.append(name);
+            if (varType != null){
+                sb.append(", type=");
+                sb.append(varType);
+                sb.append(", value=");
+                sb.append(value);
+            }else{
+                sb.append(", value=<not set>");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public void setBinding(UniformBinding binding){
+        this.binding = binding;
+    }
+
+    public UniformBinding getBinding(){
+        return binding;
+    }
+
+    public VarType getVarType() {
+        return varType;
+    }
+
+    public Object getValue(){
+        return value;
+    }
+
+    public boolean isSetByCurrentMaterial() {
+        return setByCurrentMaterial;
+    }
+
+    public void clearSetByCurrentMaterial(){
+        setByCurrentMaterial = false;
+    }
+
+//    public void setLastChanger(Object lastChanger){
+//        this.lastChanger = lastChanger;
+//    }
+//
+//    public Object getLastChanger(){
+//        return lastChanger;
+//    }
+
+    public void clearValue(){
+        updateNeeded = true;
+
+        if (multiData != null){
+            ZERO_BUF.clear();
+            multiData.clear();
+
+            while (multiData.remaining() > 0){
+                ZERO_BUF.limit( Math.min(multiData.remaining(), 16) );
+                multiData.put(ZERO_BUF);
+            }
+
+            multiData.clear();
+
+            return;
+        }
+
+        if (varType == null)
+            return;
+
+        switch (varType){
+            case Int:
+                this.value = ZERO_INT;
+                break;
+            case Boolean:
+                this.value = Boolean.FALSE;
+                break;
+            case Float:
+                this.value = ZERO_FLT; 
+                break;
+            case Vector2:
+                this.value = Vector2f.ZERO;
+                break;
+            case Vector3:
+                this.value = Vector3f.ZERO;
+                break;
+            case Vector4:
+                if (this.value instanceof ColorRGBA){
+                    this.value = ColorRGBA.BlackNoAlpha;
+                }else{
+                    this.value = Quaternion.ZERO;
+                }
+                break;
+            default:
+                break; // won't happen because those are either textures
+                       // or multidata types
+        }
+    }
+
+    public void setValue(VarType type, Object value){
+        if (location == -1)
+            return;
+
+        if (varType != null && varType != type)
+            throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+
+        if (value == null)
+            throw new NullPointerException();
+
+        setByCurrentMaterial = true;
+
+        switch (type){
+            case Matrix3:
+                Matrix3f m3 = (Matrix3f) value;
+                if (multiData == null)
+                    multiData = BufferUtils.createFloatBuffer(9);
+                
+                m3.fillFloatBuffer(multiData, true);
+                multiData.clear();
+                break;
+            case Matrix4:
+                Matrix4f m4 = (Matrix4f) value;
+                if (multiData == null)
+                    multiData = BufferUtils.createFloatBuffer(16);
+                
+                m4.fillFloatBuffer(multiData, true);
+                multiData.clear();
+                break;
+            case FloatArray:
+                float[] fa = (float[]) value;
+                if (multiData == null){
+                    multiData = BufferUtils.createFloatBuffer(fa);
+                }else{
+                    multiData = BufferUtils.ensureLargeEnough(multiData, fa.length);
+                }
+                
+                multiData.put(fa);
+                multiData.clear();
+                break;
+            case Vector2Array:
+                Vector2f[] v2a = (Vector2f[]) value;
+                if (multiData == null){
+                    multiData = BufferUtils.createFloatBuffer(v2a);
+                } else {
+                    multiData = BufferUtils.ensureLargeEnough(multiData, v2a.length * 2);
+                }
+
+                for (int i = 0; i < v2a.length; i++)
+                    BufferUtils.setInBuffer(v2a[i], multiData, i);
+                
+                multiData.clear();
+                break;
+            case Vector3Array:
+                Vector3f[] v3a = (Vector3f[]) value;
+                if (multiData == null){
+                    multiData = BufferUtils.createFloatBuffer(v3a);
+                } else{
+                    multiData = BufferUtils.ensureLargeEnough(multiData, v3a.length * 3);
+                }
+                
+                for (int i = 0; i < v3a.length; i++)
+                    BufferUtils.setInBuffer(v3a[i], multiData, i);
+
+                multiData.clear();
+                break;
+            case Vector4Array:
+                Quaternion[] v4a = (Quaternion[]) value;
+                if (multiData == null){
+                    multiData = BufferUtils.createFloatBuffer(v4a);
+                } else {
+                    multiData = BufferUtils.ensureLargeEnough(multiData, v4a.length * 4);
+                }
+                
+                for (int i = 0; i < v4a.length; i++)
+                    BufferUtils.setInBuffer(v4a[i], multiData, i);
+
+                multiData.clear();
+                break;
+            case Matrix3Array:
+                Matrix3f[] m3a = (Matrix3f[]) value;
+
+                if (multiData == null)
+                    multiData = BufferUtils.createFloatBuffer(m3a.length * 9);
+                else{
+                    multiData = BufferUtils.ensureLargeEnough(multiData, m3a.length * 9);
+                }
+
+                for (int i = 0; i < m3a.length; i++)
+                    m3a[i].fillFloatBuffer(multiData, true);
+                
+                multiData.clear();
+                break;
+            case Matrix4Array:
+                Matrix4f[] m4a = (Matrix4f[]) value;
+
+                if (multiData == null)
+                    multiData = BufferUtils.createFloatBuffer(m4a.length * 16);
+                else{
+                    multiData = BufferUtils.ensureLargeEnough(multiData, m4a.length * 16);
+                }
+
+                for (int i = 0; i < m4a.length; i++)
+                    m4a[i].fillFloatBuffer(multiData, true);
+                
+                multiData.clear();
+                break;
+            // Only use check if equals optimization for primitive values
+            case Int:
+            case Float:
+            case Boolean:
+                if (this.value != null && this.value.equals(value))
+                    return;
+
+                this.value = value;
+                break;
+            default:
+                this.value = value;
+                break;
+        }
+
+        if (multiData != null)
+            this.value = multiData;
+        
+        varType = type;
+        updateNeeded = true;
+    }
+
+    public void setVector4Length(int length){
+        if (location == -1)
+            return;
+
+        FloatBuffer fb = (FloatBuffer) value;
+        if (fb == null || fb.capacity() < length){
+            value = BufferUtils.createFloatBuffer(length * 4);
+        }
+
+        varType = VarType.Vector4Array;
+        updateNeeded = true;
+        setByCurrentMaterial = true;
+    }
+
+    public void setVector4InArray(float x, float y, float z, float w, int index){
+        if (location == -1)
+            return;
+
+        if (varType != null && varType != VarType.Vector4Array)
+            throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+
+        FloatBuffer fb = (FloatBuffer) value;
+        fb.position(index * 4);
+        fb.put(x).put(y).put(z).put(w);
+        fb.rewind();
+        updateNeeded = true;
+        setByCurrentMaterial = true;
+    }
+    
+    public boolean isUpdateNeeded(){
+        return updateNeeded;
+    }
+
+    public void clearUpdateNeeded(){
+        updateNeeded = false;
+    }
+
+    public void reset(){
+        setByCurrentMaterial = false;
+        location = -2;
+        updateNeeded = true;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shader/UniformBinding.java b/engine/src/core/com/jme3/shader/UniformBinding.java
new file mode 100644
index 0000000..bdca35b
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/UniformBinding.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public enum UniformBinding {
+
+    /**
+     * The world matrix. Converts Model space to World space.
+     * Type: mat4
+     */
+    WorldMatrix,
+
+    /**
+     * The view matrix. Converts World space to View space.
+     * Type: mat4
+     */
+    ViewMatrix,
+
+    /**
+     * The projection matrix. Converts View space to Clip/Projection space.
+     * Type: mat4
+     */
+    ProjectionMatrix,
+
+    /**
+     * The world view matrix. Converts Model space to View space.
+     * Type: mat4
+     */
+    WorldViewMatrix,
+
+    /**
+     * The normal matrix. The inverse transpose of the worldview matrix.
+     * Converts normals from model space to view space.
+     * Type: mat3
+     */
+    NormalMatrix,
+
+    /**
+     * The world view projection matrix. Converts Model space to Clip/Projection
+     * space.
+     * Type: mat4
+     */
+    WorldViewProjectionMatrix,
+
+    /**
+     * The view projection matrix. Converts Model space to Clip/Projection
+     * space.
+     * Type: mat4
+     */
+    ViewProjectionMatrix,
+
+
+    WorldMatrixInverse,
+    ViewMatrixInverse,
+    ProjectionMatrixInverse,
+    ViewProjectionMatrixInverse,
+    WorldViewMatrixInverse,
+    NormalMatrixInverse,
+    WorldViewProjectionMatrixInverse,
+
+    /**
+     * Contains the four viewport parameters in this order:
+     * X = Left,
+     * Y = Top,
+     * Z = Right,
+     * W = Bottom.
+     * Type: vec4
+     */
+    ViewPort,
+
+    /**
+     * The near and far values for the camera frustum.
+     * X = Near
+     * Y = Far.
+     * Type: vec2
+     */
+    FrustumNearFar,
+    
+    /**
+     * The width and height of the camera.
+     * Type: vec2
+     */
+    Resolution,
+
+    /**
+     * Aspect ratio of the resolution currently set. Width/Height.
+     * Type: float
+     */
+    Aspect,
+
+    /**
+     * Camera position in world space.
+     * Type: vec3
+     */
+    CameraPosition,
+
+    /**
+     * Direction of the camera.
+     * Type: vec3
+     */
+    CameraDirection,
+
+    /**
+     * Left vector of the camera.
+     * Type: vec3
+     */
+    CameraLeft,
+
+    /**
+     * Up vector of the camera.
+     * Type: vec3
+     */
+    CameraUp,
+
+    /**
+     * Time in seconds since the application was started.
+     * Type: float
+     */
+    Time,
+
+    /**
+     * Time in seconds that the last frame took.
+     * Type: float
+     */
+    Tpf,
+
+    /**
+     * Frames per second.
+     * Type: float
+     */
+    FrameRate,
+}
diff --git a/engine/src/core/com/jme3/shader/VarType.java b/engine/src/core/com/jme3/shader/VarType.java
new file mode 100644
index 0000000..723bc93
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/VarType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public enum VarType {
+
+    Float,
+    Vector2,
+    Vector3,
+    Vector4,
+
+    FloatArray(true,false),
+    Vector2Array(true,false),
+    Vector3Array(true,false),
+    Vector4Array(true,false),
+
+    Boolean,
+
+    Matrix3(true,false),
+    Matrix4(true,false),
+
+    Matrix3Array(true,false),
+    Matrix4Array(true,false),
+
+    TextureBuffer(false,true),
+    Texture2D(false,true),
+    Texture3D(false,true),
+    TextureArray(false,true),
+    TextureCubeMap(false,true),
+    Int;
+
+    private boolean usesMultiData = false;
+    private boolean textureType = false;
+
+    VarType(){
+    }
+
+    VarType(boolean multiData, boolean textureType){
+        usesMultiData = multiData;
+        this.textureType = textureType;
+    }
+
+    public boolean isTextureType() {
+        return textureType;
+    }
+
+    public boolean usesMultiData() {
+        return usesMultiData;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
new file mode 100644
index 0000000..3fd01c9
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
@@ -0,0 +1,216 @@
+/*

+ * Copyright (c) 2009-2010 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.shadow;

+

+import com.jme3.asset.AssetManager;

+import com.jme3.material.Material;

+import com.jme3.math.Vector3f;

+import com.jme3.post.SceneProcessor;

+import com.jme3.renderer.Camera;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.Renderer;

+import com.jme3.renderer.ViewPort;

+import com.jme3.renderer.queue.GeometryList;

+import com.jme3.renderer.queue.RenderQueue;

+import com.jme3.renderer.queue.RenderQueue.ShadowMode;

+import com.jme3.texture.FrameBuffer;

+import com.jme3.texture.Image.Format;

+import com.jme3.texture.Texture2D;

+import com.jme3.ui.Picture;

+

+/**

+ * BasicShadowRenderer uses standard shadow mapping with one map

+ * it's useful to render shadows in a small scene, but edges might look a bit jagged.

+ * 

+ * @author Kirill Vainer

+ */

+public class BasicShadowRenderer implements SceneProcessor {

+

+    private RenderManager renderManager;

+    private ViewPort viewPort;

+    private FrameBuffer shadowFB;

+    private Texture2D shadowMap;

+    private Camera shadowCam;

+    private Material preshadowMat;

+    private Material postshadowMat;

+    private Picture dispPic = new Picture("Picture");

+    private boolean noOccluders = false;

+    private Vector3f[] points = new Vector3f[8];

+    private Vector3f direction = new Vector3f();

+

+    /**

+     * Creates a BasicShadowRenderer

+     * @param manager the asset manager

+     * @param size the size of the shadow map (the map is square)

+     */

+    public BasicShadowRenderer(AssetManager manager, int size) {

+        shadowFB = new FrameBuffer(size, size, 1);

+        shadowMap = new Texture2D(size, size, Format.Depth);

+        shadowFB.setDepthTexture(shadowMap);

+        shadowCam = new Camera(size, size);

+

+        preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");

+        postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md");

+        postshadowMat.setTexture("ShadowMap", shadowMap);

+

+        dispPic.setTexture(manager, shadowMap, false);

+

+        for (int i = 0; i < points.length; i++) {

+            points[i] = new Vector3f();

+        }

+    }

+

+    public void initialize(RenderManager rm, ViewPort vp) {

+        renderManager = rm;

+        viewPort = vp;

+

+        reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());

+    }

+

+    public boolean isInitialized() {

+        return viewPort != null;

+    }

+

+    /**

+     * returns the light direction used for this processor

+     * @return 

+     */

+    public Vector3f getDirection() {

+        return direction;

+    }

+

+    /**

+     * sets the light direction to use to computs shadows

+     * @param direction 

+     */

+    public void setDirection(Vector3f direction) {

+        this.direction.set(direction).normalizeLocal();

+    }

+

+    /**

+     * debug only

+     * @return 

+     */

+    public Vector3f[] getPoints() {

+        return points;

+    }

+

+    /**

+     * debug only

+     * returns the shadow camera 

+     * @return 

+     */

+    public Camera getShadowCamera() {

+        return shadowCam;

+    }

+

+    public void postQueue(RenderQueue rq) {

+        GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);

+        if (occluders.size() == 0) {

+            noOccluders = true;

+            return;

+        } else {

+            noOccluders = false;

+        }

+

+        GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);

+

+        // update frustum points based on current camera

+        Camera viewCam = viewPort.getCamera();

+        ShadowUtil.updateFrustumPoints(viewCam,

+                viewCam.getFrustumNear(),

+                viewCam.getFrustumFar(),

+                1.0f,

+                points);

+

+        Vector3f frustaCenter = new Vector3f();

+        for (Vector3f point : points) {

+            frustaCenter.addLocal(point);

+        }

+        frustaCenter.multLocal(1f / 8f);

+

+        // update light direction

+        shadowCam.setProjectionMatrix(null);

+        shadowCam.setParallelProjection(true);

+//        shadowCam.setFrustumPerspective(45, 1, 1, 20);

+

+        shadowCam.lookAtDirection(direction, Vector3f.UNIT_Y);

+        shadowCam.update();

+        shadowCam.setLocation(frustaCenter);

+        shadowCam.update();

+        shadowCam.updateViewProjection();

+

+        // render shadow casters to shadow map

+        ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points);

+

+        Renderer r = renderManager.getRenderer();

+        renderManager.setCamera(shadowCam, false);

+        renderManager.setForcedMaterial(preshadowMat);

+

+        r.setFrameBuffer(shadowFB);

+        r.clearBuffers(false, true, false);

+        viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true);

+        r.setFrameBuffer(viewPort.getOutputFrameBuffer());

+

+        renderManager.setForcedMaterial(null);

+        renderManager.setCamera(viewCam, false);

+    }

+

+    /**

+     * debug only

+     * @return 

+     */

+    public Picture getDisplayPicture() {

+        return dispPic;

+    }

+

+    public void postFrame(FrameBuffer out) {

+        if (!noOccluders) {

+            postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix());

+            renderManager.setForcedMaterial(postshadowMat);

+            viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true);

+            renderManager.setForcedMaterial(null);

+        }

+    }

+

+    public void preFrame(float tpf) {

+    }

+

+    public void cleanup() {

+    }

+

+    public void reshape(ViewPort vp, int w, int h) {

+        dispPic.setPosition(w / 20f, h / 20f);

+        dispPic.setWidth(w / 5f);

+        dispPic.setHeight(h / 5f);

+    }

+}

diff --git a/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
new file mode 100644
index 0000000..9fa95cb
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * 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.shadow;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.OpaqueComparator;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.ShadowCompareMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)<br>
+ * It splits the view frustum in several parts and compute a shadow map for each
+ * one.<br> splits are distributed so that the closer they are from the camera,
+ * the smaller they are to maximize the resolution used of the shadow map.<br>
+ * This result in a better quality shadow than standard shadow mapping.<br> for
+ * more informations on this read this
+ * <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a><br>
+ * <p/>
+ * @author Rémy Bouquet aka Nehon
+ */
+public class PssmShadowRenderer implements SceneProcessor {
+
+    /**
+     * <code>FilterMode</code> specifies how shadows are filtered
+     */
+    public enum FilterMode {
+
+        /**
+         * Shadows are not filtered. Nearest sample is used, causing in blocky
+         * shadows.
+         */
+        Nearest,
+        /**
+         * Bilinear filtering is used. Has the potential of being hardware
+         * accelerated on some GPUs
+         */
+        Bilinear,
+        /**
+         * Dither-based sampling is used, very cheap but can look bad
+         * at low resolutions.
+         */
+        Dither,
+        /**
+         * 4x4 percentage-closer filtering is used. Shadows will be smoother
+         * at the cost of performance
+         */
+        PCF4,
+        /**
+         * 8x8 percentage-closer  filtering is used. Shadows will be smoother
+         * at the cost of performance
+         */
+        PCF8
+    }
+
+    /**
+     * Specifies the shadow comparison mode 
+     */
+    public enum CompareMode {
+
+        /**
+         * Shadow depth comparisons are done by using shader code
+         */
+        Software,
+        /**
+         * Shadow depth comparisons are done by using the GPU's dedicated
+         * shadowing pipeline.
+         */
+        Hardware;
+    }
+    private int nbSplits = 3;
+    private float lambda = 0.65f;
+    private float shadowIntensity = 0.7f;
+    private float zFarOverride = 0;
+    private RenderManager renderManager;
+    private ViewPort viewPort;
+    private FrameBuffer[] shadowFB;
+    private Texture2D[] shadowMaps;
+    private Texture2D dummyTex;
+    private Camera shadowCam;
+    private Material preshadowMat;
+    private Material postshadowMat;
+    private GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
+    private Matrix4f[] lightViewProjectionsMatrices;
+    private ColorRGBA splits;
+    private float[] splitsArray;
+    private boolean noOccluders = false;
+    private Vector3f direction = new Vector3f();
+    private AssetManager assetManager;
+    private boolean debug = false;
+    private float edgesThickness = 1.0f;
+    private FilterMode filterMode;
+    private CompareMode compareMode;
+    private Picture[] dispPic;
+    private Vector3f[] points = new Vector3f[8];
+    private boolean flushQueues = true;
+
+    /**
+     * Create a PSSM Shadow Renderer 
+     * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
+     * @param manager the application asset manager
+     * @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
+     * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 
+     *  @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 
+     */
+    public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) {
+        this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"));
+
+    }
+
+    /**
+     * Create a PSSM Shadow Renderer 
+     * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
+     * @param manager the application asset manager
+     * @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
+     * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 
+     * @param postShadowMat the material used for post shadows if you need to override it      * 
+     */
+    //TODO remove the postShadowMat when we have shader injection....or remove this todo if we are in 2020.
+    public PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat) {
+        assetManager = manager;
+        nbSplits = Math.max(Math.min(nbSplits, 4), 1);
+        this.nbSplits = nbSplits;
+
+        shadowFB = new FrameBuffer[nbSplits];
+        shadowMaps = new Texture2D[nbSplits];
+        dispPic = new Picture[nbSplits];
+        lightViewProjectionsMatrices = new Matrix4f[nbSplits];
+        splits = new ColorRGBA();
+        splitsArray = new float[nbSplits + 1];
+
+        //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+        dummyTex = new Texture2D(size, size, Format.RGBA8);
+
+        preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
+        this.postshadowMat = postShadowMat;
+
+        for (int i = 0; i < nbSplits; i++) {
+            lightViewProjectionsMatrices[i] = new Matrix4f();
+            shadowFB[i] = new FrameBuffer(size, size, 1);
+            shadowMaps[i] = new Texture2D(size, size, Format.Depth);
+
+            shadowFB[i].setDepthTexture(shadowMaps[i]);
+
+            //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+            shadowFB[i].setColorTexture(dummyTex);
+
+            postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
+
+            //quads for debuging purpose
+            dispPic[i] = new Picture("Picture" + i);
+            dispPic[i].setTexture(manager, shadowMaps[i], false);
+        }
+
+        setCompareMode(CompareMode.Hardware);
+        setFilterMode(FilterMode.Bilinear);
+        setShadowIntensity(0.7f);
+
+        shadowCam = new Camera(size, size);
+        shadowCam.setParallelProjection(true);
+
+        for (int i = 0; i < points.length; i++) {
+            points[i] = new Vector3f();
+        }
+    }
+
+    /**
+     * Sets the filtering mode for shadow edges see {@link FilterMode} for more info
+     * @param filterMode 
+     */
+    public void setFilterMode(FilterMode filterMode) {
+        if (filterMode == null) {
+            throw new NullPointerException();
+        }
+
+        if (this.filterMode == filterMode) {
+            return;
+        }
+
+        this.filterMode = filterMode;
+        postshadowMat.setInt("FilterMode", filterMode.ordinal());
+        postshadowMat.setFloat("PCFEdge", edgesThickness);
+        if (compareMode == CompareMode.Hardware) {
+            for (Texture2D shadowMap : shadowMaps) {
+                if (filterMode == FilterMode.Bilinear) {
+                    shadowMap.setMagFilter(MagFilter.Bilinear);
+                    shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+                } else {
+                    shadowMap.setMagFilter(MagFilter.Nearest);
+                    shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+                }
+            }
+        }
+    }
+
+    /**
+     * sets the shadow compare mode see {@link CompareMode} for more info
+     * @param compareMode 
+     */
+    public void setCompareMode(CompareMode compareMode) {
+        if (compareMode == null) {
+            throw new NullPointerException();
+        }
+
+        if (this.compareMode == compareMode) {
+            return;
+        }
+
+        this.compareMode = compareMode;
+        for (Texture2D shadowMap : shadowMaps) {
+            if (compareMode == CompareMode.Hardware) {
+                shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
+                if (filterMode == FilterMode.Bilinear) {
+                    shadowMap.setMagFilter(MagFilter.Bilinear);
+                    shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+                } else {
+                    shadowMap.setMagFilter(MagFilter.Nearest);
+                    shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+                }
+            } else {
+                shadowMap.setShadowCompareMode(ShadowCompareMode.Off);
+                shadowMap.setMagFilter(MagFilter.Nearest);
+                shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+            }
+        }
+        postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
+    }
+
+    //debug function that create a displayable frustrum
+    private Geometry createFrustum(Vector3f[] pts, int i) {
+        WireFrustum frustum = new WireFrustum(pts);
+        Geometry frustumMdl = new Geometry("f", frustum);
+        frustumMdl.setCullHint(Spatial.CullHint.Never);
+        frustumMdl.setShadowMode(ShadowMode.Off);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        frustumMdl.setMaterial(mat);
+        switch (i) {
+            case 0:
+                frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink);
+                break;
+            case 1:
+                frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red);
+                break;
+            case 2:
+                frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green);
+                break;
+            case 3:
+                frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue);
+                break;
+            default:
+                frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);
+                break;
+        }
+
+        frustumMdl.updateGeometricState();
+        return frustumMdl;
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        renderManager = rm;
+        viewPort = vp;
+    }
+
+    public boolean isInitialized() {
+        return viewPort != null;
+    }
+
+    /**
+     * returns the light direction used by the processor
+     * @return 
+     */
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    /**
+     * Sets the light direction to use to compute shadows
+     * @param direction 
+     */
+    public void setDirection(Vector3f direction) {
+        this.direction.set(direction).normalizeLocal();
+    }
+
+    @SuppressWarnings("fallthrough")
+    public void postQueue(RenderQueue rq) {
+        GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
+        if (occluders.size() == 0) {
+            return;
+        }
+
+        GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
+        if (receivers.size() == 0) {
+            return;
+        }
+
+        Camera viewCam = viewPort.getCamera();
+
+        float zFar = zFarOverride;
+        if (zFar == 0) {
+            zFar = viewCam.getFrustumFar();
+        }
+
+        //We prevent computing the frustum points and splits with zeroed or negative near clip value
+        float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f);
+        ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points);
+
+        //shadowCam.setDirection(direction);
+        shadowCam.getRotation().lookAt(direction, shadowCam.getUp());
+        shadowCam.update();
+        shadowCam.updateViewProjection();
+
+        PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda);
+
+
+        switch (splitsArray.length) {
+            case 5:
+                splits.a = splitsArray[4];
+            case 4:
+                splits.b = splitsArray[3];
+            case 3:
+                splits.g = splitsArray[2];
+            case 2:
+            case 1:
+                splits.r = splitsArray[1];
+                break;
+        }
+
+        Renderer r = renderManager.getRenderer();
+        renderManager.setForcedMaterial(preshadowMat);
+        renderManager.setForcedTechnique("PreShadow");
+
+        for (int i = 0; i < nbSplits; i++) {
+
+            // update frustum points based on current camera and split
+            ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points);
+
+            //Updating shadow cam with curent split frustra
+            ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders);
+
+            //saving light view projection matrix for this split
+            lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone();
+            renderManager.setCamera(shadowCam, false);
+
+            r.setFrameBuffer(shadowFB[i]);
+            r.clearBuffers(false, true, false);
+
+            // render shadow casters to shadow map
+            viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true);
+        }
+        if (flushQueues) {
+            occluders.clear();
+        }
+        //restore setting for future rendering
+        r.setFrameBuffer(viewPort.getOutputFrameBuffer());
+        renderManager.setForcedMaterial(null);
+        renderManager.setForcedTechnique(null);
+        renderManager.setCamera(viewCam, false);
+
+    }
+
+    //debug only : displays depth shadow maps
+    private void displayShadowMap(Renderer r) {
+        Camera cam = viewPort.getCamera();
+        renderManager.setCamera(cam, true);
+        int h = cam.getHeight();
+        for (int i = 0; i < dispPic.length; i++) {
+            dispPic[i].setPosition(64 * (i + 1) + 128 * i, h / 20f);
+            dispPic[i].setWidth(128);
+            dispPic[i].setHeight(128);
+            dispPic[i].updateGeometricState();
+            renderManager.renderGeometry(dispPic[i]);
+        }
+        renderManager.setCamera(cam, false);
+    }
+
+    /**For dubuging purpose
+     * Allow to "snapshot" the current frustrum to the scene
+     */
+    public void displayDebug() {
+        debug = true;
+    }
+
+    public void postFrame(FrameBuffer out) {
+        Camera cam = viewPort.getCamera();
+        if (!noOccluders) {
+            postshadowMat.setColor("Splits", splits);
+            for (int i = 0; i < nbSplits; i++) {
+                postshadowMat.setMatrix4("LightViewProjectionMatrix" + i, lightViewProjectionsMatrices[i]);
+            }
+            renderManager.setForcedMaterial(postshadowMat);
+
+            viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues);
+
+            renderManager.setForcedMaterial(null);
+            renderManager.setCamera(cam, false);
+
+        }
+        if (debug) {
+            displayShadowMap(renderManager.getRenderer());
+        }
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void cleanup() {
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    /**
+     * returns the labda parameter<br>
+     * see {@link setLambda(float lambda)}
+     * @return lambda
+     */
+    public float getLambda() {
+        return lambda;
+    }
+
+    /*
+     * Adjust the repartition of the different shadow maps in the shadow extend
+     * usualy goes from 0.0 to 1.0
+     * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged
+     * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend.
+     * the default value is set to 0.65f (theoric optimal value).
+     * @param lambda the lambda value.
+     */
+    public void setLambda(float lambda) {
+        this.lambda = lambda;
+    }
+
+    /**
+     * How far the shadows are rendered in the view
+     * see {@link setShadowZExtend(float zFar)}
+     * @return shadowZExtend
+     */
+    public float getShadowZExtend() {
+        return zFarOverride;
+    }
+
+    /**
+     * Set the distance from the eye where the shadows will be rendered
+     * default value is dynamicaly computed to the shadow casters/receivers union bound zFar, capped to view frustum far value.
+     * @param zFar the zFar values that override the computed one
+     */
+    public void setShadowZExtend(float zFar) {
+        this.zFarOverride = zFar;
+    }
+
+    /**
+     * returns the shdaow intensity<br>
+     * see {@link setShadowIntensity(float shadowIntensity)}
+     * @return shadowIntensity
+     */
+    public float getShadowIntensity() {
+        return shadowIntensity;
+    }
+
+    /**
+     * Set the shadowIntensity, the value should be between 0 and 1,
+     * a 0 value gives a bright and invisilble shadow,
+     * a 1 value gives a pitch black shadow,
+     * default is 0.7
+     * @param shadowIntensity the darkness of the shadow
+     */
+    public void setShadowIntensity(float shadowIntensity) {
+        this.shadowIntensity = shadowIntensity;
+        postshadowMat.setFloat("ShadowIntensity", shadowIntensity);
+    }
+
+    /**
+     * returns the edges thickness <br>
+     * see {@link setEdgesThickness(int edgesThickness)}
+     * @return edgesThickness
+     */
+    public int getEdgesThickness() {
+        return (int) (edgesThickness * 10);
+    }
+
+    /**
+     * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges
+     * @param edgesThickness 
+     */
+    public void setEdgesThickness(int edgesThickness) {
+        this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10));
+        this.edgesThickness *= 0.1f;
+        postshadowMat.setFloat("PCFEdge", edgesThickness);
+    }
+
+    /**
+     * returns true if the PssmRenderer flushed the shadow queues
+     * @return flushQueues
+     */
+    public boolean isFlushQueues() {
+        return flushQueues;
+    }
+
+    /**
+     * Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources.
+     * Make sure the last PssmRenderer in the stack DO flush the queues, but not the others
+     * @param flushQueues 
+     */
+    public void setFlushQueues(boolean flushQueues) {
+        this.flushQueues = flushQueues;
+    }
+}
diff --git a/engine/src/core/com/jme3/shadow/PssmShadowUtil.java b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
new file mode 100644
index 0000000..2633625
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 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.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ * <ul>
+ * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
+ * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
+ * </ul>
+ * for more info.
+ */
+public final class PssmShadowUtil {
+
+    /**
+     * Updates the frustum splits stores in <code>splits</code> using PSSM.
+     */
+    public static void updateFrustumSplits(float[] splits, float near, float far, float lambda) {
+        for (int i = 0; i < splits.length; i++) {
+            float IDM = i / (float) splits.length;
+            float log = near * FastMath.pow((far / near), IDM);
+            float uniform = near + (far - near) * IDM;
+            splits[i] = log * lambda + uniform * (1.0f - lambda);
+        }
+
+        // This is used to improve the correctness of the calculations. Our main near- and farplane
+        // of the camera always stay the same, no matter what happens.
+        splits[0] = near;
+        splits[splits.length - 1] = far;
+    }
+
+    /**
+     * Compute the Zfar in the model vieuw to adjust the Zfar distance for the splits calculation
+     */
+    public static float computeZFar(GeometryList occ, GeometryList recv, Camera cam) {
+        Matrix4f mat = cam.getViewMatrix();
+        BoundingBox bbOcc = ShadowUtil.computeUnionBound(occ, mat);
+        BoundingBox bbRecv = ShadowUtil.computeUnionBound(recv, mat);
+
+        return min(max(bbOcc.getZExtent() - bbOcc.getCenter().z, bbRecv.getZExtent() - bbRecv.getCenter().z), cam.getFrustumFar());
+    }
+}
diff --git a/engine/src/core/com/jme3/shadow/ShadowCamera.java b/engine/src/core/com/jme3/shadow/ShadowCamera.java
new file mode 100644
index 0000000..920e456
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowCamera.java
@@ -0,0 +1,75 @@
+/*

+ * Copyright (c) 2009-2010 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.shadow;

+

+import com.jme3.light.DirectionalLight;

+import com.jme3.light.Light;

+import com.jme3.light.PointLight;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+

+/**

+ * Creates a camera according to a light

+ * Handy to compute projection matrix of a light

+ * @author Kirill Vainer

+ */

+public class ShadowCamera {

+

+    private Vector3f[] points = new Vector3f[8];

+    private Light target;

+

+    public ShadowCamera(Light target) {

+        this.target = target;

+        for (int i = 0; i < points.length; i++) {

+            points[i] = new Vector3f();

+        }

+    }

+

+    /**

+     * Updates the camera view direction and position based on the light

+     */

+    public void updateLightCamera(Camera lightCam) {

+        if (target.getType() == Light.Type.Directional) {

+            DirectionalLight dl = (DirectionalLight) target;

+            lightCam.setParallelProjection(true);

+            lightCam.setLocation(Vector3f.ZERO);

+            lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y);

+            lightCam.setFrustum(-1, 1, -1, 1, 1, -1);

+        } else {

+            PointLight pl = (PointLight) target;

+            lightCam.setParallelProjection(false);

+            lightCam.setLocation(pl.getPosition());

+            // direction will have to be calculated automatically

+            lightCam.setFrustumPerspective(45, 1, 1, 300);

+        }

+    }

+}

diff --git a/engine/src/core/com/jme3/shadow/ShadowUtil.java b/engine/src/core/com/jme3/shadow/ShadowUtil.java
new file mode 100644
index 0000000..5832bda
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowUtil.java
@@ -0,0 +1,486 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.shadow;

+

+import com.jme3.bounding.BoundingBox;

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.math.Matrix4f;

+import com.jme3.math.Transform;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.renderer.queue.GeometryList;

+import com.jme3.scene.Geometry;

+import static java.lang.Math.max;

+import static java.lang.Math.min;

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * Includes various useful shadow mapping functions.

+ *

+ * @see

+ * <ul>

+ * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>

+ * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>

+ * </ul>

+ * for more info.

+ */

+public class ShadowUtil {

+

+    /**

+     * Updates a points arrays with the frustum corners of the provided camera.

+     * @param viewCam

+     * @param points 

+     */

+    public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {

+        int w = viewCam.getWidth();

+        int h = viewCam.getHeight();

+        float n = viewCam.getFrustumNear();

+        float f = viewCam.getFrustumFar();

+

+        points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));

+        points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));

+        points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));

+        points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));

+

+        points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));

+        points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));

+        points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));

+        points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));

+    }

+

+    /**

+     * Updates the points array to contain the frustum corners of the given

+     * camera. The nearOverride and farOverride variables can be used

+     * to override the camera's near/far values with own values.

+     *

+     * TODO: Reduce creation of new vectors

+     *

+     * @param viewCam

+     * @param nearOverride

+     * @param farOverride

+     */

+    public static void updateFrustumPoints(Camera viewCam,

+            float nearOverride,

+            float farOverride,

+            float scale,

+            Vector3f[] points) {

+

+        Vector3f pos = viewCam.getLocation();

+        Vector3f dir = viewCam.getDirection();

+        Vector3f up = viewCam.getUp();

+

+        float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();

+        float near = nearOverride;

+        float far = farOverride;

+        float ftop = viewCam.getFrustumTop();

+        float fright = viewCam.getFrustumRight();

+        float ratio = fright / ftop;

+

+        float near_height;

+        float near_width;

+        float far_height;

+        float far_width;

+

+        if (viewCam.isParallelProjection()) {

+            near_height = ftop;

+            near_width = near_height * ratio;

+            far_height = ftop;

+            far_width = far_height * ratio;

+        } else {

+            near_height = depthHeightRatio * near;

+            near_width = near_height * ratio;

+            far_height = depthHeightRatio * far;

+            far_width = far_height * ratio;

+        }

+

+        Vector3f right = dir.cross(up).normalizeLocal();

+

+        Vector3f temp = new Vector3f();

+        temp.set(dir).multLocal(far).addLocal(pos);

+        Vector3f farCenter = temp.clone();

+        temp.set(dir).multLocal(near).addLocal(pos);

+        Vector3f nearCenter = temp.clone();

+

+        Vector3f nearUp = temp.set(up).multLocal(near_height).clone();

+        Vector3f farUp = temp.set(up).multLocal(far_height).clone();

+        Vector3f nearRight = temp.set(right).multLocal(near_width).clone();

+        Vector3f farRight = temp.set(right).multLocal(far_width).clone();

+

+        points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);

+        points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);

+        points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);

+        points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);

+

+        points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);

+        points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);

+        points[6].set(farCenter).addLocal(farUp).addLocal(farRight);

+        points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);

+

+        if (scale != 1.0f) {

+            // find center of frustum

+            Vector3f center = new Vector3f();

+            for (int i = 0; i < 8; i++) {

+                center.addLocal(points[i]);

+            }

+            center.divideLocal(8f);

+

+            Vector3f cDir = new Vector3f();

+            for (int i = 0; i < 8; i++) {

+                cDir.set(points[i]).subtractLocal(center);

+                cDir.multLocal(scale - 1.0f);

+                points[i].addLocal(cDir);

+            }

+        }

+    }

+

+    /**

+     * Compute bounds of a geomList

+     * @param list

+     * @param transform

+     * @return 

+     */

+    public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {

+        BoundingBox bbox = new BoundingBox();

+        for (int i = 0; i < list.size(); i++) {

+            BoundingVolume vol = list.get(i).getWorldBound();

+            BoundingVolume newVol = vol.transform(transform);

+            //Nehon : prevent NaN and infinity values to screw the final bounding box

+            if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {

+                bbox.mergeLocal(newVol);

+            }

+        }

+        return bbox;

+    }

+

+    /**

+     * Compute bounds of a geomList

+     * @param list

+     * @param mat

+     * @return 

+     */

+    public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {

+        BoundingBox bbox = new BoundingBox();

+        BoundingVolume store = null;

+        for (int i = 0; i < list.size(); i++) {

+            BoundingVolume vol = list.get(i).getWorldBound();

+            store = vol.clone().transform(mat, null);

+            //Nehon : prevent NaN and infinity values to screw the final bounding box

+            if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {

+                bbox.mergeLocal(store);

+            }

+        }

+        return bbox;

+    }

+

+    /**

+     * Computes the bounds of multiple bounding volumes

+     * @param bv

+     * @return 

+     */

+    public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {

+        BoundingBox bbox = new BoundingBox();

+        for (int i = 0; i < bv.size(); i++) {

+            BoundingVolume vol = bv.get(i);

+            bbox.mergeLocal(vol);

+        }

+        return bbox;

+    }

+

+    /**

+     * Compute bounds from an array of points

+     * @param pts

+     * @param transform

+     * @return 

+     */

+    public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {

+        Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);

+        Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);

+        Vector3f temp = new Vector3f();

+        for (int i = 0; i < pts.length; i++) {

+            transform.transformVector(pts[i], temp);

+

+            min.minLocal(temp);

+            max.maxLocal(temp);

+        }

+        Vector3f center = min.add(max).multLocal(0.5f);

+        Vector3f extent = max.subtract(min).multLocal(0.5f);

+        return new BoundingBox(center, extent.x, extent.y, extent.z);

+    }

+

+    /**

+     * Compute bounds from an array of points

+     * @param pts

+     * @param mat

+     * @return 

+     */

+    public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {

+        Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);

+        Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);

+        Vector3f temp = new Vector3f();

+

+        for (int i = 0; i < pts.length; i++) {

+            float w = mat.multProj(pts[i], temp);

+

+            temp.x /= w;

+            temp.y /= w;

+            // Why was this commented out?

+            temp.z /= w;

+

+            min.minLocal(temp);

+            max.maxLocal(temp);

+        }

+

+        Vector3f center = min.add(max).multLocal(0.5f);

+        Vector3f extent = max.subtract(min).multLocal(0.5f);

+        //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned

+        return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);

+    }

+

+    /**

+     * Updates the shadow camera to properly contain the given

+     * points (which contain the eye camera frustum corners)

+     *

+     * @param shadowCam

+     * @param points

+     */

+    public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {

+        boolean ortho = shadowCam.isParallelProjection();

+        shadowCam.setProjectionMatrix(null);

+

+        if (ortho) {

+            shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);

+        } else {

+            shadowCam.setFrustumPerspective(45, 1, 1, 150);

+        }

+

+        Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();

+        Matrix4f projMatrix = shadowCam.getProjectionMatrix();

+

+        BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);

+

+        Vector3f splitMin = splitBB.getMin(null);

+        Vector3f splitMax = splitBB.getMax(null);

+

+//        splitMin.z = 0;

+

+        // Create the crop matrix.

+        float scaleX, scaleY, scaleZ;

+        float offsetX, offsetY, offsetZ;

+

+        scaleX = 2.0f / (splitMax.x - splitMin.x);

+        scaleY = 2.0f / (splitMax.y - splitMin.y);

+        offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;

+        offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;

+        scaleZ = 1.0f / (splitMax.z - splitMin.z);

+        offsetZ = -splitMin.z * scaleZ;

+

+        Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,

+                0f, scaleY, 0f, offsetY,

+                0f, 0f, scaleZ, offsetZ,

+                0f, 0f, 0f, 1f);

+

+

+        Matrix4f result = new Matrix4f();

+        result.set(cropMatrix);

+        result.multLocal(projMatrix);

+

+        shadowCam.setProjectionMatrix(result);

+    }

+

+    /**

+     * Updates the shadow camera to properly contain the given

+     * points (which contain the eye camera frustum corners) and the

+     * shadow occluder objects.

+     *

+     * @param occluders

+     * @param receivers

+     * @param shadowCam

+     * @param points

+     */

+    public static void updateShadowCamera(GeometryList occluders,

+            GeometryList receivers,

+            Camera shadowCam,

+            Vector3f[] points) {

+        updateShadowCamera(occluders, receivers, shadowCam, points, null);

+    }

+

+    /**

+     * Updates the shadow camera to properly contain the given

+     * points (which contain the eye camera frustum corners) and the

+     * shadow occluder objects.

+     * 

+     * @param occluders

+     * @param shadowCam

+     * @param points

+     */

+    public static void updateShadowCamera(GeometryList occluders,

+            GeometryList receivers,

+            Camera shadowCam,

+            Vector3f[] points,

+            GeometryList splitOccluders) {

+

+        boolean ortho = shadowCam.isParallelProjection();

+

+        shadowCam.setProjectionMatrix(null);

+

+        if (ortho) {

+            shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);

+        } else {

+            shadowCam.setFrustumPerspective(45, 1, 1, 150);

+        }

+

+        // create transform to rotate points to viewspace        

+        Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();

+

+        BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);

+

+        ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();

+        for (int i = 0; i < receivers.size(); i++) {

+            // convert bounding box to light's viewproj space

+            Geometry receiver = receivers.get(i);

+            BoundingVolume bv = receiver.getWorldBound();

+            BoundingVolume recvBox = bv.transform(viewProjMatrix, null);

+

+            if (splitBB.intersects(recvBox)) {

+                visRecvList.add(recvBox);

+            }

+        }

+

+        ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();

+        for (int i = 0; i < occluders.size(); i++) {

+            // convert bounding box to light's viewproj space

+            Geometry occluder = occluders.get(i);

+            BoundingVolume bv = occluder.getWorldBound();

+            BoundingVolume occBox = bv.transform(viewProjMatrix, null);

+

+            boolean intersects = splitBB.intersects(occBox);

+            if (!intersects && occBox instanceof BoundingBox) {

+                BoundingBox occBB = (BoundingBox) occBox;

+                //Kirill 01/10/2011

+                // Extend the occluder further into the frustum

+                // This fixes shadow dissapearing issues when

+                // the caster itself is not in the view camera

+                // but its shadow is in the camera

+                //      The number is in world units

+                occBB.setZExtent(occBB.getZExtent() + 50);

+                occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));

+                if (splitBB.intersects(occBB)) {

+                    // To prevent extending the depth range too much

+                    // We return the bound to its former shape

+                    // Before adding it

+                    occBB.setZExtent(occBB.getZExtent() - 50);

+                    occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));

+                    visOccList.add(occBox);

+                    if (splitOccluders != null) {

+                        splitOccluders.add(occluder);

+                    }

+                }

+            } else if (intersects) {

+                visOccList.add(occBox);

+                if (splitOccluders != null) {

+                    splitOccluders.add(occluder);

+                }

+            }

+        }

+

+        BoundingBox casterBB = computeUnionBound(visOccList);

+        BoundingBox receiverBB = computeUnionBound(visRecvList);

+

+        //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows

+        if (visOccList.size() != visRecvList.size()) {

+            casterBB.setXExtent(casterBB.getXExtent() + 2.0f);

+            casterBB.setYExtent(casterBB.getYExtent() + 2.0f);

+            casterBB.setZExtent(casterBB.getZExtent() + 2.0f);

+        }

+

+        Vector3f casterMin = casterBB.getMin(null);

+        Vector3f casterMax = casterBB.getMax(null);

+

+        Vector3f receiverMin = receiverBB.getMin(null);

+        Vector3f receiverMax = receiverBB.getMax(null);

+

+        Vector3f splitMin = splitBB.getMin(null);

+        Vector3f splitMax = splitBB.getMax(null);

+

+        splitMin.z = 0;

+

+        if (!ortho) {

+            shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);

+        }

+

+        Matrix4f projMatrix = shadowCam.getProjectionMatrix();

+

+        Vector3f cropMin = new Vector3f();

+        Vector3f cropMax = new Vector3f();

+

+        // IMPORTANT: Special handling for Z values

+        cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);

+        cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);

+

+        cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);

+        cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);

+

+        cropMin.z = min(casterMin.z, splitMin.z);

+        cropMax.z = min(receiverMax.z, splitMax.z);

+

+

+        // Create the crop matrix.

+        float scaleX, scaleY, scaleZ;

+        float offsetX, offsetY, offsetZ;

+

+        scaleX = (2.0f) / (cropMax.x - cropMin.x);

+        scaleY = (2.0f) / (cropMax.y - cropMin.y);

+

+        offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;

+        offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;

+

+        scaleZ = 1.0f / (cropMax.z - cropMin.z);

+        offsetZ = -cropMin.z * scaleZ;

+

+

+

+        Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,

+                0f, scaleY, 0f, offsetY,

+                0f, 0f, scaleZ, offsetZ,

+                0f, 0f, 0f, 1f);

+

+

+        Matrix4f result = new Matrix4f();

+        result.set(cropMatrix);

+        result.multLocal(projMatrix);

+

+        shadowCam.setProjectionMatrix(result);

+

+    }

+}

diff --git a/engine/src/core/com/jme3/system/Annotations.java b/engine/src/core/com/jme3/system/Annotations.java
new file mode 100644
index 0000000..0cf72eb
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Annotations.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import checkers.quals.TypeQualifier;
+import java.lang.annotation.*;
+
+/**
+ * This class contains the Annotation definitions for jME3. Mostly these are used
+ * for code error checking.
+ * @author normenhansen
+ */
+public class Annotations {
+
+    /**
+     * Annotation used for math primitive fields, method parameters or method return values.
+     * Specifies that the primitve is read only and should not be changed.
+     */
+    @Documented
+    @Retention(RetentionPolicy.RUNTIME)
+    @TypeQualifier
+    @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.TYPE, ElementType.METHOD})
+    public @interface ReadOnly {
+    }
+
+    /**
+     * Annotation used for methods in math primitives that are destructive to the
+     * object (xxxLocal, setXXX etc.).
+     */
+    @Documented
+    @Retention(RetentionPolicy.RUNTIME)
+    @TypeQualifier
+    @Target({ElementType.METHOD})
+    public @interface Destructive {
+    }
+
+    /**
+     * Annotation used for public methods that are not to be called by users.
+     * Examples include update() methods etc.
+     */
+    public @interface Internal {
+    }
+}
diff --git a/engine/src/core/com/jme3/system/AppSettings.java b/engine/src/core/com/jme3/system/AppSettings.java
new file mode 100644
index 0000000..ad9facc
--- /dev/null
+++ b/engine/src/core/com/jme3/system/AppSettings.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+/**
+ * <code>AppSettings</code> provides a store of configuration
+ * to be used by the application. 
+ * <p>
+ * By default only the {@link JmeContext context} uses the configuration,
+ * however the user may set and retrieve the settings as well. 
+ * 
+ * @author Kirill Vainer
+ */
+public final class AppSettings extends HashMap<String, Object> {
+
+    private static final AppSettings defaults = new AppSettings(false);
+    
+    /**
+     * Use LWJGL as the display system and force using the OpenGL1.1 renderer.
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     */
+    public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1";
+    
+    /**
+     * Use LWJGL as the display system and force using the OpenGL2.0 renderer.
+     * <p>
+     * If the underlying system does not support OpenGL2.0, then the context
+     * initialization will throw an exception.
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     */
+    public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2";
+    
+    /**
+     * Use LWJGL as the display system and force using the core OpenGL3.3 renderer.
+     * <p>
+     * If the underlying system does not support OpenGL3.3, then the context
+     * initialization will throw an exception. Note that currently jMonkeyEngine
+     * does not have any shaders that support OpenGL3.3 therefore this 
+     * option is not useful.
+     * 
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     */
+    public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3";
+    
+    /**
+     * Use LWJGL as the display system and allow the context 
+     * to choose an appropriate renderer based on system capabilities.
+     * <p>
+     * If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will
+     * be used, otherwise, the OpenGL1.1 renderer is used.
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     */
+    public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any";
+    
+    /**
+     * The JOGL renderer is no longer supported by jME.
+     * 
+     * @deprecated Use the LWJGL renderer instead.
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     */
+    @Deprecated
+    public static final String JOGL = "JOGL";
+    
+    /**
+     * The "NULL" option is no longer supported
+     * 
+     * @deprecated Specify the "null" value instead
+     * 
+     * @see AppSettings#setRenderer(java.lang.String) 
+     * @see AppSettings#setAudioRenderer(java.lang.String) 
+     */
+    @Deprecated
+    public static final String NULL = "NULL";
+    
+    /**
+     * Use the LWJGL OpenAL based renderer for audio capabilities.
+     * 
+     * @see AppSettings#setAudioRenderer(java.lang.String) 
+     */
+    public static final String LWJGL_OPENAL = "LWJGL";
+
+    static {
+        defaults.put("Width", 640);
+        defaults.put("Height", 480);
+        defaults.put("BitsPerPixel", 24);
+        defaults.put("Frequency", 60);
+        defaults.put("DepthBits", 24);
+        defaults.put("StencilBits", 0);
+        defaults.put("Samples", 0);
+        defaults.put("Fullscreen", false);
+        defaults.put("Title", "jMonkey Engine 3.0");
+        defaults.put("Renderer", LWJGL_OPENGL2);
+        defaults.put("AudioRenderer", LWJGL_OPENAL);
+        defaults.put("DisableJoysticks", true);
+        defaults.put("UseInput", true);
+        defaults.put("VSync", false);
+        defaults.put("FrameRate", -1);
+        defaults.put("SettingsDialogImage", "/com/jme3/app/Monkey.png");
+      //  defaults.put("Icons", null);
+    }
+
+    /**
+     * Create a new instance of <code>AppSettings</code>.
+     * <p>
+     * If <code>loadDefaults</code> is true, then the default settings
+     * will be set on the AppSettings. 
+     * Use false if you want to change some settings but you would like the
+     * application to load settings from previous launches.
+     * 
+     * @param loadDefaults If default settings are to be loaded.
+     */
+    public AppSettings(boolean loadDefaults) {
+        if (loadDefaults) {
+            putAll(defaults);
+        }
+    }
+
+    /**
+     * Copies all settings from <code>other</code> to <code>this</code>
+     * AppSettings. 
+     * <p>
+     * Any settings that are specified in other will overwrite settings
+     * set on this AppSettings.
+     *  
+     * @param other The AppSettings to copy the settings from
+     */
+    public void copyFrom(AppSettings other) {
+        this.putAll(other);
+    }
+
+    /**
+     * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except
+     * doesn't overwrite settings that are already set.
+     * 
+     * @param other  The AppSettings to merge the settings from
+     */
+    public void mergeFrom(AppSettings other) {
+        for (String key : other.keySet()) {
+            if (get(key) == null) {
+                put(key, other.get(key));
+            }
+        }
+    }
+
+    /**
+     * Loads the settings from the given properties input stream.
+     * 
+     * @param in The InputStream to load from
+     * @throws IOException If an IOException occurs
+     * 
+     * @see #save(java.io.OutputStream) 
+     */
+    public void load(InputStream in) throws IOException {
+        Properties props = new Properties();
+        props.load(in);
+        for (Map.Entry<Object, Object> entry : props.entrySet()) {
+            String key = (String) entry.getKey();
+            String val = (String) entry.getValue();
+            if (val != null) {
+                val = val.trim();
+            }
+            if (key.endsWith("(int)")) {
+                key = key.substring(0, key.length() - 5);
+                int iVal = Integer.parseInt(val);
+                putInteger(key, iVal);
+            } else if (key.endsWith("(string)")) {
+                putString(key.substring(0, key.length() - 8), val);
+            } else if (key.endsWith("(bool)")) {
+                boolean bVal = Boolean.parseBoolean(val);
+                putBoolean(key.substring(0, key.length() - 6), bVal);
+            } else {
+                throw new IOException("Cannot parse key: " + key);
+            }
+        }
+    }
+
+    /**
+     * Saves all settings to the given properties output stream.
+     * 
+     * @param out The OutputStream to write to
+     * @throws IOException If an IOException occurs
+     * 
+     * @see #load(java.io.InputStream) 
+     */
+    public void save(OutputStream out) throws IOException {
+        Properties props = new Properties();
+        for (Map.Entry<String, Object> entry : entrySet()) {
+            Object val = entry.getValue();
+            String type;
+            if (val instanceof Integer) {
+                type = "(int)";
+            } else if (val instanceof String) {
+                type = "(string)";
+            } else if (val instanceof Boolean) {
+                type = "(bool)";
+            } else {
+                throw new UnsupportedEncodingException();
+            }
+            props.setProperty(entry.getKey() + type, val.toString());
+        }
+        props.store(out, "jME3 AppSettings");
+    }
+
+    /**
+     * Loads settings previously saved in the Java preferences.
+     * 
+     * @param preferencesKey The preferencesKey previously used to save the settings.
+     * @throws BackingStoreException If an exception occurs with the preferences
+     * 
+     * @see #save(java.lang.String) 
+     */
+    public void load(String preferencesKey) throws BackingStoreException {
+        Preferences prefs = Preferences.userRoot().node(preferencesKey);
+        String[] keys = prefs.keys();
+        if (keys != null) {
+            for (String key : keys) {
+                Object defaultValue = defaults.get(key);
+                if (defaultValue instanceof Integer) {
+                    put(key, prefs.getInt(key, (Integer) defaultValue));
+                } else if (defaultValue instanceof String) {
+                    put(key, prefs.get(key, (String) defaultValue));
+                } else if (defaultValue instanceof Boolean) {
+                    put(key, prefs.getBoolean(key, (Boolean) defaultValue));
+                }
+            }
+        }
+    }
+
+    /**
+     * Saves settings into the Java preferences.
+     * <p>
+     * On the Windows operating system, the preferences are saved in the registry
+     * at the following key:<br>
+     * <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code>
+     * 
+     * @param preferencesKey The preferences key to save at. Generally the 
+     * application's unique name. 
+     * 
+     * @throws BackingStoreException If an exception occurs with the preferences
+     */
+    public void save(String preferencesKey) throws BackingStoreException {
+        Preferences prefs = Preferences.userRoot().node(preferencesKey);
+        for (String key : keySet()) {         
+            prefs.put(key, get(key).toString());
+        }
+    }
+
+    /**
+     * Get an integer from the settings.
+     * <p>
+     * If the key is not set, then 0 is returned.
+     */
+    public int getInteger(String key) {
+        Integer i = (Integer) get(key);
+        if (i == null) {
+            return 0;
+        }
+
+        return i.intValue();
+    }
+
+    /**
+     * Get a boolean from the settings.
+     * <p>
+     * If the key is not set, then false is returned.
+     */
+    public boolean getBoolean(String key) {
+        Boolean b = (Boolean) get(key);
+        if (b == null) {
+            return false;
+        }
+
+        return b.booleanValue();
+    }
+
+    /**
+     * Get a string from the settings.
+     * <p>
+     * If the key is not set, then null is returned.
+     */
+    public String getString(String key) {
+        String s = (String) get(key);
+        if (s == null) {
+            return null;
+        }
+
+        return s;
+    }
+
+    /**
+     * Set an integer on the settings.
+     */
+    public void putInteger(String key, int value) {
+        put(key, Integer.valueOf(value));
+    }
+
+    /**
+     * Set a boolean on the settings.
+     */
+    public void putBoolean(String key, boolean value) {
+        put(key, Boolean.valueOf(value));
+    }
+
+    /**
+     * Set a string on the settings.
+     */
+    public void putString(String key, String value) {
+        put(key, value);
+    }
+
+    /**
+     * @param frameRate The frame-rate is the upper limit on how high
+     * the application's frames-per-second can go.
+     * (Default: -1 no frame rate limit imposed)
+     */
+    public void setFrameRate(int frameRate) {
+        putInteger("FrameRate", frameRate);
+    }
+
+    /**
+     * @param use If true, the application will initialize and use input.
+     * Set to false for headless applications that do not require keyboard
+     * or mouse input.
+     * (Default: true)
+     */
+    public void setUseInput(boolean use) {
+        putBoolean("UseInput", use);
+    }
+
+    /**
+     * @param use If true, the application will initialize and use joystick
+     * input. Set to false if no joystick input is desired.
+     * (Default: false)
+     */
+    public void setUseJoysticks(boolean use) {
+        putBoolean("DisableJoysticks", !use);
+    }
+
+    /**
+     * Set the graphics renderer to use, one of:<br>
+     * <ul>
+     * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
+     * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li>
+     * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li>
+     * <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate 
+     * OpenGL version based on system capabilities</li>
+     * <li>null - Disable graphics rendering</li>
+     * </ul>
+     * @param renderer The renderer to set
+     * (Default: AppSettings.LWJGL_OPENGL2)
+     */
+    public void setRenderer(String renderer) {
+        putString("Renderer", renderer);
+    }
+
+    /**
+     * Set a custom graphics renderer to use. The class should implement 
+     * the {@link JmeContext} interface.
+     * @param clazz The custom context class.
+     * (Default: not set)
+     */
+    public void setCustomRenderer(Class<? extends JmeContext> clazz){
+        put("Renderer", "CUSTOM" + clazz.getName());
+    }
+
+    /**
+     * Set the audio renderer to use. One of:<br>
+     * <ul>
+     * <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li>
+     * <li>null - Disable audio</li>
+     * </ul>
+     * @param audioRenderer 
+     * (Default: LWJGL)
+     */
+    public void setAudioRenderer(String audioRenderer) {
+        putString("AudioRenderer", audioRenderer);
+    }
+
+    /**
+     * @param value the width for the rendering display.
+     * (Default: 640)
+     */
+    public void setWidth(int value) {
+        putInteger("Width", value);
+    }
+
+    /**
+     * @param value the height for the rendering display.
+     * (Default: 480)
+     */
+    public void setHeight(int value) {
+        putInteger("Height", value);
+    }
+
+    /**
+     * Set the resolution for the rendering display
+     * @param width The width
+     * @param height The height
+     * (Default: 640x480)
+     */
+    public void setResolution(int width, int height) {
+        setWidth(width);
+        setHeight(height);
+    }
+
+    /**
+     * Set the frequency, also known as refresh rate, for the 
+     * rendering display.
+     * @param value The frequency
+     * (Default: 60)
+     */
+    public void setFrequency(int value) {
+        putInteger("Frequency", value);
+    }
+
+    /**
+     * Sets the number of depth bits to use.
+     * <p>
+     * The number of depth bits specifies the precision of the depth buffer.
+     * To increase precision, specify 32 bits. To decrease precision, specify
+     * 16 bits. On some platforms 24 bits might not be supported, in that case,
+     * specify 16 bits.<p>
+     * (Default: 24)
+     * 
+     * @param value The depth bits
+     */
+    public void setDepthBits(int value){
+        putInteger("DepthBits", value);
+    }
+    
+    /**
+     * Set the number of stencil bits.
+     * <p>
+     * This value is only relevant when the stencil buffer is being used.
+     * Specify 8 to indicate an 8-bit stencil buffer, specify 0 to disable
+     * the stencil buffer.
+     * </p>
+     * (Default: 0)
+     * 
+     * @param value Number of stencil bits
+     */
+    public void setStencilBits(int value){
+        putInteger("StencilBits", value);
+    }
+    
+    /**
+     * Set the bits per pixel for the display. Appropriate
+     * values are 16 for RGB565 color format, or 24 for RGB8 color format.
+     * 
+     * @param value The bits per pixel to set
+     * (Default: 24)
+     */
+    public void setBitsPerPixel(int value) {
+        putInteger("BitsPerPixel", value);
+    }
+
+    /**
+     * Set the number of samples per pixel. A value of 1 indicates
+     * each pixel should be single-sampled, higher values indicate
+     * a pixel should be multi-sampled.
+     * 
+     * @param value The number of samples
+     * (Default: 1)
+     */
+    public void setSamples(int value) {
+        putInteger("Samples", value);
+    }
+
+    /**
+     * @param title The title of the rendering display
+     * (Default: jMonkeyEngine 3.0)
+     */
+    public void setTitle(String title) {
+        putString("Title", title);
+    }
+
+    /**
+     * @param value true to enable full-screen rendering, false to render in a window
+     * (Default: false)
+     */
+    public void setFullscreen(boolean value) {
+        putBoolean("Fullscreen", value);
+    }
+
+    /**
+     * Set to true to enable vertical-synchronization, limiting and synchronizing
+     * every frame rendered to the monitor's refresh rate.
+     * @param value 
+     * (Default: false)
+     */
+    public void setVSync(boolean value) {
+        putBoolean("VSync", value);
+    }
+    
+    /**
+     * Enable 3D stereo.
+     * <p>This feature requires hardware support from the GPU driver. 
+     * @see <a href="http://en.wikipedia.org/wiki/Quad_buffering">http://en.wikipedia.org/wiki/Quad_buffering</a><br />
+     * Once enabled, filters or scene processors that handle 3D stereo rendering
+     * could use this feature to render using hardware 3D stereo.</p>
+     * (Default: false)
+     */
+    public void setStereo3D(boolean value){
+        putBoolean("Stereo3D", value);
+    }
+
+    /**
+     * Sets the application icons to be used, with the most preferred first.
+     * For Windows you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar,
+     * the latter for the alt-tab icon.
+     * Linux (and similar platforms) expect one 32x32 icon.
+     * Mac OS X should be supplied one 128x128 icon.
+     * <br/>
+     * The icon is used for the settings window, and the LWJGL render window. Not currently supported for JOGL.
+     * Note that a bug in Java 6 (bug ID 6445278, currently hidden but available in Google cache) currently prevents
+     * the icon working for alt-tab on the settings dialog in Windows.
+     *
+     * @param value An array of BufferedImages to use as icons.
+     * (Default: not set)
+     */
+    public void setIcons(Object[] value) {
+        put("Icons", value);
+    }
+    
+    /**
+     * Sets the path of the settings dialog image to use.
+     * <p>
+     * The image will be displayed in the settings dialog when the 
+     * application is started.
+     * </p>
+     * (Default: /com/jme3/app/Monkey.png)
+     * 
+     * @param path The path to the image in the classpath. 
+     */
+    public void setSettingsDialogImage(String path) {
+        putString("SettingsDialogImage", path);
+    }
+
+    /**
+     * Get the framerate.
+     * @see #setFrameRate(int) 
+     */
+    public int getFrameRate() {
+        return getInteger("FrameRate");
+    }
+
+    /**
+     * Get the use input state.
+     * @see #setUseInput(boolean) 
+     */
+    public boolean useInput() {
+        return getBoolean("UseInput");
+    }
+
+    /**
+     * Get the renderer
+     * @see #setRenderer(java.lang.String) 
+     */
+    public String getRenderer() {
+        return getString("Renderer");
+    }
+
+    /**
+     * Get the width
+     * @see #setWidth(int) 
+     */
+    public int getWidth() {
+        return getInteger("Width");
+    }
+
+    /**
+     * Get the height
+     * @see #setHeight(int) 
+     */
+    public int getHeight() {
+        return getInteger("Height");
+    }
+
+    /**
+     * Get the bits per pixel
+     * @see #setBitsPerPixel(int) 
+     */
+    public int getBitsPerPixel() {
+        return getInteger("BitsPerPixel");
+    }
+
+    /**
+     * Get the frequency
+     * @see #setFrequency(int) 
+     */
+    public int getFrequency() {
+        return getInteger("Frequency");
+    }
+
+    /**
+     * Get the number of depth bits
+     * @see #setDepthBits(int)
+     */
+    public int getDepthBits() {
+        return getInteger("DepthBits");
+    }
+
+    /**
+     * Get the number of stencil bits
+     * @see #setStencilBits(int) 
+     */
+    public int getStencilBits() {
+        return getInteger("StencilBits");
+    }
+
+    /**
+     * Get the number of samples
+     * @see #setSamples(int) 
+     */
+    public int getSamples() {
+        return getInteger("Samples");
+    }
+
+    /**
+     * Get the application title
+     * @see #setTitle(java.lang.String) 
+     */
+    public String getTitle() {
+        return getString("Title");
+    }
+
+    /**
+     * Get the vsync state
+     * @see #setVSync(boolean) 
+     */
+    public boolean isVSync() {
+        return getBoolean("VSync");
+    }
+
+    /**
+     * Get the fullscreen state
+     * @see #setFullscreen(boolean) 
+     */
+    public boolean isFullscreen() {
+        return getBoolean("Fullscreen");
+    }
+
+    /**
+     * Get the use joysticks state
+     * @see #setUseJoysticks(boolean) 
+     */
+    public boolean useJoysticks() {
+        return !getBoolean("DisableJoysticks");
+    }
+
+    /**
+     * Get the audio renderer
+     * @see #setAudioRenderer(java.lang.String) 
+     */
+    public String getAudioRenderer() {
+        return getString("AudioRenderer");
+    }
+    
+    /**
+     * Get the stereo 3D state
+     * @see #setStereo3D(boolean) 
+     */
+    public boolean useStereo3D(){
+        return getBoolean("Stereo3D");  
+    }
+
+    /**
+     * Get the icon array
+     * @see #setIcons(java.lang.Object[]) 
+     */
+    public Object[] getIcons() {
+        return (Object[]) get("Icons");
+    }
+    
+    /**
+     * Get the settings dialog image
+     * @see #setSettingsDialogImage(java.lang.String) 
+     */
+    public String getSettingsDialogImage() {
+        return getString("SettingsDialogImage");
+    }
+}
diff --git a/engine/src/core/com/jme3/system/JmeContext.java b/engine/src/core/com/jme3/system/JmeContext.java
new file mode 100644
index 0000000..37283a5
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeContext.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.renderer.Renderer;
+
+/**
+ * Represents a rendering context within the engine.
+ */
+public interface JmeContext {
+
+    /**
+     * The type of context.
+     */
+    public enum Type {
+        /**
+         * A display can represent a windowed or a fullscreen-exclusive display.
+         * If windowed, the graphics are rendered to a new on-screen surface
+         * enclosed in a window defined by the operating system. Implementations
+         * are encouraged to not use AWT or Swing to create the OpenGL display
+         * but rather use native operating system functions to set up a native
+         * display with the windowing system.
+         */
+        Display,
+        
+        /**
+         * A canvas type context makes a rendering surface available as an
+         * AWT {@link java.awt.Canvas} object that can be embedded in a Swing/AWT
+         * frame. To retrieve the Canvas object, you should cast the context
+         * to {@link JmeCanvasContext}.
+         */
+        Canvas,
+        
+        /**
+         * An <code>OffscreenSurface</code> is a context that is not visible
+         * by the user. The application can use the offscreen surface to do
+         * General Purpose GPU computations or render a scene into a buffer
+         * in order to save it as a screenshot, video or send through a network.
+         */
+        OffscreenSurface,
+
+        /**
+         * A <code>Headless</code> context is not visible and does not have
+         * any drawable surface. The implementation does not provide any
+         * display, input, or sound support.
+         */
+        Headless;
+    }
+
+    /**
+     * @return The type of the context.
+     */
+    public Type getType();
+    
+    /**
+     * @param settings the display settings to use for the created context. If
+     * the context has already been created, then <code>restart()</code> must be called
+     * for the changes to be applied.
+     */
+    public void setSettings(AppSettings settings);
+
+    /**
+     * Sets the listener that will receive events relating to context
+     * creation, update, and destroy.
+     */
+    public void setSystemListener(SystemListener listener);
+
+    /**
+     * @return The current display settings. Note that they might be 
+     * different from the ones set with setDisplaySettings() if the context
+     * was restarted or the settings changed internally.
+     */
+    public AppSettings getSettings();
+
+    /**
+     * @return The renderer for this context, or null if not created yet.
+     */
+    public Renderer getRenderer();
+
+    /**
+     * @return Mouse input implementation. May be null if not available.
+     */
+    public MouseInput getMouseInput();
+
+    /**
+     * @return Keyboard input implementation. May be null if not available.
+     */
+    public KeyInput getKeyInput();
+
+    /**
+     * @return Joystick input implementation. May be null if not available.
+     */
+    public JoyInput getJoyInput();
+    
+    /**
+     * @return Touch device input implementation. May be null if not available.
+     */
+    public TouchInput getTouchInput();
+    
+    /**
+     * @return The timer for this context, or null if not created yet.
+     */
+    public Timer getTimer();
+    
+    /**
+     * Sets the title of the display (if available). This does nothing
+     * for fullscreen, headless, or canvas contexts.
+     * @param title The new title of the display.
+     */
+    public void setTitle(String title);
+
+    /**
+     * @return True if the context has been created but not yet destroyed.
+     */
+    public boolean isCreated();
+
+    /**
+     * @return True if the context contains a valid render surface,
+     * if any of the rendering methods in {@link Renderer} are called
+     * while this is <code>false</code>, then the result is undefined.
+     */
+    public boolean isRenderable();
+
+    /**
+     * @param enabled If enabled, the context will automatically flush
+     * frames to the video card (swap buffers) after an update cycle.
+     */
+    public void setAutoFlushFrames(boolean enabled);
+
+    /**
+     * Creates the context and makes it active.
+     *
+     * @param waitFor If true, will wait until context has initialized.
+     */
+    public void create(boolean waitFor);
+
+    /**
+     * Destroys and then re-creates the context. This should be called after
+     * the display settings have been changed.
+     */
+    public void restart();
+
+    /**
+     * Destroys the context completely, making it inactive.
+     *
+     * @param waitFor If true, will wait until the context is destroyed fully.
+     */
+    public void destroy(boolean waitFor);
+
+}
diff --git a/engine/src/core/com/jme3/system/JmeSystem.java b/engine/src/core/com/jme3/system/JmeSystem.java
new file mode 100644
index 0000000..1a13a42
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeSystem.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioRenderer;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class JmeSystem {
+
+    private static JmeSystemDelegate systemDelegate;
+
+    public static void setSystemDelegate(JmeSystemDelegate systemDelegate) {
+        JmeSystem.systemDelegate = systemDelegate;
+    }
+    
+    public static synchronized File getStorageFolder() {
+        checkDelegate();
+        return systemDelegate.getStorageFolder();
+    }
+
+    public static String getFullName() {
+        checkDelegate();
+        return systemDelegate.getFullName();
+    }
+
+    public static InputStream getResourceAsStream(String name) {
+        checkDelegate();
+        return systemDelegate.getResourceAsStream(name);
+    }
+
+    public static URL getResource(String name) {
+        checkDelegate();
+        return systemDelegate.getResource(name);
+    }
+
+    public static boolean trackDirectMemory() {
+        checkDelegate();
+        return systemDelegate.trackDirectMemory();
+    }
+
+    public static void setLowPermissions(boolean lowPerm) {
+        checkDelegate();
+        systemDelegate.setLowPermissions(lowPerm);
+    }
+
+    public static boolean isLowPermissions() {
+        checkDelegate();
+        return systemDelegate.isLowPermissions();
+    }
+
+    public static AssetManager newAssetManager(URL configFile) {
+        checkDelegate();
+        return systemDelegate.newAssetManager(configFile);
+    }
+
+    public static AssetManager newAssetManager() {
+        checkDelegate();
+        return systemDelegate.newAssetManager();
+    }
+
+    public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
+        checkDelegate();
+        return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry);
+    }
+
+    public static Platform getPlatform() {
+        checkDelegate();
+        return systemDelegate.getPlatform();
+    }
+
+    public static JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {
+        checkDelegate();
+        return systemDelegate.newContext(settings, contextType);
+    }
+
+    public static AudioRenderer newAudioRenderer(AppSettings settings) {
+        checkDelegate();
+        return systemDelegate.newAudioRenderer(settings);
+    }
+
+    public static void initialize(AppSettings settings) {
+        checkDelegate();
+        systemDelegate.initialize(settings);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static void checkDelegate() {
+        if (systemDelegate == null) {
+            Class<JmeSystemDelegate> systemDelegateClass;
+            try {
+                systemDelegateClass = (Class<JmeSystemDelegate>) Class.forName("com.jme3.system.JmeDesktopSystem");
+                systemDelegate = systemDelegateClass.newInstance();
+            } catch (InstantiationException ex) {
+                Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+            } catch (IllegalAccessException ex) {
+                Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+            } catch (ClassNotFoundException ex) {
+                Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+            }
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/system/JmeSystemDelegate.java b/engine/src/core/com/jme3/system/JmeSystemDelegate.java
new file mode 100644
index 0000000..60265ae
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeSystemDelegate.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioRenderer;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Kirill Vainer, normenhansen
+ */
+public abstract class JmeSystemDelegate {
+
+    protected final Logger logger = Logger.getLogger(JmeSystem.class.getName());
+    protected boolean initialized = false;
+    protected boolean lowPermissions = false;
+    protected File storageFolder = null;
+
+    public synchronized File getStorageFolder() {
+        if (lowPermissions) {
+            throw new UnsupportedOperationException("File system access restricted");
+        }
+        if (storageFolder == null) {
+            // Initialize storage folder
+            storageFolder = new File(System.getProperty("user.home"), ".jme3");
+            if (!storageFolder.exists()) {
+                storageFolder.mkdir();
+            }
+        }
+        return storageFolder;
+    }
+    
+    public String getFullName() {
+        return JmeVersion.FULL_NAME;
+    }
+
+    public InputStream getResourceAsStream(String name) {
+        return this.getClass().getResourceAsStream(name);
+    }
+
+    public URL getResource(String name) {
+        return this.getClass().getResource(name);
+    }
+
+    public boolean trackDirectMemory() {
+        return false;
+    }
+
+    public void setLowPermissions(boolean lowPerm) {
+        lowPermissions = lowPerm;
+    }
+
+    public boolean isLowPermissions() {
+        return lowPermissions;
+    }
+
+    public abstract AssetManager newAssetManager(URL configFile);
+
+    public abstract AssetManager newAssetManager();
+
+    public abstract boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry);
+
+    private boolean is64Bit(String arch) {
+        if (arch.equals("x86")) {
+            return false;
+        } else if (arch.equals("amd64")) {
+            return true;
+        } else if (arch.equals("x86_64")) {
+            return true;
+        } else if (arch.equals("ppc") || arch.equals("PowerPC")) {
+            return false;
+        } else if (arch.equals("ppc64")) {
+            return true;
+        } else if (arch.equals("i386") || arch.equals("i686")) {
+            return false;
+        } else if (arch.equals("universal")) {
+            return false;
+        } else {
+            throw new UnsupportedOperationException("Unsupported architecture: " + arch);
+        }
+    }
+
+    public Platform getPlatform() {
+        String os = System.getProperty("os.name").toLowerCase();
+        String arch = System.getProperty("os.arch").toLowerCase();
+        boolean is64 = is64Bit(arch);
+        if (os.contains("windows")) {
+            return is64 ? Platform.Windows64 : Platform.Windows32;
+        } else if (os.contains("linux") || os.contains("freebsd") || os.contains("sunos")) {
+            return is64 ? Platform.Linux64 : Platform.Linux32;
+        } else if (os.contains("mac os x") || os.contains("darwin")) {
+            if (arch.startsWith("ppc")) {
+                return is64 ? Platform.MacOSX_PPC64 : Platform.MacOSX_PPC32;
+            } else {
+                return is64 ? Platform.MacOSX64 : Platform.MacOSX32;
+            }
+        } else {
+            throw new UnsupportedOperationException("The specified platform: " + os + " is not supported.");
+        }
+    }
+
+    public abstract JmeContext newContext(AppSettings settings, JmeContext.Type contextType);
+
+    public abstract AudioRenderer newAudioRenderer(AppSettings settings);
+
+    public abstract void initialize(AppSettings settings);
+}
diff --git a/engine/src/core/com/jme3/system/JmeVersion.java b/engine/src/core/com/jme3/system/JmeVersion.java
new file mode 100644
index 0000000..95d021f
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeVersion.java
@@ -0,0 +1,5 @@
+package com.jme3.system;
+
+public class JmeVersion {
+    public static final String FULL_NAME = "jMonkeyEngine 3.0.0 Beta";
+}
diff --git a/engine/src/core/com/jme3/system/NanoTimer.java b/engine/src/core/com/jme3/system/NanoTimer.java
new file mode 100644
index 0000000..13a150a
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NanoTimer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+/**
+ * <code>NanoTimer</code> is a System.nanoTime implementation of <code>Timer</code>.
+ * This is primarily useful for headless applications running on a server.
+ * 
+ * @author Matthew D. Hicks
+ */
+public class NanoTimer extends Timer {
+    
+    private static final long TIMER_RESOLUTION = 1000000000L;
+    private static final float INVERSE_TIMER_RESOLUTION = 1f/1000000000L;
+    
+    private long startTime;
+    private long previousTime;
+    private float tpf;
+    private float fps;
+    
+    public NanoTimer() {
+        startTime = System.nanoTime();
+    }
+
+    /**
+     * Returns the time in seconds. The timer starts
+     * at 0.0 seconds.
+     *
+     * @return the current time in seconds
+     */
+    @Override
+    public float getTimeInSeconds() {
+        return getTime() * INVERSE_TIMER_RESOLUTION;
+    }
+
+    public long getTime() {
+        return System.nanoTime() - startTime;
+    }
+
+    public long getResolution() {
+        return TIMER_RESOLUTION;
+    }
+
+    public float getFrameRate() {
+        return fps;
+    }
+
+    public float getTimePerFrame() {
+        return tpf;
+    }
+
+    public void update() {
+        tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION);
+        fps = 1.0f / tpf;
+        previousTime = getTime();
+    }
+    
+    public void reset() {
+        startTime = System.nanoTime();
+        previousTime = getTime();
+    }
+}
diff --git a/engine/src/core/com/jme3/system/NullContext.java b/engine/src/core/com/jme3/system/NullContext.java
new file mode 100644
index 0000000..75fdf2b
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NullContext.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.dummy.DummyKeyInput;
+import com.jme3.input.dummy.DummyMouseInput;
+import com.jme3.renderer.Renderer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class NullContext implements JmeContext, Runnable {
+
+    protected static final Logger logger = Logger.getLogger(NullContext.class.getName());
+
+    protected AtomicBoolean created = new AtomicBoolean(false);
+    protected AtomicBoolean needClose = new AtomicBoolean(false);
+    protected final Object createdLock = new Object();
+
+    protected int frameRate;
+    protected AppSettings settings = new AppSettings(true);
+    protected Timer timer;
+    protected SystemListener listener;
+    protected NullRenderer renderer;
+
+    public Type getType() {
+        return Type.Headless;
+    }
+
+    public void setSystemListener(SystemListener listener){
+        this.listener = listener;
+    }
+
+    protected void initInThread(){
+        logger.info("NullContext created.");
+        logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
+
+        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            public void uncaughtException(Thread thread, Throwable thrown) {
+                listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);
+            }
+        });
+
+        timer = new NanoTimer();
+        renderer = new NullRenderer();
+        synchronized (createdLock){
+            created.set(true);
+            createdLock.notifyAll();
+        }
+
+        listener.initialize();
+    }
+
+    protected void deinitInThread(){
+        listener.destroy();
+        timer = null;
+        synchronized (createdLock){
+            created.set(false);
+            createdLock.notifyAll();
+        }
+    }
+
+    private static long timeThen;
+    private static long timeLate;
+
+    public void sync(int fps) {
+        long timeNow;
+        long gapTo;
+        long savedTimeLate;
+
+        gapTo = timer.getResolution() / fps + timeThen;
+        timeNow = timer.getTime();
+        savedTimeLate = timeLate;
+
+        try {
+            while (gapTo > timeNow + savedTimeLate) {
+                Thread.sleep(1);
+                timeNow = timer.getTime();
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+
+        if (gapTo < timeNow) {
+            timeLate = timeNow - gapTo;
+        } else {
+            timeLate = 0;
+        }
+
+        timeThen = timeNow;
+    }
+
+    public void run(){
+        initInThread();
+
+        while (!needClose.get()){
+            listener.update();
+
+            if (frameRate > 0)
+                sync(frameRate);
+        }
+
+        deinitInThread();
+        
+        logger.info("NullContext destroyed.");
+    }
+
+    public void destroy(boolean waitFor){
+        needClose.set(true);
+        if (waitFor)
+            waitFor(false);
+    }
+
+    public void create(boolean waitFor){
+        if (created.get()){
+            logger.warning("create() called when NullContext is already created!");
+            return;
+        }
+
+        new Thread(this, "Headless Application Thread").start();
+        if (waitFor)
+            waitFor(true);
+    }
+
+    public void restart() {
+    }
+
+    public void setAutoFlushFrames(boolean enabled){
+    }
+
+    public MouseInput getMouseInput() {
+        return new DummyMouseInput();
+    }
+
+    public KeyInput getKeyInput() {
+        return new DummyKeyInput();
+    }
+
+    public JoyInput getJoyInput() {
+        return null;
+    }
+    
+    public TouchInput getTouchInput() {
+        return null;
+    }
+
+    public void setTitle(String title) {
+    }
+
+    public void create(){
+        create(false);
+    }
+
+    public void destroy(){
+        destroy(false);
+    }
+
+    protected void waitFor(boolean createdVal){
+        synchronized (createdLock){
+            while (created.get() != createdVal){
+                try {
+                    createdLock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+    }
+
+    public boolean isCreated(){
+        return created.get();
+    }
+
+    public void setSettings(AppSettings settings) {
+        this.settings.copyFrom(settings);
+        frameRate = settings.getFrameRate();
+        if (frameRate <= 0)
+            frameRate = 60; // use default update rate.
+    }
+
+    public AppSettings getSettings(){
+        return settings;
+    }
+
+    public Renderer getRenderer() {
+        return renderer;
+    }
+
+    public Timer getTimer() {
+        return timer;
+    }
+
+    public boolean isRenderable() {
+        return true; // Doesn't really matter if true or false. Either way
+                     // RenderManager won't render anything. 
+    }
+}
diff --git a/engine/src/core/com/jme3/system/NullRenderer.java b/engine/src/core/com/jme3/system/NullRenderer.java
new file mode 100644
index 0000000..2b1d821
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NullRenderer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.light.LightList;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.Statistics;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+public class NullRenderer implements Renderer {
+
+    private static final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
+    private static final Statistics stats = new Statistics();
+
+    public EnumSet<Caps> getCaps() {
+        return caps;
+    }
+
+    public Statistics getStatistics() {
+        return stats;
+    }
+
+    public void invalidateState(){
+    }
+
+    public void clearBuffers(boolean color, boolean depth, boolean stencil) {
+    }
+
+    public void setBackgroundColor(ColorRGBA color) {
+    }
+
+    public void applyRenderState(RenderState state) {
+    }
+
+    public void setDepthRange(float start, float end) {
+    }
+
+    public void onFrame() {
+    }
+
+    public void setWorldMatrix(Matrix4f worldMatrix) {
+    }
+
+    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
+    }
+
+    public void setViewPort(int x, int y, int width, int height) {
+    }
+
+    public void setClipRect(int x, int y, int width, int height) {
+    }
+
+    public void clearClipRect() {
+    }
+
+    public void setLighting(LightList lights) {
+    }
+
+    public void setShader(Shader shader) {
+    }
+
+    public void deleteShader(Shader shader) {
+    }
+
+    public void deleteShaderSource(ShaderSource source) {
+    }
+
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
+    }
+
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
+    }
+    
+    public void setMainFrameBufferOverride(FrameBuffer fb) {
+    }
+    
+    public void setFrameBuffer(FrameBuffer fb) {
+    }
+
+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
+    }
+
+    public void deleteFrameBuffer(FrameBuffer fb) {
+    }
+
+    public void setTexture(int unit, Texture tex) {
+    }
+
+    public void updateBufferData(VertexBuffer vb) {
+    }
+
+    public void deleteBuffer(VertexBuffer vb) {
+    }
+
+    public void renderMesh(Mesh mesh, int lod, int count) {
+    }
+
+    public void resetGLObjects() {
+    }
+
+    public void cleanup() {
+    }
+
+    public void deleteImage(Image image) {
+    }
+
+    public void setAlphaToCoverage(boolean value) {
+    }
+
+}
diff --git a/engine/src/core/com/jme3/system/Platform.java b/engine/src/core/com/jme3/system/Platform.java
new file mode 100644
index 0000000..5a3f5c9
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Platform.java
@@ -0,0 +1,65 @@
+package com.jme3.system;
+
+public enum Platform {
+
+    /**
+     * Microsoft Windows 32 bit
+     */
+    Windows32,
+    
+    /**
+     * Microsoft Windows 64 bit
+     */
+    Windows64,
+    
+    /**
+     * Linux 32 bit
+     */
+    Linux32,
+    
+    /**
+     * Linux 64 bit
+     */
+    Linux64,
+    
+    /**
+     * Apple Mac OS X 32 bit
+     */
+    MacOSX32,
+    
+    /**
+     * Apple Mac OS X 64 bit
+     */
+    MacOSX64,
+    
+    /**
+     * Apple Mac OS X 32 bit PowerPC
+     */
+    MacOSX_PPC32,
+    
+    /**
+     * Apple Mac OS X 64 bit PowerPC
+     */
+    MacOSX_PPC64,
+    
+    /**
+     * Android ARM5
+     */
+    Android_ARM5,
+    
+    /**
+     * Android ARM6
+     */
+    Android_ARM6,
+
+    /**
+     * Android ARM7
+     */
+    Android_ARM7,
+
+    /**
+     * Android x86
+     */
+    Android_X86;
+    
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/system/SystemListener.java b/engine/src/core/com/jme3/system/SystemListener.java
new file mode 100644
index 0000000..e1ccf57
--- /dev/null
+++ b/engine/src/core/com/jme3/system/SystemListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+/**
+ * The <code>ContextListener> provides a means for an application
+ * to receive events relating to a context.
+ */
+public interface SystemListener {
+
+    /**
+     * Callback to indicate the application to initialize. This method
+     * is called in the GL/Rendering thread so any GL-dependent resources
+     * can be initialized.
+     */
+    public void initialize();
+
+    /**
+     * Called to notify the application that the resolution has changed.
+     * @param width
+     * @param height
+     */
+    public void reshape(int width, int height);
+
+    /**
+     * Callback to update the application state, and render the scene
+     * to the back buffer.
+     */
+    public void update();
+
+    /**
+     * Called when the user requests to close the application. This
+     * could happen when he clicks the X button on the window, presses
+     * the Alt-F4 combination, attempts to shutdown the process from 
+     * the task manager, or presses ESC. 
+     * @param esc If true, the user pressed ESC to close the application.
+     */
+    public void requestClose(boolean esc);
+
+    /**
+     * Called when the application gained focus. The display
+     * implementation is not allowed to call this method before
+     * initialize() has been called or after destroy() has been called.
+     */
+    public void gainFocus();
+
+    /**
+     * Called when the application lost focus. The display
+     * implementation is not allowed to call this method before
+     * initialize() has been called or after destroy() has been called.
+     */
+    public void loseFocus();
+
+    /**
+     * Called when an error has occured. This is typically
+     * invoked when an uncought exception is thrown in the render thread.
+     * @param errorMsg The error message, if any, or null.
+     * @param t Throwable object, or null.
+     */
+    public void handleError(String errorMsg, Throwable t);
+
+    /**
+     * Callback to indicate that the context has been destroyed (either
+     * by the user or requested by the application itself). Typically
+     * cleanup of native resources should happen here. This method is called
+     * in the GL/Rendering thread.
+     */
+    public void destroy();
+}
diff --git a/engine/src/core/com/jme3/system/Timer.java b/engine/src/core/com/jme3/system/Timer.java
new file mode 100644
index 0000000..3ce1ee8
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Timer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+/**
+ * <code>Timer</code> is the base class for a high resolution timer. It is
+ * created from getTimer("display system")
+ *
+ * @author Mark Powell
+ * @version $Id: Timer.java,v 1.18 2007/03/09 10:19:34 rherlitz Exp $
+ */
+public abstract class Timer {
+
+    /**
+     * Returns the current time in ticks. A tick is an arbitrary measure of time
+     * defined by the timer implementation. The number of ticks per second is
+     * given by <code>getResolution()</code>. The timer starts at 0 ticks.
+     *
+     * @return a long value representing the current time
+     */
+    public abstract long getTime();
+
+    /**
+     * Returns the time in seconds. The timer starts
+     * at 0.0 seconds.
+     *
+     * @return the current time in seconds
+     */
+    public float getTimeInSeconds() {
+        return getTime() / (float) getResolution();
+    }
+
+    /**
+     * Returns the resolution of the timer.
+     *
+     * @return the number of timer ticks per second
+     */
+    public abstract long getResolution();
+
+    /**
+     * Returns the "calls per second". If this is called every frame, then it
+     * will return the "frames per second".
+     *
+     * @return The "calls per second".
+     */
+    public abstract float getFrameRate();
+
+    /**
+     * Returns the time, in seconds, between the last call and the current one.
+     *
+     * @return Time between this call and the last one.
+     */
+    public abstract float getTimePerFrame();
+
+    /**
+     * <code>update</code> recalculates the frame rate based on the previous
+     * call to update. It is assumed that update is called each frame.
+     */
+    public abstract void update();
+
+    /**
+     * Reset the timer to 0. Clear any tpf history.
+     */
+    public abstract void reset();
+}
diff --git a/engine/src/core/com/jme3/texture/FrameBuffer.java b/engine/src/core/com/jme3/texture/FrameBuffer.java
new file mode 100644
index 0000000..b52927f
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/FrameBuffer.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.NativeObject;
+import java.util.ArrayList;
+
+/**
+ * <p>
+ * <code>FrameBuffer</code>s are rendering surfaces allowing
+ * off-screen rendering and render-to-texture functionality.
+ * Instead of the scene rendering to the screen, it is rendered into the 
+ * FrameBuffer, the result can be either a texture or a buffer.
+ * <p>
+ * A <code>FrameBuffer</code> supports two methods of rendering, 
+ * using a {@link Texture} or using a buffer. 
+ * When using a texture, the result of the rendering will be rendered
+ * onto the texture, after which the texture can be placed on an object
+ * and rendered as if the texture was uploaded from disk.
+ * When using a buffer, the result is rendered onto 
+ * a buffer located on the GPU, the data of this buffer is not accessible
+ * to the user. buffers are useful if one
+ * wishes to retrieve only the color content of the scene, but still desires
+ * depth testing (which requires a depth buffer). 
+ * Buffers can be copied to other framebuffers
+ * including the main screen, by using 
+ * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer) }.
+ * The content of a {@link RenderBuffer} can be retrieved by using 
+ * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }.
+ * <p>
+ * <code>FrameBuffer</code>s have several attachment points, there are 
+ * several <em>color</em> attachment points and a single <em>depth</em> 
+ * attachment point.
+ * The color attachment points support image formats such as
+ * {@link Format#RGBA8}, allowing rendering the color content of the scene.
+ * The depth attachment point requires a depth image format. 
+ * 
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 
+ * 
+ * @author Kirill Vainer
+ */
+public class FrameBuffer extends NativeObject {
+
+    private int width = 0;
+    private int height = 0;
+    private int samples = 1;
+    private ArrayList<RenderBuffer> colorBufs = new ArrayList<RenderBuffer>();
+    private RenderBuffer depthBuf = null;
+    private int colorBufIndex = 0;
+
+    /**
+     * <code>RenderBuffer</code> represents either a texture or a 
+     * buffer that will be rendered to. <code>RenderBuffer</code>s
+     * are attached to an attachment slot on a <code>FrameBuffer</code>.
+     */
+    public class RenderBuffer {
+
+        Texture tex;
+        Image.Format format;
+        int id = -1;
+        int slot = -1;
+
+        /**
+         * @return The image format of the render buffer.
+         */
+        public Format getFormat() {
+            return format;
+        }
+
+        /**
+         * @return The texture to render to for this <code>RenderBuffer</code>
+         * or null if content should be rendered into a buffer.
+         */
+        public Texture getTexture(){
+            return tex;
+        }
+
+        /**
+         * Do not use.
+         */
+        public int getId() {
+            return id;
+        }
+
+        /**
+         * Do not use.
+         */
+        public void setId(int id){
+            this.id = id;
+        }
+
+        /**
+         * Do not use.
+         */
+        public int getSlot() {
+            return slot;
+        }
+
+        public void resetObject(){
+            id = -1;
+        }
+
+        public RenderBuffer createDestructableClone(){
+            if (tex != null){
+                return null;
+            }else{
+                RenderBuffer destructClone =  new RenderBuffer();
+                destructClone.id = id;
+                return destructClone;
+            }
+        }
+
+        @Override
+        public String toString(){
+            if (tex != null){
+                return "TextureTarget[format=" + format + "]";
+            }else{
+                return "BufferTarget[format=" + format + "]";
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Creates a new FrameBuffer with the given width, height, and number
+     * of samples. If any textures are attached to this FrameBuffer, then
+     * they must have the same number of samples as given in this constructor.
+     * <p>
+     * Note that if the {@link Renderer} does not expose the 
+     * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown
+     * if the width and height arguments are not power of two.
+     * 
+     * @param width The width to use
+     * @param height The height to use
+     * @param samples The number of samples to use for a multisampled
+     * framebuffer, or 1 if the framebuffer should be singlesampled.
+     * 
+     * @throws IllegalArgumentException If width or height are not positive.
+     */
+    public FrameBuffer(int width, int height, int samples){
+        super(FrameBuffer.class);
+        if (width <= 0 || height <= 0)
+                throw new IllegalArgumentException("FrameBuffer must have valid size.");
+
+        this.width = width;
+        this.height = height;
+        this.samples = samples == 0 ? 1 : samples;
+    }
+
+    protected FrameBuffer(FrameBuffer src){
+        super(FrameBuffer.class, src.id);
+        /*
+        for (RenderBuffer renderBuf : src.colorBufs){
+            RenderBuffer clone = renderBuf.createDestructableClone();
+            if (clone != null)
+                this.colorBufs.add(clone);
+        }
+
+        this.depthBuf = src.depthBuf.createDestructableClone();
+         */
+    }
+
+    /**
+     * Enables the use of a depth buffer for this <code>FrameBuffer</code>.
+     * 
+     * @param format The format to use for the depth buffer.
+     * @throws IllegalArgumentException If <code>format</code> is not a depth format.
+     */
+    public void setDepthBuffer(Image.Format format){
+        if (id != -1)
+            throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+        if (!format.isDepthFormat())
+            throw new IllegalArgumentException("Depth buffer format must be depth.");
+            
+        depthBuf = new RenderBuffer();
+        depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER
+        depthBuf.format = format;
+    }
+
+    /**
+     * Enables the use of a color buffer for this <code>FrameBuffer</code>.
+     * 
+     * @param format The format to use for the color buffer.
+     * @throws IllegalArgumentException If <code>format</code> is not a color format.
+     */
+    public void setColorBuffer(Image.Format format){
+        if (id != -1)
+            throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+        if (format.isDepthFormat())
+            throw new IllegalArgumentException("Color buffer format must be color/luminance.");
+        
+        RenderBuffer colorBuf = new RenderBuffer();
+        colorBuf.slot = 0;
+        colorBuf.format = format;
+        
+        colorBufs.clear();
+        colorBufs.add(colorBuf);
+    }
+
+    private void checkSetTexture(Texture tex, boolean depth){
+        Image img = tex.getImage();
+        if (img == null)
+            throw new IllegalArgumentException("Texture not initialized with RTT.");
+
+        if (depth && !img.getFormat().isDepthFormat())
+            throw new IllegalArgumentException("Texture image format must be depth.");
+        else if (!depth && img.getFormat().isDepthFormat())
+            throw new IllegalArgumentException("Texture image format must be color/luminance.");
+
+        // check that resolution matches texture resolution
+        if (width != img.getWidth() || height != img.getHeight())
+            throw new IllegalArgumentException("Texture image resolution " +
+                                               "must match FB resolution");
+
+        if (samples != tex.getImage().getMultiSamples())
+            throw new IllegalStateException("Texture samples must match framebuffer samples");
+    }
+
+    /**
+     * If enabled, any shaders rendering into this <code>FrameBuffer</code>
+     * will be able to write several results into the renderbuffers
+     * by using the <code>gl_FragData</code> array. Every slot in that
+     * array maps into a color buffer attached to this framebuffer.
+     * 
+     * @param enabled True to enable MRT (multiple rendering targets).
+     */
+    public void setMultiTarget(boolean enabled){
+        if (enabled) colorBufIndex = -1;
+        else colorBufIndex = 0;
+    }
+
+    /**
+     * @return True if MRT (multiple rendering targets) is enabled.
+     * @see FrameBuffer#setMultiTarget(boolean)
+     */
+    public boolean isMultiTarget(){
+        return colorBufIndex == -1;
+    }
+    
+    /**
+     * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false)
+     * then this specifies the color target to which the scene should be rendered.
+     * <p>
+     * By default the value is 0.
+     * 
+     * @param index The color attachment index.
+     * @throws IllegalArgumentException If index is negative or doesn't map
+     * to any attachment on this framebuffer.
+     */
+    public void setTargetIndex(int index){
+        if (index < 0 || index >= 16)
+            throw new IllegalArgumentException("Target index must be between 0 and 16");
+
+        if (colorBufs.size() < index)
+            throw new IllegalArgumentException("The target at " + index + " is not set!");
+
+        colorBufIndex = index;
+        setUpdateNeeded();
+    }
+
+    /**
+     * @return The color target to which the scene should be rendered.
+     * 
+     * @see FrameBuffer#setTargetIndex(int) 
+     */
+    public int getTargetIndex(){
+        return colorBufIndex;
+    }
+
+    /**
+     * Set the color texture to use for this framebuffer.
+     * This automatically clears all existing textures added previously
+     * with {@link FrameBuffer#addColorTexture(com.jme3.texture.Texture2D) }
+     * and adds this texture as the only target.
+     * 
+     * @param tex The color texture to set.
+     */
+    public void setColorTexture(Texture2D tex){
+        clearColorTargets();
+        addColorTexture(tex);
+    }
+
+    /**
+     * Clears all color targets that were set or added previously.
+     */
+    public void clearColorTargets(){
+        colorBufs.clear();
+    }
+
+    /**
+     * Add a color texture to use for this framebuffer.
+     * If MRT is enabled, then each subsequently added texture can be
+     * rendered to through a shader that writes to the array <code>gl_FragData</code>.
+     * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) }
+     * is rendered to by the shader.
+     * 
+     * @param tex The texture to add.
+     */
+    public void addColorTexture(Texture2D tex) {
+        if (id != -1)
+            throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+        Image img = tex.getImage();
+        checkSetTexture(tex, false);
+
+        RenderBuffer colorBuf = new RenderBuffer();
+        colorBuf.slot = colorBufs.size();
+        colorBuf.tex = tex;
+        colorBuf.format = img.getFormat();
+
+        colorBufs.add(colorBuf);
+    }
+
+    /**
+     * Set the depth texture to use for this framebuffer.
+     * 
+     * @param tex The color texture to set.
+     */
+    public void setDepthTexture(Texture2D tex){
+        if (id != -1)
+            throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+        Image img = tex.getImage();
+        checkSetTexture(tex, true);
+        
+        depthBuf = new RenderBuffer();
+        depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT
+        depthBuf.tex = tex;
+        depthBuf.format = img.getFormat();
+    }
+
+    /**
+     * @return The number of color buffers attached to this texture. 
+     */
+    public int getNumColorBuffers(){
+        return colorBufs.size();
+    }
+
+    /**
+     * @param index
+     * @return The color buffer at the given index.
+     */
+    public RenderBuffer getColorBuffer(int index){
+        return colorBufs.get(index);
+    }
+
+    /**
+     * @return The first color buffer attached to this FrameBuffer, or null
+     * if no color buffers are attached.
+     */
+    public RenderBuffer getColorBuffer() {
+        if (colorBufs.isEmpty())
+            return null;
+        
+        return colorBufs.get(0);
+    }
+
+    /**
+     * @return The depth buffer attached to this FrameBuffer, or null
+     * if no depth buffer is attached
+     */
+    public RenderBuffer getDepthBuffer() {
+        return depthBuf;
+    }
+
+    /**
+     * @return The height in pixels of this framebuffer.
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    /**
+     * @return The width in pixels of this framebuffer.
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * @return The number of samples when using a multisample framebuffer, or
+     * 1 if this is a singlesampled framebuffer.
+     */
+    public int getSamples() {
+        return samples;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt";
+        sb.append("FrameBuffer[format=").append(width).append("x").append(height)
+          .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n");
+        if (depthBuf != null)
+            sb.append("Depth => ").append(depthBuf).append("\n");
+        for (RenderBuffer colorBuf : colorBufs){
+            sb.append("Color(").append(colorBuf.slot)
+              .append(") => ").append(colorBuf).append("\n");
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public void resetObject() {
+        this.id = -1;
+        
+        for (int i = 0; i < colorBufs.size(); i++) {
+            colorBufs.get(i).resetObject();
+        }
+        
+        if (depthBuf != null)
+            depthBuf.resetObject();
+
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((Renderer)rendererObject).deleteFrameBuffer(this);
+    }
+
+    public NativeObject createDestructableClone(){
+        return new FrameBuffer(this);
+    }
+}
diff --git a/engine/src/core/com/jme3/texture/Image.java b/engine/src/core/com/jme3/texture/Image.java
new file mode 100644
index 0000000..5d23522
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Image.java
@@ -0,0 +1,790 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.export.*;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <code>Image</code> defines a data format for a graphical image. The image
+ * is defined by a format, a height and width, and the image data. The width and
+ * height must be greater than 0. The data is contained in a byte buffer, and
+ * should be packed before creation of the image object.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Id: Image.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public class Image extends NativeObject implements Savable /*, Cloneable*/ {
+
+    public enum Format {
+        /**
+         * 8-bit alpha
+         */
+        Alpha8(8),
+        
+        /**
+         * 16-bit alpha
+         */
+        Alpha16(16),
+
+        /**
+         * 8-bit grayscale/luminance.
+         */
+        Luminance8(8),
+        
+        /**
+         * 16-bit grayscale/luminance.
+         */
+        Luminance16(16),
+        
+        /**
+         * half-precision floating-point grayscale/luminance.
+         */
+        Luminance16F(16,true),
+        
+        /**
+         * single-precision floating-point grayscale/luminance.
+         */
+        Luminance32F(32,true),
+        
+        /**
+         * 8-bit luminance/grayscale and 8-bit alpha.
+         */
+        Luminance8Alpha8(16),
+        
+        /**
+         * 16-bit luminance/grayscale and 16-bit alpha.
+         */
+        Luminance16Alpha16(32),
+        
+        /**
+         * half-precision floating-point grayscale/luminance and alpha.
+         */
+        Luminance16FAlpha16F(32,true),
+
+        Intensity8(8),
+        Intensity16(16),
+
+        /**
+         * 8-bit blue, green, and red.
+         */
+        BGR8(24), // BGR and ABGR formats are often used on windows systems
+        
+        /**
+         * 8-bit red, green, and blue.
+         */
+        RGB8(24),
+        
+        /**
+         * 10-bit red, green, and blue.
+         */
+        RGB10(30),
+        
+        /**
+         * 16-bit red, green, and blue.
+         */
+        RGB16(48),
+
+        /**
+         * 5-bit red, 6-bit green, and 5-bit blue.
+         */
+        RGB565(16),
+        
+        /**
+         * 4-bit alpha, red, green, and blue. Used on Android only.
+         */
+        ARGB4444(16),
+        
+        /**
+         * 5-bit red, green, and blue with 1-bit alpha.
+         */
+        RGB5A1(16),
+        
+        /**
+         * 8-bit red, green, blue, and alpha.
+         */
+        RGBA8(32),
+        
+        /**
+         * 8-bit alpha, blue, green, and red.
+         */
+        ABGR8(32),
+        
+        /**
+         * 16-bit red, green, blue and alpha
+         */
+        RGBA16(64),
+
+        /**
+         * S3TC compression DXT1. 
+         * Called BC1 in DirectX10.
+         */
+        DXT1(4,false,true, false),
+        
+        /**
+         * S3TC compression DXT1 with 1-bit alpha.
+         */
+        DXT1A(4,false,true, false),
+        
+        /**
+         * S3TC compression DXT3 with 4-bit alpha.
+         * Called BC2 in DirectX10.
+         */
+        DXT3(8,false,true, false),
+        
+        /**
+         * S3TC compression DXT5 with interpolated 8-bit alpha.
+         * Called BC3 in DirectX10.
+         */
+        DXT5(8,false,true, false),
+        
+        /**
+         * Luminance-Alpha Texture Compression. 
+         * Called BC5 in DirectX10.
+         */
+        LATC(8, false, true, false),
+
+        /**
+         * Arbitrary depth format. The precision is chosen by the video
+         * hardware.
+         */
+        Depth(0,true,false,false),
+        
+        /**
+         * 16-bit depth.
+         */
+        Depth16(16,true,false,false),
+        
+        /**
+         * 24-bit depth.
+         */
+        Depth24(24,true,false,false),
+        
+        /**
+         * 32-bit depth.
+         */
+        Depth32(32,true,false,false),
+        
+        /**
+         * single-precision floating point depth.
+         */
+        Depth32F(32,true,false,true),
+
+        /**
+         * Texture data is stored as {@link Format#RGB16F} in system memory,
+         * but will be converted to {@link Format#RGB111110F} when sent
+         * to the video hardware.
+         */
+        RGB16F_to_RGB111110F(48,true),
+        
+        /**
+         * unsigned floating-point red, green and blue that uses 32 bits.
+         */
+        RGB111110F(32,true),
+        
+        /**
+         * Texture data is stored as {@link Format#RGB16F} in system memory,
+         * but will be converted to {@link Format#RGB9E5} when sent
+         * to the video hardware.
+         */
+        RGB16F_to_RGB9E5(48,true),
+        
+        /**
+         * 9-bit red, green and blue with 5-bit exponent.
+         */
+        RGB9E5(32,true),
+        
+        /**
+         * half-precision floating point red, green, and blue.
+         */
+        RGB16F(48,true),
+        
+        /**
+         * half-precision floating point red, green, blue, and alpha.
+         */
+        RGBA16F(64,true),
+        
+        /**
+         * single-precision floating point red, green, and blue.
+         */
+        RGB32F(96,true),
+        
+        /**
+         * single-precision floating point red, green, blue and alpha.
+         */
+        RGBA32F(128,true),
+
+        /**
+         * Luminance/grayscale texture compression. 
+         * Called BC4 in DirectX10.
+         */
+        LTC(4, false, true, false);
+
+        private int bpp;
+        private boolean isDepth;
+        private boolean isCompressed;
+        private boolean isFloatingPoint;
+
+        private Format(int bpp){
+            this.bpp = bpp;
+        }
+
+        private Format(int bpp, boolean isFP){
+            this(bpp);
+            this.isFloatingPoint = isFP;
+        }
+
+        private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){
+            this(bpp, isFP);
+            this.isDepth = isDepth;
+            this.isCompressed = isCompressed;
+        }
+
+        /**
+         * @return bits per pixel.
+         */
+        public int getBitsPerPixel(){
+            return bpp;
+        }
+
+        /**
+         * @return True if this format is a depth format, false otherwise.
+         */
+        public boolean isDepthFormat(){
+            return isDepth;
+        }
+
+        /**
+         * @return True if this is a compressed image format, false if
+         * uncompressed.
+         */
+        public boolean isCompressed() {
+            return isCompressed;
+        }
+
+        /**
+         * @return True if this image format is in floating point, 
+         * false if it is an integer format.
+         */
+        public boolean isFloatingPont(){
+            return isFloatingPoint;
+        }
+
+    }
+
+    // image attributes
+    protected Format format;
+    protected int width, height, depth;
+    protected int[] mipMapSizes;
+    protected ArrayList<ByteBuffer> data;
+    protected transient Object efficientData;
+    protected int multiSamples = 1;
+//    protected int mipOffset = 0;
+
+    @Override
+    public void resetObject() {
+        this.id = -1;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((Renderer)rendererObject).deleteImage(this);
+    }
+
+    @Override
+    public NativeObject createDestructableClone() {
+        return new Image(id);
+    }
+
+    /**
+     * @return A shallow clone of this image. The data is not cloned.
+     */
+    @Override
+    public Image clone(){
+        Image clone = (Image) super.clone();
+        clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null;
+        clone.data = data != null ? new ArrayList<ByteBuffer>(data) : null;
+        clone.setUpdateNeeded();
+        return clone;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Image</code> object. All values
+     * are undefined.
+     */
+    public Image() {
+        super(Image.class);
+        data = new ArrayList<ByteBuffer>(1);
+    }
+
+    protected Image(int id){
+        super(Image.class, id);
+    }
+
+    /**
+     * Constructor instantiates a new <code>Image</code> object. The
+     * attributes of the image are defined during construction.
+     *
+     * @param format
+     *            the data format of the image.
+     * @param width
+     *            the width of the image.
+     * @param height
+     *            the height of the image.
+     * @param data
+     *            the image data.
+     * @param mipMapSizes
+     *            the array of mipmap sizes, or null for no mipmaps.
+     */
+    public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data,
+            int[] mipMapSizes) {
+        
+        this();
+
+        if (mipMapSizes != null && mipMapSizes.length <= 1) {
+            mipMapSizes = null;
+        }
+
+        setFormat(format);
+        this.width = width;
+        this.height = height;
+        this.data = data;
+        this.depth = depth;
+        this.mipMapSizes = mipMapSizes;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Image</code> object. The
+     * attributes of the image are defined during construction.
+     *
+     * @param format
+     *            the data format of the image.
+     * @param width
+     *            the width of the image.
+     * @param height
+     *            the height of the image.
+     * @param data
+     *            the image data.
+     * @param mipMapSizes
+     *            the array of mipmap sizes, or null for no mipmaps.
+     */
+    public Image(Format format, int width, int height, ByteBuffer data,
+            int[] mipMapSizes) {
+
+        this();
+
+        if (mipMapSizes != null && mipMapSizes.length <= 1) {
+            mipMapSizes = null;
+        }
+
+        setFormat(format);
+        this.width = width;
+        this.height = height;
+        if (data != null){
+            this.data = new ArrayList<ByteBuffer>(1);
+            this.data.add(data);
+        }
+        this.mipMapSizes = mipMapSizes;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Image</code> object. The
+     * attributes of the image are defined during construction.
+     *
+     * @param format
+     *            the data format of the image.
+     * @param width
+     *            the width of the image.
+     * @param height
+     *            the height of the image.
+     * @param data
+     *            the image data.
+     */
+    public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data) {
+        this(format, width, height, depth, data, null);
+    }
+
+    /**
+     * Constructor instantiates a new <code>Image</code> object. The
+     * attributes of the image are defined during construction.
+     *
+     * @param format
+     *            the data format of the image.
+     * @param width
+     *            the width of the image.
+     * @param height
+     *            the height of the image.
+     * @param data
+     *            the image data.
+     */
+    public Image(Format format, int width, int height, ByteBuffer data) {
+        this(format, width, height, data, null);
+    }
+
+    /**
+     * @return The number of samples (for multisampled textures).
+     * @see Image#setMultiSamples(int)
+     */
+    public int getMultiSamples() {
+        return multiSamples;
+    }
+
+    /**
+     * @param multiSamples Set the number of samples to use for this image,
+     * setting this to a value higher than 1 turns this image/texture
+     * into a multisample texture (on OpenGL3.1 and higher).
+     */
+    public void setMultiSamples(int multiSamples) {
+        if (multiSamples <= 0)
+            throw new IllegalArgumentException("multiSamples must be > 0");
+
+        if (getData(0) != null)
+            throw new IllegalArgumentException("Cannot upload data as multisample texture");
+
+        if (hasMipmaps())
+            throw new IllegalArgumentException("Multisample textures do not support mipmaps");
+
+        this.multiSamples = multiSamples;
+    }
+
+    /**
+     * <code>setData</code> sets the data that makes up the image. This data
+     * is packed into an array of <code>ByteBuffer</code> objects.
+     *
+     * @param data
+     *            the data that contains the image information.
+     */
+    public void setData(ArrayList<ByteBuffer> data) {
+        this.data = data;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>setData</code> sets the data that makes up the image. This data
+     * is packed into a single <code>ByteBuffer</code>.
+     *
+     * @param data
+     *            the data that contains the image information.
+     */
+    public void setData(ByteBuffer data) {
+        this.data = new ArrayList<ByteBuffer>(1);
+        this.data.add(data);
+        setUpdateNeeded();
+    }
+
+    public void addData(ByteBuffer data) {
+        if (this.data == null)
+            this.data = new ArrayList<ByteBuffer>(1);
+        this.data.add(data);
+        setUpdateNeeded();
+    }
+
+    public void setData(int index, ByteBuffer data) {
+        if (index >= 0) {
+            while (this.data.size() <= index) {
+                this.data.add(null);
+            }
+            this.data.set(index, data);
+            setUpdateNeeded();
+        } else {
+            throw new IllegalArgumentException("index must be greater than or equal to 0.");
+        }
+    }
+
+    /**
+     * Set the efficient data representation of this image.
+     * <p>
+     * Some system implementations are more efficient at operating
+     * on data other than ByteBuffers, in that case, this method can be used.
+     *
+     * @param efficientData
+     */
+    public void setEfficentData(Object efficientData){
+        this.efficientData = efficientData;
+        setUpdateNeeded();
+    }
+
+    /**
+     * @return The efficient data representation of this image.
+     * @see Image#setEfficentData(java.lang.Object)
+     */
+    public Object getEfficentData(){
+        return efficientData;
+    }
+
+    /**
+     * Sets the mipmap sizes stored in this image's data buffer. Mipmaps are
+     * stored sequentially, and the first mipmap is the main image data. To
+     * specify no mipmaps, pass null and this will automatically be expanded
+     * into a single mipmap of the full
+     *
+     * @param mipMapSizes
+     *            the mipmap sizes array, or null for a single image map.
+     */
+    public void setMipMapSizes(int[] mipMapSizes) {
+        if (mipMapSizes != null && mipMapSizes.length <= 1)
+            mipMapSizes = null;
+
+        this.mipMapSizes = mipMapSizes;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>setHeight</code> sets the height value of the image. It is
+     * typically a good idea to try to keep this as a multiple of 2.
+     *
+     * @param height
+     *            the height of the image.
+     */
+    public void setHeight(int height) {
+        this.height = height;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>setDepth</code> sets the depth value of the image. It is
+     * typically a good idea to try to keep this as a multiple of 2. This is
+     * used for 3d images.
+     *
+     * @param depth
+     *            the depth of the image.
+     */
+    public void setDepth(int depth) {
+        this.depth = depth;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>setWidth</code> sets the width value of the image. It is
+     * typically a good idea to try to keep this as a multiple of 2.
+     *
+     * @param width
+     *            the width of the image.
+     */
+    public void setWidth(int width) {
+        this.width = width;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>setFormat</code> sets the image format for this image.
+     *
+     * @param format
+     *            the image format.
+     * @throws NullPointerException
+     *             if format is null
+     * @see Format
+     */
+    public void setFormat(Format format) {
+        if (format == null) {
+            throw new NullPointerException("format may not be null.");
+        }
+
+        this.format = format;
+        setUpdateNeeded();
+    }
+
+    /**
+     * <code>getFormat</code> returns the image format for this image.
+     *
+     * @return the image format.
+     * @see Format
+     */
+    public Format getFormat() {
+        return format;
+    }
+
+    /**
+     * <code>getWidth</code> returns the width of this image.
+     *
+     * @return the width of this image.
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * <code>getHeight</code> returns the height of this image.
+     *
+     * @return the height of this image.
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    /**
+     * <code>getDepth</code> returns the depth of this image (for 3d images).
+     *
+     * @return the depth of this image.
+     */
+    public int getDepth() {
+        return depth;
+    }
+
+    /**
+     * <code>getData</code> returns the data for this image. If the data is
+     * undefined, null will be returned.
+     *
+     * @return the data for this image.
+     */
+    public List<ByteBuffer> getData() {
+        return data;
+    }
+
+    /**
+     * <code>getData</code> returns the data for this image. If the data is
+     * undefined, null will be returned.
+     *
+     * @return the data for this image.
+     */
+    public ByteBuffer getData(int index) {
+        if (data.size() > index)
+            return data.get(index);
+        else
+            return null;
+    }
+
+    /**
+     * Returns whether the image data contains mipmaps.
+     *
+     * @return true if the image data contains mipmaps, false if not.
+     */
+    public boolean hasMipmaps() {
+        return mipMapSizes != null;
+    }
+
+    /**
+     * Returns the mipmap sizes for this image.
+     *
+     * @return the mipmap sizes for this image.
+     */
+    public int[] getMipMapSizes() {
+        return mipMapSizes;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append("[size=").append(width).append("x").append(height);
+
+        if (depth > 1)
+            sb.append("x").append(depth);
+
+        sb.append(", format=").append(format.name());
+
+        if (hasMipmaps())
+            sb.append(", mips");
+
+        if (getId() >= 0)
+            sb.append(", id=").append(id);
+
+        sb.append("]");
+        
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (!(other instanceof Image)) {
+            return false;
+        }
+        Image that = (Image) other;
+        if (this.getFormat() != that.getFormat())
+            return false;
+        if (this.getWidth() != that.getWidth())
+            return false;
+        if (this.getHeight() != that.getHeight())
+            return false;
+        if (this.getData() != null && !this.getData().equals(that.getData()))
+            return false;
+        if (this.getData() == null && that.getData() != null)
+            return false;
+        if (this.getMipMapSizes() != null
+                && !Arrays.equals(this.getMipMapSizes(), that.getMipMapSizes()))
+            return false;
+        if (this.getMipMapSizes() == null && that.getMipMapSizes() != null)
+            return false;
+        if (this.getMultiSamples() != that.getMultiSamples())
+            return false;
+        
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 97 * hash + (this.format != null ? this.format.hashCode() : 0);
+        hash = 97 * hash + this.width;
+        hash = 97 * hash + this.height;
+        hash = 97 * hash + this.depth;
+        hash = 97 * hash + Arrays.hashCode(this.mipMapSizes);
+        hash = 97 * hash + (this.data != null ? this.data.hashCode() : 0);
+        hash = 97 * hash + this.multiSamples;
+        return hash;
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(format, "format", Format.RGBA8);
+        capsule.write(width, "width", 0);
+        capsule.write(height, "height", 0);
+        capsule.write(depth, "depth", 0);
+        capsule.write(mipMapSizes, "mipMapSizes", null);
+        capsule.write(multiSamples, "multiSamples", 1);
+        capsule.writeByteBufferArrayList(data, "data", null);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        format = capsule.readEnum("format", Format.class, Format.RGBA8);
+        width = capsule.readInt("width", 0);
+        height = capsule.readInt("height", 0);
+        depth = capsule.readInt("depth", 0);
+        mipMapSizes = capsule.readIntArray("mipMapSizes", null);
+        multiSamples = capsule.readInt("multiSamples", 1);
+        data = (ArrayList<ByteBuffer>) capsule.readByteBufferArrayList("data", null);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/texture/Texture.java b/engine/src/core/com/jme3/texture/Texture.java
new file mode 100644
index 0000000..1efedec
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.export.*;
+import com.jme3.util.PlaceholderAssets;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Texture</code> defines a texture object to be used to display an
+ * image on a piece of geometry. The image to be displayed is defined by the
+ * <code>Image</code> class. All attributes required for texture mapping are
+ * contained within this class. This includes mipmapping if desired,
+ * magnificationFilter options, apply options and correction options. Default
+ * values are as follows: minificationFilter - NearestNeighborNoMipMaps,
+ * magnificationFilter - NearestNeighbor, wrap - EdgeClamp on S,T and R, apply -
+ * Modulate, environment - None.
+ *
+ * @see com.jme3.texture.Image
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public abstract class Texture implements Asset, Savable, Cloneable {
+
+    public enum Type {
+
+        /**
+         * Two dimensional texture (default). A rectangle.
+         */
+        TwoDimensional,
+        
+        /**
+         * An array of two dimensional textures. 
+         */
+        TwoDimensionalArray,
+
+        /**
+         * Three dimensional texture. (A cube)
+         */
+        ThreeDimensional,
+
+        /**
+         * A set of 6 TwoDimensional textures arranged as faces of a cube facing
+         * inwards.
+         */
+        CubeMap;
+    }
+
+    public enum MinFilter {
+
+        /**
+         * Nearest neighbor interpolation is the fastest and crudest filtering
+         * method - it simply uses the color of the texel closest to the pixel
+         * center for the pixel color. While fast, this results in aliasing and
+         * shimmering during minification. (GL equivalent: GL_NEAREST)
+         */
+        NearestNoMipMaps(false),
+
+        /**
+         * In this method the four nearest texels to the pixel center are
+         * sampled (at texture level 0), and their colors are combined by
+         * weighted averages. Though smoother, without mipmaps it suffers the
+         * same aliasing and shimmering problems as nearest
+         * NearestNeighborNoMipMaps. (GL equivalent: GL_LINEAR)
+         */
+        BilinearNoMipMaps(false),
+
+        /**
+         * Same as NearestNeighborNoMipMaps except that instead of using samples
+         * from texture level 0, the closest mipmap level is chosen based on
+         * distance. This reduces the aliasing and shimmering significantly, but
+         * does not help with blockiness. (GL equivalent:
+         * GL_NEAREST_MIPMAP_NEAREST)
+         */
+        NearestNearestMipMap(true),
+
+        /**
+         * Same as BilinearNoMipMaps except that instead of using samples from
+         * texture level 0, the closest mipmap level is chosen based on
+         * distance. By using mipmapping we avoid the aliasing and shimmering
+         * problems of BilinearNoMipMaps. (GL equivalent:
+         * GL_LINEAR_MIPMAP_NEAREST)
+         */
+        BilinearNearestMipMap(true),
+
+        /**
+         * Similar to NearestNeighborNoMipMaps except that instead of using
+         * samples from texture level 0, a sample is chosen from each of the
+         * closest (by distance) two mipmap levels. A weighted average of these
+         * two samples is returned. (GL equivalent: GL_NEAREST_MIPMAP_LINEAR)
+         */
+        NearestLinearMipMap(true),
+
+        /**
+         * Trilinear filtering is a remedy to a common artifact seen in
+         * mipmapped bilinearly filtered images: an abrupt and very noticeable
+         * change in quality at boundaries where the renderer switches from one
+         * mipmap level to the next. Trilinear filtering solves this by doing a
+         * texture lookup and bilinear filtering on the two closest mipmap
+         * levels (one higher and one lower quality), and then linearly
+         * interpolating the results. This results in a smooth degradation of
+         * texture quality as distance from the viewer increases, rather than a
+         * series of sudden drops. Of course, closer than Level 0 there is only
+         * one mipmap level available, and the algorithm reverts to bilinear
+         * filtering (GL equivalent: GL_LINEAR_MIPMAP_LINEAR)
+         */
+        Trilinear(true);
+
+        private boolean usesMipMapLevels;
+
+        private MinFilter(boolean usesMipMapLevels) {
+            this.usesMipMapLevels = usesMipMapLevels;
+        }
+
+        public boolean usesMipMapLevels() {
+            return usesMipMapLevels;
+        }
+    }
+
+    public enum MagFilter {
+
+        /**
+         * Nearest neighbor interpolation is the fastest and crudest filtering
+         * mode - it simply uses the color of the texel closest to the pixel
+         * center for the pixel color. While fast, this results in texture
+         * 'blockiness' during magnification. (GL equivalent: GL_NEAREST)
+         */
+        Nearest,
+
+        /**
+         * In this mode the four nearest texels to the pixel center are sampled
+         * (at the closest mipmap level), and their colors are combined by
+         * weighted average according to distance. This removes the 'blockiness'
+         * seen during magnification, as there is now a smooth gradient of color
+         * change from one texel to the next, instead of an abrupt jump as the
+         * pixel center crosses the texel boundary. (GL equivalent: GL_LINEAR)
+         */
+        Bilinear;
+
+    }
+
+    public enum WrapMode {
+        /**
+         * Only the fractional portion of the coordinate is considered.
+         */
+        Repeat,
+        /**
+         * Only the fractional portion of the coordinate is considered, but if
+         * the integer portion is odd, we'll use 1 - the fractional portion.
+         * (Introduced around OpenGL1.4) Falls back on Repeat if not supported.
+         */
+        MirroredRepeat,
+        /**
+         * coordinate will be clamped to [0,1]
+         */
+        Clamp,
+        /**
+         * mirrors and clamps the texture coordinate, where mirroring and
+         * clamping a value f computes:
+         * <code>mirrorClamp(f) = min(1, max(1/(2*N),
+         * abs(f)))</code> where N
+         * is the size of the one-, two-, or three-dimensional texture image in
+         * the direction of wrapping. (Introduced after OpenGL1.4) Falls back on
+         * Clamp if not supported.
+         */
+        MirrorClamp,
+        /**
+         * coordinate will be clamped to the range [-1/(2N), 1 + 1/(2N)] where N
+         * is the size of the texture in the direction of clamping. Falls back
+         * on Clamp if not supported.
+         */
+        BorderClamp,
+        /**
+         * Wrap mode MIRROR_CLAMP_TO_BORDER_EXT mirrors and clamps to border the
+         * texture coordinate, where mirroring and clamping to border a value f
+         * computes:
+         * <code>mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f)))</code>
+         * where N is the size of the one-, two-, or three-dimensional texture
+         * image in the direction of wrapping." (Introduced after OpenGL1.4)
+         * Falls back on BorderClamp if not supported.
+         */
+        MirrorBorderClamp,
+        /**
+         * coordinate will be clamped to the range [1/(2N), 1 - 1/(2N)] where N
+         * is the size of the texture in the direction of clamping. Falls back
+         * on Clamp if not supported.
+         */
+        EdgeClamp,
+        /**
+         * mirrors and clamps to edge the texture coordinate, where mirroring
+         * and clamping to edge a value f computes:
+         * <code>mirrorClampToEdge(f) = min(1-1/(2*N), max(1/(2*N), abs(f)))</code>
+         * where N is the size of the one-, two-, or three-dimensional texture
+         * image in the direction of wrapping. (Introduced after OpenGL1.4)
+         * Falls back on EdgeClamp if not supported.
+         */
+        MirrorEdgeClamp;
+    }
+
+    public enum WrapAxis {
+        /**
+         * S wrapping (u or "horizontal" wrap)
+         */
+        S,
+        /**
+         * T wrapping (v or "vertical" wrap)
+         */
+        T,
+        /**
+         * R wrapping (w or "depth" wrap)
+         */
+        R;
+    }
+
+    /**
+     * If this texture is a depth texture (the format is Depth*) then
+     * this value may be used to compare the texture depth to the R texture
+     * coordinate. 
+     */
+    public enum ShadowCompareMode {
+        /**
+         * Shadow comparison mode is disabled.
+         * Texturing is done normally.
+         */
+        Off,
+
+        /**
+         * Compares the 3rd texture coordinate R to the value
+         * in this depth texture. If R <= texture value then result is 1.0,
+         * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
+         * the implementation may sample the texture multiple times to provide
+         * smoother results in the range [0, 1].
+         */
+        LessOrEqual,
+
+        /**
+         * Compares the 3rd texture coordinate R to the value
+         * in this depth texture. If R >= texture value then result is 1.0,
+         * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
+         * the implementation may sample the texture multiple times to provide
+         * smoother results in the range [0, 1].
+         */
+        GreaterOrEqual
+    }
+
+    /**
+     * The name of the texture (if loaded as a resource).
+     */
+    private String name = null;
+
+    /**
+     * The image stored in the texture
+     */
+    private Image image = null;
+
+    /**
+     * The texture key allows to reload a texture from a file
+     * if needed.
+     */
+    private TextureKey key = null;
+
+    private MinFilter minificationFilter = MinFilter.BilinearNoMipMaps;
+    private MagFilter magnificationFilter = MagFilter.Bilinear;
+    private ShadowCompareMode shadowCompareMode = ShadowCompareMode.Off;
+    private int anisotropicFilter;
+
+    /**
+     * @return
+     */
+    @Override
+    public Texture clone(){
+        try {
+            return (Texture) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Constructor instantiates a new <code>Texture</code> object with default
+     * attributes.
+     */
+    public Texture() {
+    }
+
+    /**
+     * @return the MinificationFilterMode of this texture.
+     */
+    public MinFilter getMinFilter() {
+        return minificationFilter;
+    }
+
+    /**
+     * @param minificationFilter
+     *            the new MinificationFilterMode for this texture.
+     * @throws IllegalArgumentException
+     *             if minificationFilter is null
+     */
+    public void setMinFilter(MinFilter minificationFilter) {
+        if (minificationFilter == null) {
+            throw new IllegalArgumentException(
+                    "minificationFilter can not be null.");
+        }
+        this.minificationFilter = minificationFilter;
+    }
+
+    /**
+     * @return the MagnificationFilterMode of this texture.
+     */
+    public MagFilter getMagFilter() {
+        return magnificationFilter;
+    }
+
+    /**
+     * @param magnificationFilter
+     *            the new MagnificationFilter for this texture.
+     * @throws IllegalArgumentException
+     *             if magnificationFilter is null
+     */
+    public void setMagFilter(MagFilter magnificationFilter) {
+        if (magnificationFilter == null) {
+            throw new IllegalArgumentException(
+                    "magnificationFilter can not be null.");
+        }
+        this.magnificationFilter = magnificationFilter;
+    }
+
+    /**
+     * @return The ShadowCompareMode of this texture.
+     * @see ShadowCompareMode
+     */
+    public ShadowCompareMode getShadowCompareMode(){
+        return shadowCompareMode;
+    }
+
+    /**
+     * @param compareMode
+     *            the new ShadowCompareMode for this texture.
+     * @throws IllegalArgumentException
+     *             if compareMode is null
+     * @see ShadowCompareMode
+     */
+    public void setShadowCompareMode(ShadowCompareMode compareMode){
+        if (compareMode == null){
+            throw new IllegalArgumentException(
+                    "compareMode can not be null.");
+        }
+        this.shadowCompareMode = compareMode;
+    }
+
+    /**
+     * <code>setImage</code> sets the image object that defines the texture.
+     *
+     * @param image
+     *            the image that defines the texture.
+     */
+    public void setImage(Image image) {
+        this.image = image;
+    }
+
+    /**
+     * @param key The texture key that was used to load this texture
+     */
+    public void setKey(AssetKey key){
+        this.key = (TextureKey) key;
+    }
+
+    public AssetKey getKey(){
+        return this.key;
+    }
+
+    /**
+     * <code>getImage</code> returns the image data that makes up this
+     * texture. If no image data has been set, this will return null.
+     *
+     * @return the image data that makes up the texture.
+     */
+    public Image getImage() {
+        return image;
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for a
+     * particular axis.
+     *
+     * @param axis
+     *            the texture axis to define a wrapmode on.
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if axis or mode are null or invalid for this type of texture
+     */
+    public abstract void setWrap(WrapAxis axis, WrapMode mode);
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+     *
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if mode is null or invalid for this type of texture
+     */
+    public abstract void setWrap(WrapMode mode);
+
+    /**
+     * <code>getWrap</code> returns the wrap mode for a given coordinate axis
+     * on this texture.
+     *
+     * @param axis
+     *            the axis to return for
+     * @return the wrap mode of the texture.
+     * @throws IllegalArgumentException
+     *             if axis is null or invalid for this type of texture
+     */
+    public abstract WrapMode getWrap(WrapAxis axis);
+
+    public abstract Type getType();
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return the anisotropic filtering level for this texture. Default value
+     * is 1 (no anisotrophy), 2 means x2, 4 is x4, etc.
+     */
+    public int getAnisotropicFilter() {
+        return anisotropicFilter;
+    }
+
+    /**
+     * @param level
+     *            the anisotropic filtering level for this texture.
+     */
+    public void setAnisotropicFilter(int level) {
+        if (level < 1)
+            anisotropicFilter = 1;
+        else
+            anisotropicFilter = level;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append("[name=").append(name);
+        if (image != null)
+            sb.append(", image=").append(image.toString());
+
+        sb.append("]");
+
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Texture other = (Texture) obj;
+        if (this.image != other.image && (this.image == null || !this.image.equals(other.image))) {
+            return false;
+        }
+        if (this.minificationFilter != other.minificationFilter) {
+            return false;
+        }
+        if (this.magnificationFilter != other.magnificationFilter) {
+            return false;
+        }
+        if (this.shadowCompareMode != other.shadowCompareMode) {
+            return false;
+        }
+        if (this.anisotropicFilter != other.anisotropicFilter) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 67 * hash + (this.image != null ? this.image.hashCode() : 0);
+        hash = 67 * hash + (this.minificationFilter != null ? this.minificationFilter.hashCode() : 0);
+        hash = 67 * hash + (this.magnificationFilter != null ? this.magnificationFilter.hashCode() : 0);
+        hash = 67 * hash + (this.shadowCompareMode != null ? this.shadowCompareMode.hashCode() : 0);
+        hash = 67 * hash + this.anisotropicFilter;
+        return hash;
+    }
+
+    
+
+//    public abstract Texture createSimpleClone();
+
+
+   /** Retrieve a basic clone of this Texture (ie, clone everything but the
+     * image data, which is shared)
+     *
+     * @return Texture
+     */
+    public Texture createSimpleClone(Texture rVal) {
+        rVal.setMinFilter(minificationFilter);
+        rVal.setMagFilter(magnificationFilter);
+        rVal.setShadowCompareMode(shadowCompareMode);
+//        rVal.setHasBorder(hasBorder);
+        rVal.setAnisotropicFilter(anisotropicFilter);
+        rVal.setImage(image); // NOT CLONED.
+//        rVal.memReq = memReq;
+        rVal.setKey(key);
+        rVal.setName(name);
+//        rVal.setBlendColor(blendColor != null ? blendColor.clone() : null);
+//        if (getTextureKey() != null) {
+//            rVal.setTextureKey(getTextureKey());
+//        }
+        return rVal;
+    }
+
+    public abstract Texture createSimpleClone();
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(name, "name", null);
+        
+        if (key == null){
+            // no texture key is set, try to save image instead then
+            capsule.write(image, "image", null);
+        }else{
+            capsule.write(key, "key", null);
+        }
+        
+        capsule.write(anisotropicFilter, "anisotropicFilter", 1);
+        capsule.write(minificationFilter, "minificationFilter",
+                MinFilter.BilinearNoMipMaps);
+        capsule.write(magnificationFilter, "magnificationFilter",
+                MagFilter.Bilinear);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        name = capsule.readString("name", null);
+        key = (TextureKey) capsule.readSavable("key", null);
+        
+        // load texture from key, if available
+        if (key != null) {
+            // key is available, so try the texture from there.
+            try {
+                Texture loadedTex = e.getAssetManager().loadTexture(key);
+                image = loadedTex.getImage();
+            } catch (AssetNotFoundException ex){
+                Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot locate texture {0}", key);
+                image = PlaceholderAssets.getPlaceholderImage();
+            }
+        }else{
+            // no key is set on the texture. Attempt to load an embedded image
+            image = (Image) capsule.readSavable("image", null);
+            if (image == null){
+                // TODO: what to print out here? the texture has no key or data, there's no useful information .. 
+                // assume texture.name is set even though the key is null
+                Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot load embedded image {0}", toString() );
+            }
+        }
+
+        anisotropicFilter = capsule.readInt("anisotropicFilter", 1);
+        minificationFilter = capsule.readEnum("minificationFilter",
+                MinFilter.class,
+                MinFilter.BilinearNoMipMaps);
+        magnificationFilter = capsule.readEnum("magnificationFilter",
+                MagFilter.class, MagFilter.Bilinear);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/Texture2D.java b/engine/src/core/com/jme3/texture/Texture2D.java
new file mode 100644
index 0000000..9598413
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture2D.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * @author Joshua Slack
+ */
+public class Texture2D extends Texture {
+
+    private WrapMode wrapS = WrapMode.EdgeClamp;
+    private WrapMode wrapT = WrapMode.EdgeClamp;
+
+    /**
+     * Creates a new two-dimensional texture with default attributes.
+     */
+    public Texture2D(){
+        super();
+    }
+
+    /**
+     * Creates a new two-dimensional texture using the given image.
+     * @param img The image to use.
+     */
+    public Texture2D(Image img){
+        super();
+        setImage(img);
+        if (img.getFormat().isDepthFormat()){
+            setMagFilter(MagFilter.Nearest);
+            setMinFilter(MinFilter.NearestNoMipMaps);
+        }
+    }
+
+    /**
+     * Creates a new two-dimensional texture for the purpose of offscreen
+     * rendering.
+     *
+     * @see com.jme3.texture.FrameBuffer
+     *
+     * @param width
+     * @param height
+     * @param format
+     */
+    public Texture2D(int width, int height, Image.Format format){
+        this(new Image(format, width, height, null));
+    }
+
+    /**
+     * Creates a new two-dimensional texture for the purpose of offscreen
+     * rendering.
+     *
+     * @see com.jme3.texture.FrameBuffer
+     *
+     * @param width
+     * @param height
+     * @param format
+     * @param numSamples
+     */
+    public Texture2D(int width, int height, int numSamples, Image.Format format){
+        this(new Image(format, width, height, null));
+        getImage().setMultiSamples(numSamples);
+    }
+
+    @Override
+    public Texture createSimpleClone() {
+        Texture2D clone = new Texture2D();
+        createSimpleClone(clone);
+        return clone;
+    }
+
+    @Override
+    public Texture createSimpleClone(Texture rVal) {
+        rVal.setWrap(WrapAxis.S, wrapS);
+        rVal.setWrap(WrapAxis.T, wrapT);
+        return super.createSimpleClone(rVal);
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for a
+     * particular axis.
+     *
+     * @param axis
+     *            the texture axis to define a wrapmode on.
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if axis or mode are null
+     */
+    public void setWrap(WrapAxis axis, WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        } else if (axis == null) {
+            throw new IllegalArgumentException("axis can not be null.");
+        }
+        switch (axis) {
+            case S:
+                this.wrapS = mode;
+                break;
+            case T:
+                this.wrapT = mode;
+                break;
+            default:
+                throw new IllegalArgumentException("Not applicable for 2D textures");
+        }
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+     *
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if mode is null
+     */
+    public void setWrap(WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        }
+        this.wrapS = mode;
+        this.wrapT = mode;
+    }
+
+    /**
+     * <code>getWrap</code> returns the wrap mode for a given coordinate axis
+     * on this texture.
+     *
+     * @param axis
+     *            the axis to return for
+     * @return the wrap mode of the texture.
+     * @throws IllegalArgumentException
+     *             if axis is null
+     */
+    public WrapMode getWrap(WrapAxis axis) {
+        switch (axis) {
+            case S:
+                return wrapS;
+            case T:
+                return wrapT;
+            default:
+                throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+        }
+    }
+
+    @Override
+    public Type getType() {
+        return Type.TwoDimensional;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof Texture2D)) {
+            return false;
+        }
+        Texture2D that = (Texture2D) other;
+        if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S))
+            return false;
+        if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T))
+            return false;
+        return super.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = super.hashCode();
+        hash = 79 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0);
+        hash = 79 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+        capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+        wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/texture/Texture3D.java b/engine/src/core/com/jme3/texture/Texture3D.java
new file mode 100644
index 0000000..bded644
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture3D.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * @author Maarten Steur
+ */
+public class Texture3D extends Texture {
+
+    private WrapMode wrapS = WrapMode.EdgeClamp;
+    private WrapMode wrapT = WrapMode.EdgeClamp;
+    private WrapMode wrapR = WrapMode.EdgeClamp;
+
+    /**
+     * Creates a new two-dimensional texture with default attributes.
+     */
+    public Texture3D() {
+        super();
+    }
+
+    /**
+     * Creates a new three-dimensional texture using the given image.
+     * @param img The image to use.
+     */
+    public Texture3D(Image img) {
+        super();
+        setImage(img);
+        if (img.getFormat().isDepthFormat()) {
+            setMagFilter(MagFilter.Nearest);
+            setMinFilter(MinFilter.NearestNoMipMaps);
+        }
+    }
+
+    /**
+     * Creates a new three-dimensional texture for the purpose of offscreen
+     * rendering.
+     *
+     * @see com.jme3.texture.FrameBuffer
+     *
+     * @param width
+     * @param height
+     * @param depth
+     * @param format
+     */
+    public Texture3D(int width, int height, int depth, Image.Format format) {
+        this(new Image(format, width, height, depth, null));
+    }
+
+    /**
+     * Creates a new three-dimensional texture for the purpose of offscreen
+     * rendering.
+     *
+     * @see com.jme3.texture.FrameBuffer
+     *
+     * @param width
+     * @param height
+     * @param format
+     * @param numSamples
+     */
+    public Texture3D(int width, int height, int depth, int numSamples, Image.Format format) {
+        this(new Image(format, width, height, depth, null));
+        getImage().setMultiSamples(numSamples);
+    }
+
+    @Override
+    public Texture createSimpleClone() {
+        Texture3D clone = new Texture3D();
+        createSimpleClone(clone);
+        return clone;
+    }
+
+    @Override
+    public Texture createSimpleClone(Texture rVal) {
+        rVal.setWrap(WrapAxis.S, wrapS);
+        rVal.setWrap(WrapAxis.T, wrapT);
+        rVal.setWrap(WrapAxis.R, wrapR);
+        return super.createSimpleClone(rVal);
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for a
+     * particular axis.
+     *
+     * @param axis
+     *            the texture axis to define a wrapmode on.
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if axis or mode are null
+     */
+    public void setWrap(WrapAxis axis, WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        } else if (axis == null) {
+            throw new IllegalArgumentException("axis can not be null.");
+        }
+        switch (axis) {
+            case S:
+                this.wrapS = mode;
+                break;
+            case T:
+                this.wrapT = mode;
+                break;
+            case R:
+                this.wrapR = mode;
+                break;
+        }
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+     *
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if mode is null
+     */
+    public void setWrap(WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        }
+        this.wrapS = mode;
+        this.wrapT = mode;
+        this.wrapR = mode;
+    }
+
+    /**
+     * <code>getWrap</code> returns the wrap mode for a given coordinate axis
+     * on this texture.
+     *
+     * @param axis
+     *            the axis to return for
+     * @return the wrap mode of the texture.
+     * @throws IllegalArgumentException
+     *             if axis is null
+     */
+    public WrapMode getWrap(WrapAxis axis) {
+        switch (axis) {
+            case S:
+                return wrapS;
+            case T:
+                return wrapT;
+            case R:
+                return wrapR;
+        }
+        throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+    }
+
+    @Override
+    public Type getType() {
+        return Type.ThreeDimensional;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof Texture3D)) {
+            return false;
+        }
+        Texture3D that = (Texture3D) other;
+        if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+            return false;
+        }
+        if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) {
+            return false;
+        }
+        if (this.getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) {
+            return false;
+        }
+        return super.equals(other);
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+        capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+        capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+        wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+        wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/TextureArray.java b/engine/src/core/com/jme3/texture/TextureArray.java
new file mode 100644
index 0000000..e39d95f
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/TextureArray.java
@@ -0,0 +1,113 @@
+package com.jme3.texture;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class implements a Texture array
+ * warning, this feature is only supported on opengl 3.0 version.
+ * To check if a hardware supports TextureArray check : 
+ * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+ * @author phate666
+ */
+public class TextureArray extends Texture {
+
+    private WrapMode wrapS = WrapMode.EdgeClamp;
+    private WrapMode wrapT = WrapMode.EdgeClamp;
+
+    /**
+     * Construct a TextureArray
+     * warning, this feature is only supported on opengl 3.0 version.
+     * To check if a hardware supports TextureArray check : 
+     * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+     */
+    public TextureArray() {
+        super();
+    }
+
+    /**
+     * Construct a TextureArray from the given list of images
+     * warning, this feature is only supported on opengl 3.0 version.
+     * To check if a hardware supports TextureArray check : 
+     * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+     * @param images 
+     */
+    public TextureArray(List<Image> images) {
+        super();        
+        int width = images.get(0).getWidth();
+        int height = images.get(0).getHeight();
+        Image arrayImage = new Image(images.get(0).getFormat(), width, height,
+                null);
+
+        for (Image img : images) {
+            if (img.getHeight() != height || img.getWidth() != width) {
+                Logger.getLogger(TextureArray.class.getName()).log(
+                        Level.WARNING,
+                        "all images must have the same width/height");
+                continue;
+            }
+            arrayImage.addData(img.getData(0));
+        }
+        setImage(arrayImage);
+    }
+
+    @Override
+    public Texture createSimpleClone() {
+        TextureArray clone = new TextureArray();
+        createSimpleClone(clone);
+        return clone;
+    }
+
+    @Override
+    public Texture createSimpleClone(Texture rVal) {
+        rVal.setWrap(WrapAxis.S, wrapS);
+        rVal.setWrap(WrapAxis.T, wrapT);
+        return super.createSimpleClone(rVal);
+    }
+
+    @Override
+    public Type getType() {
+        return Type.TwoDimensionalArray;
+    }
+
+    @Override
+    public WrapMode getWrap(WrapAxis axis) {
+        switch (axis) {
+            case S:
+                return wrapS;
+            case T:
+                return wrapT;
+            default:
+                throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+        }
+    }
+
+    @Override
+    public void setWrap(WrapAxis axis, WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        } else if (axis == null) {
+            throw new IllegalArgumentException("axis can not be null.");
+        }
+        switch (axis) {
+            case S:
+                this.wrapS = mode;
+                break;
+            case T:
+                this.wrapT = mode;
+                break;
+            default:
+                throw new IllegalArgumentException("Not applicable for 2D textures");
+        }
+    }
+
+    @Override
+    public void setWrap(WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        }
+        this.wrapS = mode;
+        this.wrapT = mode;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/TextureCubeMap.java b/engine/src/core/com/jme3/texture/TextureCubeMap.java
new file mode 100644
index 0000000..9290d8a
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/TextureCubeMap.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * Describes a cubemap texture.
+ * The image specified by setImage must contain 6 data units,
+ * each data contains a 2D image representing a cube's face.
+ * The slices are specified in this order:<br/>
+ * <br/>
+ * 0 => Positive X (+x)<br/>
+ * 1 => Negative X (-x)<br/>
+ * 2 => Positive Y (+y)<br/>
+ * 3 => Negative Y (-y)<br/>
+ * 4 => Positive Z (+z)<br/>
+ * 5 => Negative Z (-z)<br/>
+ *
+ * @author Joshua Slack
+ */
+public class TextureCubeMap extends Texture {
+
+    private WrapMode wrapS = WrapMode.EdgeClamp;
+    private WrapMode wrapT = WrapMode.EdgeClamp;
+    private WrapMode wrapR = WrapMode.EdgeClamp;
+
+    /**
+     * Face of the Cubemap as described by its directional offset from the
+     * origin.
+     */
+//    public enum Face {
+//        PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ;
+//    }
+
+    public TextureCubeMap(){
+        super();
+    }
+
+    public TextureCubeMap(Image img){
+        super();
+        setImage(img);
+    }
+
+    public Texture createSimpleClone() {
+        return createSimpleClone(new TextureCubeMap());
+    }
+
+    @Override
+    public Texture createSimpleClone(Texture rVal) {
+        rVal.setWrap(WrapAxis.S, wrapS);
+        rVal.setWrap(WrapAxis.T, wrapT);
+        rVal.setWrap(WrapAxis.R, wrapR);
+        return super.createSimpleClone(rVal);
+    }
+    
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for a
+     * particular axis.
+     * 
+     * @param axis
+     *            the texture axis to define a wrapmode on.
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if axis or mode are null
+     */
+    public void setWrap(WrapAxis axis, WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        } else if (axis == null) {
+            throw new IllegalArgumentException("axis can not be null.");
+        }
+        switch (axis) {
+            case S:
+                this.wrapS = mode;
+                break;
+            case T:
+                this.wrapT = mode;
+                break;
+            case R:
+                this.wrapR = mode;
+                break;
+        }
+    }
+
+    /**
+     * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+     * 
+     * @param mode
+     *            the wrap mode for the given axis of the texture.
+     * @throws IllegalArgumentException
+     *             if mode is null
+     */
+    public void setWrap(WrapMode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode can not be null.");
+        }
+        this.wrapS = mode;
+        this.wrapT = mode;
+        this.wrapR = mode;
+    }
+
+    /**
+     * <code>getWrap</code> returns the wrap mode for a given coordinate axis
+     * on this texture.
+     * 
+     * @param axis
+     *            the axis to return for
+     * @return the wrap mode of the texture.
+     * @throws IllegalArgumentException
+     *             if axis is null
+     */
+    public WrapMode getWrap(WrapAxis axis) {
+        switch (axis) {
+            case S:
+                return wrapS;
+            case T:
+                return wrapT;
+            case R:
+                return wrapR;
+        }
+        throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+    }
+
+    @Override
+    public Type getType() {
+        return Type.CubeMap;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof TextureCubeMap)) {
+            return false;
+        }
+        TextureCubeMap that = (TextureCubeMap) other;
+        if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S))
+            return false;
+        if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T))
+            return false;
+        if (this.getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R))
+            return false;
+        return super.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = super.hashCode();
+        hash = 53 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0);
+        hash = 53 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0);
+        hash = 53 * hash + (this.wrapR != null ? this.wrapR.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+        capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+        capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+        wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+        wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp);
+    }
+}
diff --git a/engine/src/core/com/jme3/ui/Picture.java b/engine/src/core/com/jme3/ui/Picture.java
new file mode 100644
index 0000000..81371f5
--- /dev/null
+++ b/engine/src/core/com/jme3/ui/Picture.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 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.ui;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture2D;
+
+/**
+ * A <code>Picture</code> represents a 2D image drawn on the screen.
+ * It can be used to represent sprites or other background elements.
+ *
+ * @author Kirill Vainer
+ */
+public class Picture extends Geometry {
+
+    private float width  = 1f;
+    private float height = 1f;
+
+    /**
+     * Create a named picture. 
+     * 
+     * By default a picture's width and height are 1
+     * and its position is 0, 0.
+     * 
+     * @param name the name of the picture in the scene graph
+     * @param flipY If true, the Y coordinates of the texture will be flipped.
+     */
+    public Picture(String name, boolean flipY){
+        super(name, new Quad(1, 1, flipY));
+        setQueueBucket(Bucket.Gui);
+        setCullHint(CullHint.Never);
+    }
+
+    /**
+     * Creates a named picture.
+     * By default a picture's width and height are 1
+     * and its position is 0, 0.
+     * The image texture coordinates will not be flipped.
+     * 
+     * @param name the name of the picture in the scene graph 
+     */
+    public Picture(String name){
+        this(name, false);
+    }
+
+    /*
+     * Serialization only. Do not use.
+     */
+    public Picture(){
+    }
+
+    /**
+     * Set the width in pixels of the picture, if the width
+     * does not match the texture's width, then the texture will
+     * be scaled to fit the picture.
+     * 
+     * @param width the width to set.
+     */
+    public void setWidth(float width){
+        this.width = width;
+        setLocalScale(new Vector3f(width, height, 1f));
+    }
+
+    /**
+     * Set the height in pixels of the picture, if the height
+     * does not match the texture's height, then the texture will
+     * be scaled to fit the picture.
+     * 
+     * @param height the height to set.
+     */
+    public void setHeight(float height){
+        this.height = height;
+        setLocalScale(new Vector3f(width, height, 1f));
+    }
+
+    /**
+     * Set the position of the picture in pixels.
+     * The origin (0, 0) is at the bottom-left of the screen.
+     * 
+     * @param x The x coordinate
+     * @param y The y coordinate
+     */
+    public void setPosition(float x, float y){
+        float z = getLocalTranslation().getZ();
+        setLocalTranslation(x, y, z);
+    }
+
+    /**
+     * Set the image to put on the picture.
+     * 
+     * @param assetManager The {@link AssetManager} to use to load the image.
+     * @param imgName The image name.
+     * @param useAlpha If true, the picture will appear transparent and allow
+     * objects behind it to appear through. If false, the transparent
+     * portions will be the image's color at that pixel.
+     */
+    public void setImage(AssetManager assetManager, String imgName, boolean useAlpha){
+        TextureKey key = new TextureKey(imgName, true);
+        Texture2D tex = (Texture2D) assetManager.loadTexture(key);
+        setTexture(assetManager, tex, useAlpha);
+    }
+
+    /**
+     * Set the texture to put on the picture.
+     * 
+     * @param assetManager The {@link AssetManager} to use to load the material.
+     * @param tex The texture
+     * @param useAlpha If true, the picture will appear transparent and allow
+     * objects behind it to appear through. If false, the transparent
+     * portions will be the image's color at that pixel.
+     */
+    public void setTexture(AssetManager assetManager, Texture2D tex, boolean useAlpha){
+        if (getMaterial() == null){
+            Material mat = new Material(assetManager, "Common/MatDefs/Gui/Gui.j3md");
+            mat.setColor("Color", ColorRGBA.White);
+            setMaterial(mat);
+        }
+        material.getAdditionalRenderState().setBlendMode(useAlpha ? BlendMode.Alpha : BlendMode.Off);
+        material.setTexture("Texture", tex);
+    }
+
+}
diff --git a/engine/src/core/com/jme3/util/BufferUtils.java b/engine/src/core/com/jme3/util/BufferUtils.java
new file mode 100644
index 0000000..f0cc698
--- /dev/null
+++ b/engine/src/core/com/jme3/util/BufferUtils.java
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>BufferUtils</code> is a helper class for generating nio buffers from
+ * jME data classes such as Vectors and ColorRGBA.
+ *
+ * @author Joshua Slack
+ * @version $Id: BufferUtils.java,v 1.16 2007/10/29 16:56:18 nca Exp $
+ */
+public final class BufferUtils {
+
+    private static final Map<Buffer, Object> trackingHash = Collections.synchronizedMap(new WeakHashMap<Buffer, Object>());
+    private static final Object ref = new Object();
+    
+    // Note: a WeakHashMap is really bad here since the hashCode() and
+    //       equals() behavior of buffers will vary based on their contents.
+    //       As it stands, put()'ing an empty buffer will wipe out the last
+    //       empty buffer with the same size.  So any tracked memory calculations
+    //       could be lying.
+    //       Besides, the hashmap behavior isn't even being used here and
+    //       yet the expense is still incurred.  For example, a newly allocated
+    //       10,000 byte buffer will iterate through the whole buffer of 0's
+    //       to calculate the hashCode and then potentially do it again to
+    //       calculate the equals()... which by the way is guaranteed for
+    //       every empty buffer of an existing size since they will always produce 
+    //       the same hashCode().
+    //       It would be better to just keep a straight list of weak references
+    //       and clean out the dead every time a new buffer is allocated.
+    //       WeakHashMap is doing that anyway... so there is no extra expense 
+    //       incurred.
+    //       Recommend a ConcurrentLinkedQueue of WeakReferences since it
+    //       supports the threading semantics required with little extra overhead. 
+    private static final boolean trackDirectMemory = false;
+
+    /**
+     * Creates a clone of the given buffer. The clone's capacity is
+     * equal to the given buffer's limit.
+     * 
+     * @param buf The buffer to clone
+     * @return The cloned buffer
+     */
+    public static Buffer clone(Buffer buf) {
+        if (buf instanceof FloatBuffer) {
+            return clone((FloatBuffer) buf);
+        } else if (buf instanceof ShortBuffer) {
+            return clone((ShortBuffer) buf);
+        } else if (buf instanceof ByteBuffer) {
+            return clone((ByteBuffer) buf);
+        } else if (buf instanceof IntBuffer) {
+            return clone((IntBuffer) buf);
+        } else if (buf instanceof DoubleBuffer) {
+            return clone((DoubleBuffer) buf);
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    private static void onBufferAllocated(Buffer buffer){
+        /*
+        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        int initialIndex = 0;
+        
+        for (int i = 0; i < stackTrace.length; i++){
+            if (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){
+                initialIndex = i;
+                break;
+            }
+        }
+        
+        int allocated = buffer.capacity();
+        int size = 0;
+    
+        if (buffer instanceof FloatBuffer){
+            size = 4;
+        }else if (buffer instanceof ShortBuffer){
+            size = 2;
+        }else if (buffer instanceof ByteBuffer){
+            size = 1;
+        }else if (buffer instanceof IntBuffer){
+            size = 4;
+        }else if (buffer instanceof DoubleBuffer){
+            size = 8;
+        }
+        
+        allocated *= size;
+        
+        for (int i = initialIndex; i < stackTrace.length; i++){
+            StackTraceElement element = stackTrace[i];
+            if (element.getClassName().startsWith("java")){
+                break;
+            }
+            
+            try {
+                Class clazz = Class.forName(element.getClassName());
+                if (i == initialIndex){
+                    System.out.println(clazz.getSimpleName()+"."+element.getMethodName()+"():" + element.getLineNumber() + " allocated " + allocated);
+                }else{
+                    System.out.println(" at " + clazz.getSimpleName()+"."+element.getMethodName()+"()");
+                }
+            } catch (ClassNotFoundException ex) {
+            }
+        }*/
+        
+        if (trackDirectMemory){
+            trackingHash.put(buffer, ref);
+        }
+    }
+
+    /**
+     * Generate a new FloatBuffer using the given array of Vector3f objects.
+     * The FloatBuffer will be 3 * data.length long and contain the vector data
+     * as data[0].x, data[0].y, data[0].z, data[1].x... etc.
+     *
+     * @param data array of Vector3f objects to place into a new FloatBuffer
+     */
+    public static FloatBuffer createFloatBuffer(Vector3f... data) {
+        if (data == null) {
+            return null;
+        }
+        FloatBuffer buff = createFloatBuffer(3 * data.length);
+        for (int x = 0; x < data.length; x++) {
+            if (data[x] != null) {
+                buff.put(data[x].x).put(data[x].y).put(data[x].z);
+            } else {
+                buff.put(0).put(0).put(0);
+            }
+        }
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Generate a new FloatBuffer using the given array of Quaternion objects.
+     * The FloatBuffer will be 4 * data.length long and contain the vector data.
+     *
+     * @param data array of Quaternion objects to place into a new FloatBuffer
+     */
+    public static FloatBuffer createFloatBuffer(Quaternion... data) {
+        if (data == null) {
+            return null;
+        }
+        FloatBuffer buff = createFloatBuffer(4 * data.length);
+        for (int x = 0; x < data.length; x++) {
+            if (data[x] != null) {
+                buff.put(data[x].getX()).put(data[x].getY()).put(data[x].getZ()).put(data[x].getW());
+            } else {
+                buff.put(0).put(0).put(0);
+            }
+        }
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Generate a new FloatBuffer using the given array of float primitives.
+     * @param data array of float primitives to place into a new FloatBuffer
+     */
+    public static FloatBuffer createFloatBuffer(float... data) {
+        if (data == null) {
+            return null;
+        }
+        FloatBuffer buff = createFloatBuffer(data.length);
+        buff.clear();
+        buff.put(data);
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Create a new FloatBuffer of an appropriate size to hold the specified
+     * number of Vector3f object data.
+     *
+     * @param vertices
+     *            number of vertices that need to be held by the newly created
+     *            buffer
+     * @return the requested new FloatBuffer
+     */
+    public static FloatBuffer createVector3Buffer(int vertices) {
+        FloatBuffer vBuff = createFloatBuffer(3 * vertices);
+        return vBuff;
+    }
+
+    /**
+     * Create a new FloatBuffer of an appropriate size to hold the specified
+     * number of Vector3f object data only if the given buffer if not already
+     * the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param vertices
+     *            number of vertices that need to be held by the newly created
+     *            buffer
+     * @return the requested new FloatBuffer
+     */
+    public static FloatBuffer createVector3Buffer(FloatBuffer buf, int vertices) {
+        if (buf != null && buf.limit() == 3 * vertices) {
+            buf.rewind();
+            return buf;
+        }
+
+        return createFloatBuffer(3 * vertices);
+    }
+
+    /**
+     * Sets the data contained in the given color into the FloatBuffer at the
+     * specified index.
+     *
+     * @param color
+     *            the data to insert
+     * @param buf
+     *            the buffer to insert into
+     * @param index
+     *            the postion to place the data; in terms of colors not floats
+     */
+    public static void setInBuffer(ColorRGBA color, FloatBuffer buf,
+            int index) {
+        buf.position(index * 4);
+        buf.put(color.r);
+        buf.put(color.g);
+        buf.put(color.b);
+        buf.put(color.a);
+    }
+
+    /**
+     * Sets the data contained in the given quaternion into the FloatBuffer at the
+     * specified index.
+     *
+     * @param quat
+     *            the {@link Quaternion} to insert
+     * @param buf
+     *            the buffer to insert into
+     * @param index
+     *            the postion to place the data; in terms of quaternions not floats
+     */
+    public static void setInBuffer(Quaternion quat, FloatBuffer buf,
+            int index) {
+        buf.position(index * 4);
+        buf.put(quat.getX());
+        buf.put(quat.getY());
+        buf.put(quat.getZ());
+        buf.put(quat.getW());
+    }
+
+    /**
+     * Sets the data contained in the given Vector3F into the FloatBuffer at the
+     * specified index.
+     *
+     * @param vector
+     *            the data to insert
+     * @param buf
+     *            the buffer to insert into
+     * @param index
+     *            the postion to place the data; in terms of vectors not floats
+     */
+    public static void setInBuffer(Vector3f vector, FloatBuffer buf, int index) {
+        if (buf == null) {
+            return;
+        }
+        if (vector == null) {
+            buf.put(index * 3, 0);
+            buf.put((index * 3) + 1, 0);
+            buf.put((index * 3) + 2, 0);
+        } else {
+            buf.put(index * 3, vector.x);
+            buf.put((index * 3) + 1, vector.y);
+            buf.put((index * 3) + 2, vector.z);
+        }
+    }
+
+    /**
+     * Updates the values of the given vector from the specified buffer at the
+     * index provided.
+     *
+     * @param vector
+     *            the vector to set data on
+     * @param buf
+     *            the buffer to read from
+     * @param index
+     *            the position (in terms of vectors, not floats) to read from
+     *            the buf
+     */
+    public static void populateFromBuffer(Vector3f vector, FloatBuffer buf, int index) {
+        vector.x = buf.get(index * 3);
+        vector.y = buf.get(index * 3 + 1);
+        vector.z = buf.get(index * 3 + 2);
+    }
+
+    /**
+     * Generates a Vector3f array from the given FloatBuffer.
+     *
+     * @param buff
+     *            the FloatBuffer to read from
+     * @return a newly generated array of Vector3f objects
+     */
+    public static Vector3f[] getVector3Array(FloatBuffer buff) {
+        buff.clear();
+        Vector3f[] verts = new Vector3f[buff.limit() / 3];
+        for (int x = 0; x < verts.length; x++) {
+            Vector3f v = new Vector3f(buff.get(), buff.get(), buff.get());
+            verts[x] = v;
+        }
+        return verts;
+    }
+
+    /**
+     * Copies a Vector3f from one position in the buffer to another. The index
+     * values are in terms of vector number (eg, vector number 0 is postions 0-2
+     * in the FloatBuffer.)
+     *
+     * @param buf
+     *            the buffer to copy from/to
+     * @param fromPos
+     *            the index of the vector to copy
+     * @param toPos
+     *            the index to copy the vector to
+     */
+    public static void copyInternalVector3(FloatBuffer buf, int fromPos, int toPos) {
+        copyInternal(buf, fromPos * 3, toPos * 3, 3);
+    }
+
+    /**
+     * Normalize a Vector3f in-buffer.
+     *
+     * @param buf
+     *            the buffer to find the Vector3f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to normalize
+     */
+    public static void normalizeVector3(FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector3f tempVec3 = vars.vect1;
+        populateFromBuffer(tempVec3, buf, index);
+        tempVec3.normalizeLocal();
+        setInBuffer(tempVec3, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Add to a Vector3f in-buffer.
+     *
+     * @param toAdd
+     *            the vector to add from
+     * @param buf
+     *            the buffer to find the Vector3f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to add to
+     */
+    public static void addInBuffer(Vector3f toAdd, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector3f tempVec3 = vars.vect1;
+        populateFromBuffer(tempVec3, buf, index);
+        tempVec3.addLocal(toAdd);
+        setInBuffer(tempVec3, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Multiply and store a Vector3f in-buffer.
+     *
+     * @param toMult
+     *            the vector to multiply against
+     * @param buf
+     *            the buffer to find the Vector3f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to multiply
+     */
+    public static void multInBuffer(Vector3f toMult, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector3f tempVec3 = vars.vect1;
+        populateFromBuffer(tempVec3, buf, index);
+        tempVec3.multLocal(toMult);
+        setInBuffer(tempVec3, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Checks to see if the given Vector3f is equals to the data stored in the
+     * buffer at the given data index.
+     *
+     * @param check
+     *            the vector to check against - null will return false.
+     * @param buf
+     *            the buffer to compare data with
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            in the buffer to check against
+     * @return
+     */
+    public static boolean equals(Vector3f check, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector3f tempVec3 = vars.vect1;
+        populateFromBuffer(tempVec3, buf, index);
+        boolean eq = tempVec3.equals(check);
+        vars.release();
+        return eq;
+    }
+
+    // // -- VECTOR2F METHODS -- ////
+    /**
+     * Generate a new FloatBuffer using the given array of Vector2f objects.
+     * The FloatBuffer will be 2 * data.length long and contain the vector data
+     * as data[0].x, data[0].y, data[1].x... etc.
+     *
+     * @param data array of Vector2f objects to place into a new FloatBuffer
+     */
+    public static FloatBuffer createFloatBuffer(Vector2f... data) {
+        if (data == null) {
+            return null;
+        }
+        FloatBuffer buff = createFloatBuffer(2 * data.length);
+        for (int x = 0; x < data.length; x++) {
+            if (data[x] != null) {
+                buff.put(data[x].x).put(data[x].y);
+            } else {
+                buff.put(0).put(0);
+            }
+        }
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Create a new FloatBuffer of an appropriate size to hold the specified
+     * number of Vector2f object data.
+     *
+     * @param vertices
+     *            number of vertices that need to be held by the newly created
+     *            buffer
+     * @return the requested new FloatBuffer
+     */
+    public static FloatBuffer createVector2Buffer(int vertices) {
+        FloatBuffer vBuff = createFloatBuffer(2 * vertices);
+        return vBuff;
+    }
+
+    /**
+     * Create a new FloatBuffer of an appropriate size to hold the specified
+     * number of Vector2f object data only if the given buffer if not already
+     * the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param vertices
+     *            number of vertices that need to be held by the newly created
+     *            buffer
+     * @return the requested new FloatBuffer
+     */
+    public static FloatBuffer createVector2Buffer(FloatBuffer buf, int vertices) {
+        if (buf != null && buf.limit() == 2 * vertices) {
+            buf.rewind();
+            return buf;
+        }
+
+        return createFloatBuffer(2 * vertices);
+    }
+
+    /**
+     * Sets the data contained in the given Vector2F into the FloatBuffer at the
+     * specified index.
+     *
+     * @param vector
+     *            the data to insert
+     * @param buf
+     *            the buffer to insert into
+     * @param index
+     *            the postion to place the data; in terms of vectors not floats
+     */
+    public static void setInBuffer(Vector2f vector, FloatBuffer buf, int index) {
+        buf.put(index * 2, vector.x);
+        buf.put((index * 2) + 1, vector.y);
+    }
+
+    /**
+     * Updates the values of the given vector from the specified buffer at the
+     * index provided.
+     *
+     * @param vector
+     *            the vector to set data on
+     * @param buf
+     *            the buffer to read from
+     * @param index
+     *            the position (in terms of vectors, not floats) to read from
+     *            the buf
+     */
+    public static void populateFromBuffer(Vector2f vector, FloatBuffer buf, int index) {
+        vector.x = buf.get(index * 2);
+        vector.y = buf.get(index * 2 + 1);
+    }
+
+    /**
+     * Generates a Vector2f array from the given FloatBuffer.
+     *
+     * @param buff
+     *            the FloatBuffer to read from
+     * @return a newly generated array of Vector2f objects
+     */
+    public static Vector2f[] getVector2Array(FloatBuffer buff) {
+        buff.clear();
+        Vector2f[] verts = new Vector2f[buff.limit() / 2];
+        for (int x = 0; x < verts.length; x++) {
+            Vector2f v = new Vector2f(buff.get(), buff.get());
+            verts[x] = v;
+        }
+        return verts;
+    }
+
+    /**
+     * Copies a Vector2f from one position in the buffer to another. The index
+     * values are in terms of vector number (eg, vector number 0 is postions 0-1
+     * in the FloatBuffer.)
+     *
+     * @param buf
+     *            the buffer to copy from/to
+     * @param fromPos
+     *            the index of the vector to copy
+     * @param toPos
+     *            the index to copy the vector to
+     */
+    public static void copyInternalVector2(FloatBuffer buf, int fromPos, int toPos) {
+        copyInternal(buf, fromPos * 2, toPos * 2, 2);
+    }
+
+    /**
+     * Normalize a Vector2f in-buffer.
+     *
+     * @param buf
+     *            the buffer to find the Vector2f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to normalize
+     */
+    public static void normalizeVector2(FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector2f tempVec2 = vars.vect2d;
+        populateFromBuffer(tempVec2, buf, index);
+        tempVec2.normalizeLocal();
+        setInBuffer(tempVec2, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Add to a Vector2f in-buffer.
+     *
+     * @param toAdd
+     *            the vector to add from
+     * @param buf
+     *            the buffer to find the Vector2f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to add to
+     */
+    public static void addInBuffer(Vector2f toAdd, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector2f tempVec2 = vars.vect2d;
+        populateFromBuffer(tempVec2, buf, index);
+        tempVec2.addLocal(toAdd);
+        setInBuffer(tempVec2, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Multiply and store a Vector2f in-buffer.
+     *
+     * @param toMult
+     *            the vector to multiply against
+     * @param buf
+     *            the buffer to find the Vector2f within
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            to multiply
+     */
+    public static void multInBuffer(Vector2f toMult, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector2f tempVec2 = vars.vect2d;
+        populateFromBuffer(tempVec2, buf, index);
+        tempVec2.multLocal(toMult);
+        setInBuffer(tempVec2, buf, index);
+        vars.release();
+    }
+
+    /**
+     * Checks to see if the given Vector2f is equals to the data stored in the
+     * buffer at the given data index.
+     *
+     * @param check
+     *            the vector to check against - null will return false.
+     * @param buf
+     *            the buffer to compare data with
+     * @param index
+     *            the position (in terms of vectors, not floats) of the vector
+     *            in the buffer to check against
+     * @return
+     */
+    public static boolean equals(Vector2f check, FloatBuffer buf, int index) {
+        TempVars vars = TempVars.get();
+        Vector2f tempVec2 = vars.vect2d;
+        populateFromBuffer(tempVec2, buf, index);
+        boolean eq = tempVec2.equals(check);
+        vars.release();
+        return eq;
+    }
+
+    ////  -- INT METHODS -- ////
+    /**
+     * Generate a new IntBuffer using the given array of ints. The IntBuffer
+     * will be data.length long and contain the int data as data[0], data[1]...
+     * etc.
+     *
+     * @param data
+     *            array of ints to place into a new IntBuffer
+     */
+    public static IntBuffer createIntBuffer(int... data) {
+        if (data == null) {
+            return null;
+        }
+        IntBuffer buff = createIntBuffer(data.length);
+        buff.clear();
+        buff.put(data);
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Create a new int[] array and populate it with the given IntBuffer's
+     * contents.
+     *
+     * @param buff
+     *            the IntBuffer to read from
+     * @return a new int array populated from the IntBuffer
+     */
+    public static int[] getIntArray(IntBuffer buff) {
+        if (buff == null) {
+            return null;
+        }
+        buff.clear();
+        int[] inds = new int[buff.limit()];
+        for (int x = 0; x < inds.length; x++) {
+            inds[x] = buff.get();
+        }
+        return inds;
+    }
+
+    /**
+     * Create a new float[] array and populate it with the given FloatBuffer's
+     * contents.
+     *
+     * @param buff
+     *            the FloatBuffer to read from
+     * @return a new float array populated from the FloatBuffer
+     */
+    public static float[] getFloatArray(FloatBuffer buff) {
+        if (buff == null) {
+            return null;
+        }
+        buff.clear();
+        float[] inds = new float[buff.limit()];
+        for (int x = 0; x < inds.length; x++) {
+            inds[x] = buff.get();
+        }
+        return inds;
+    }
+
+    //// -- GENERAL DOUBLE ROUTINES -- ////
+    /**
+     * Create a new DoubleBuffer of the specified size.
+     *
+     * @param size
+     *            required number of double to store.
+     * @return the new DoubleBuffer
+     */
+    public static DoubleBuffer createDoubleBuffer(int size) {
+        DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
+        buf.clear();
+        onBufferAllocated(buf);
+        return buf;
+    }
+
+    /**
+     * Create a new DoubleBuffer of an appropriate size to hold the specified
+     * number of doubles only if the given buffer if not already the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param size
+     *            number of doubles that need to be held by the newly created
+     *            buffer
+     * @return the requested new DoubleBuffer
+     */
+    public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, int size) {
+        if (buf != null && buf.limit() == size) {
+            buf.rewind();
+            return buf;
+        }
+
+        buf = createDoubleBuffer(size);
+        return buf;
+    }
+
+    /**
+     * Creates a new DoubleBuffer with the same contents as the given
+     * DoubleBuffer. The new DoubleBuffer is seperate from the old one and
+     * changes are not reflected across. If you want to reflect changes,
+     * consider using Buffer.duplicate().
+     *
+     * @param buf
+     *            the DoubleBuffer to copy
+     * @return the copy
+     */
+    public static DoubleBuffer clone(DoubleBuffer buf) {
+        if (buf == null) {
+            return null;
+        }
+        buf.rewind();
+
+        DoubleBuffer copy;
+        if (buf.isDirect()) {
+            copy = createDoubleBuffer(buf.limit());
+        } else {
+            copy = DoubleBuffer.allocate(buf.limit());
+        }
+        copy.put(buf);
+
+        return copy;
+    }
+
+    //// -- GENERAL FLOAT ROUTINES -- ////
+    /**
+     * Create a new FloatBuffer of the specified size.
+     *
+     * @param size
+     *            required number of floats to store.
+     * @return the new FloatBuffer
+     */
+    public static FloatBuffer createFloatBuffer(int size) {
+        FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        buf.clear();
+        onBufferAllocated(buf);
+        return buf;
+    }
+
+    /**
+     * Copies floats from one position in the buffer to another.
+     *
+     * @param buf
+     *            the buffer to copy from/to
+     * @param fromPos
+     *            the starting point to copy from
+     * @param toPos
+     *            the starting point to copy to
+     * @param length
+     *            the number of floats to copy
+     */
+    public static void copyInternal(FloatBuffer buf, int fromPos, int toPos, int length) {
+        float[] data = new float[length];
+        buf.position(fromPos);
+        buf.get(data);
+        buf.position(toPos);
+        buf.put(data);
+    }
+
+    /**
+     * Creates a new FloatBuffer with the same contents as the given
+     * FloatBuffer. The new FloatBuffer is seperate from the old one and changes
+     * are not reflected across. If you want to reflect changes, consider using
+     * Buffer.duplicate().
+     *
+     * @param buf
+     *            the FloatBuffer to copy
+     * @return the copy
+     */
+    public static FloatBuffer clone(FloatBuffer buf) {
+        if (buf == null) {
+            return null;
+        }
+        buf.rewind();
+
+        FloatBuffer copy;
+        if (buf.isDirect()) {
+            copy = createFloatBuffer(buf.limit());
+        } else {
+            copy = FloatBuffer.allocate(buf.limit());
+        }
+        copy.put(buf);
+
+        return copy;
+    }
+
+    //// -- GENERAL INT ROUTINES -- ////
+    /**
+     * Create a new IntBuffer of the specified size.
+     *
+     * @param size
+     *            required number of ints to store.
+     * @return the new IntBuffer
+     */
+    public static IntBuffer createIntBuffer(int size) {
+        IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
+        buf.clear();
+        onBufferAllocated(buf);
+        return buf;
+    }
+
+    /**
+     * Create a new IntBuffer of an appropriate size to hold the specified
+     * number of ints only if the given buffer if not already the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param size
+     *            number of ints that need to be held by the newly created
+     *            buffer
+     * @return the requested new IntBuffer
+     */
+    public static IntBuffer createIntBuffer(IntBuffer buf, int size) {
+        if (buf != null && buf.limit() == size) {
+            buf.rewind();
+            return buf;
+        }
+
+        buf = createIntBuffer(size);
+        return buf;
+    }
+
+    /**
+     * Creates a new IntBuffer with the same contents as the given IntBuffer.
+     * The new IntBuffer is seperate from the old one and changes are not
+     * reflected across. If you want to reflect changes, consider using
+     * Buffer.duplicate().
+     *
+     * @param buf
+     *            the IntBuffer to copy
+     * @return the copy
+     */
+    public static IntBuffer clone(IntBuffer buf) {
+        if (buf == null) {
+            return null;
+        }
+        buf.rewind();
+
+        IntBuffer copy;
+        if (buf.isDirect()) {
+            copy = createIntBuffer(buf.limit());
+        } else {
+            copy = IntBuffer.allocate(buf.limit());
+        }
+        copy.put(buf);
+
+        return copy;
+    }
+
+    //// -- GENERAL BYTE ROUTINES -- ////
+    /**
+     * Create a new ByteBuffer of the specified size.
+     *
+     * @param size
+     *            required number of ints to store.
+     * @return the new IntBuffer
+     */
+    public static ByteBuffer createByteBuffer(int size) {
+        ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+        buf.clear();
+        onBufferAllocated(buf);
+        return buf;
+    }
+
+    /**
+     * Create a new ByteBuffer of an appropriate size to hold the specified
+     * number of ints only if the given buffer if not already the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param size
+     *            number of bytes that need to be held by the newly created
+     *            buffer
+     * @return the requested new IntBuffer
+     */
+    public static ByteBuffer createByteBuffer(ByteBuffer buf, int size) {
+        if (buf != null && buf.limit() == size) {
+            buf.rewind();
+            return buf;
+        }
+
+        buf = createByteBuffer(size);
+        return buf;
+    }
+
+    public static ByteBuffer createByteBuffer(byte... data) {
+        ByteBuffer bb = createByteBuffer(data.length);
+        bb.put(data);
+        bb.flip();
+        return bb;
+    }
+
+    public static ByteBuffer createByteBuffer(String data) {
+        byte[] bytes = data.getBytes();
+        ByteBuffer bb = createByteBuffer(bytes.length);
+        bb.put(bytes);
+        bb.flip();
+        return bb;
+    }
+
+    /**
+     * Creates a new ByteBuffer with the same contents as the given ByteBuffer.
+     * The new ByteBuffer is seperate from the old one and changes are not
+     * reflected across. If you want to reflect changes, consider using
+     * Buffer.duplicate().
+     *
+     * @param buf
+     *            the ByteBuffer to copy
+     * @return the copy
+     */
+    public static ByteBuffer clone(ByteBuffer buf) {
+        if (buf == null) {
+            return null;
+        }
+        buf.rewind();
+
+        ByteBuffer copy;
+        if (buf.isDirect()) {
+            copy = createByteBuffer(buf.limit());
+        } else {
+            copy = ByteBuffer.allocate(buf.limit());
+        }
+        copy.put(buf);
+
+        return copy;
+    }
+
+    //// -- GENERAL SHORT ROUTINES -- ////
+    /**
+     * Create a new ShortBuffer of the specified size.
+     *
+     * @param size
+     *            required number of shorts to store.
+     * @return the new ShortBuffer
+     */
+    public static ShortBuffer createShortBuffer(int size) {
+        ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
+        buf.clear();
+        onBufferAllocated(buf);
+        return buf;
+    }
+
+    /**
+     * Create a new ShortBuffer of an appropriate size to hold the specified
+     * number of shorts only if the given buffer if not already the right size.
+     *
+     * @param buf
+     *            the buffer to first check and rewind
+     * @param size
+     *            number of shorts that need to be held by the newly created
+     *            buffer
+     * @return the requested new ShortBuffer
+     */
+    public static ShortBuffer createShortBuffer(ShortBuffer buf, int size) {
+        if (buf != null && buf.limit() == size) {
+            buf.rewind();
+            return buf;
+        }
+
+        buf = createShortBuffer(size);
+        return buf;
+    }
+
+    public static ShortBuffer createShortBuffer(short... data) {
+        if (data == null) {
+            return null;
+        }
+        ShortBuffer buff = createShortBuffer(data.length);
+        buff.clear();
+        buff.put(data);
+        buff.flip();
+        return buff;
+    }
+
+    /**
+     * Creates a new ShortBuffer with the same contents as the given ShortBuffer.
+     * The new ShortBuffer is seperate from the old one and changes are not
+     * reflected across. If you want to reflect changes, consider using
+     * Buffer.duplicate().
+     *
+     * @param buf
+     *            the ShortBuffer to copy
+     * @return the copy
+     */
+    public static ShortBuffer clone(ShortBuffer buf) {
+        if (buf == null) {
+            return null;
+        }
+        buf.rewind();
+
+        ShortBuffer copy;
+        if (buf.isDirect()) {
+            copy = createShortBuffer(buf.limit());
+        } else {
+            copy = ShortBuffer.allocate(buf.limit());
+        }
+        copy.put(buf);
+
+        return copy;
+    }
+
+    /**
+     * Ensures there is at least the <code>required</code> number of entries left after the current position of the
+     * buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer.
+     * @param buffer buffer that should be checked/copied (may be null)
+     * @param required minimum number of elements that should be remaining in the returned buffer
+     * @return a buffer large enough to receive at least the <code>required</code> number of entries, same position as
+     * the input buffer, not null
+     */
+    public static FloatBuffer ensureLargeEnough(FloatBuffer buffer, int required) {
+        if (buffer == null || (buffer.remaining() < required)) {
+            int position = (buffer != null ? buffer.position() : 0);
+            FloatBuffer newVerts = createFloatBuffer(position + required);
+            if (buffer != null) {
+                buffer.rewind();
+                newVerts.put(buffer);
+                newVerts.position(position);
+            }
+            buffer = newVerts;
+        }
+        return buffer;
+    }
+
+    public static ShortBuffer ensureLargeEnough(ShortBuffer buffer, int required) {
+        if (buffer == null || (buffer.remaining() < required)) {
+            int position = (buffer != null ? buffer.position() : 0);
+            ShortBuffer newVerts = createShortBuffer(position + required);
+            if (buffer != null) {
+                buffer.rewind();
+                newVerts.put(buffer);
+                newVerts.position(position);
+            }
+            buffer = newVerts;
+        }
+        return buffer;
+    }
+
+    public static ByteBuffer ensureLargeEnough(ByteBuffer buffer, int required) {
+        if (buffer == null || (buffer.remaining() < required)) {
+            int position = (buffer != null ? buffer.position() : 0);
+            ByteBuffer newVerts = createByteBuffer(position + required);
+            if (buffer != null) {
+                buffer.rewind();
+                newVerts.put(buffer);
+                newVerts.position(position);
+            }
+            buffer = newVerts;
+        }
+        return buffer;
+    }
+
+    public static void printCurrentDirectMemory(StringBuilder store) {
+        long totalHeld = 0;
+        // make a new set to hold the keys to prevent concurrency issues.
+        ArrayList<Buffer> bufs = new ArrayList<Buffer>(trackingHash.keySet());
+        int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0;
+        int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0;
+        for (Buffer b : bufs) {
+            if (b instanceof ByteBuffer) {
+                totalHeld += b.capacity();
+                bBufsM += b.capacity();
+                bBufs++;
+            } else if (b instanceof FloatBuffer) {
+                totalHeld += b.capacity() * 4;
+                fBufsM += b.capacity() * 4;
+                fBufs++;
+            } else if (b instanceof IntBuffer) {
+                totalHeld += b.capacity() * 4;
+                iBufsM += b.capacity() * 4;
+                iBufs++;
+            } else if (b instanceof ShortBuffer) {
+                totalHeld += b.capacity() * 2;
+                sBufsM += b.capacity() * 2;
+                sBufs++;
+            } else if (b instanceof DoubleBuffer) {
+                totalHeld += b.capacity() * 8;
+                dBufsM += b.capacity() * 8;
+                dBufs++;
+            }
+        }
+        long heapMem = Runtime.getRuntime().totalMemory()
+                - Runtime.getRuntime().freeMemory();
+
+        boolean printStout = store == null;
+        if (store == null) {
+            store = new StringBuilder();
+        }
+        store.append("Existing buffers: ").append(bufs.size()).append("\n");
+        store.append("(b: ").append(bBufs).append("  f: ").append(fBufs).append("  i: ").append(iBufs).append("  s: ").append(sBufs).append("  d: ").append(dBufs).append(")").append("\n");
+        store.append("Total   heap memory held: ").append(heapMem / 1024).append("kb\n");
+        store.append("Total direct memory held: ").append(totalHeld / 1024).append("kb\n");
+        store.append("(b: ").append(bBufsM / 1024).append("kb  f: ").append(fBufsM / 1024).append("kb  i: ").append(iBufsM / 1024).append("kb  s: ").append(sBufsM / 1024).append("kb  d: ").append(dBufsM / 1024).append("kb)").append("\n");
+        if (printStout) {
+            System.out.println(store.toString());
+        }
+    }
+    
+    /**
+    * Direct buffers are garbage collected by using a phantom reference and a
+    * reference queue. Every once a while, the JVM checks the reference queue and
+    * cleans the direct buffers. However, as this doesn't happen
+    * immediately after discarding all references to a direct buffer, it's
+    * easy to OutOfMemoryError yourself using direct buffers. This function
+    * explicitly calls the Cleaner method of a direct buffer.
+    * 
+    * @param toBeDestroyed
+    *          The direct buffer that will be "cleaned". Utilizes reflection.
+    *          
+    */
+    public static void destroyDirectBuffer(Buffer toBeDestroyed) {
+    
+        if (!toBeDestroyed.isDirect()) {
+            return;
+        }
+        try {
+            Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
+            cleanerMethod.setAccessible(true);
+            Object cleaner = cleanerMethod.invoke(toBeDestroyed);
+            if (cleaner != null) {
+                Method cleanMethod = cleaner.getClass().getMethod("clean");
+                cleanMethod.setAccessible(true);
+                cleanMethod.invoke(cleaner);
+            } else {
+                // Try the alternate approach of getting the viewed buffer
+                Method viewedBufferMethod = toBeDestroyed.getClass().getMethod("viewedBuffer");
+                viewedBufferMethod.setAccessible(true);
+                Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed);
+                if (viewedBuffer != null) {
+                    destroyDirectBuffer( (Buffer)viewedBuffer );
+                } else {
+                    Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed);
+                }
+            }
+        } catch (IllegalAccessException ex) {
+            Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+        } catch (IllegalArgumentException ex) {
+            Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+        } catch (InvocationTargetException ex) {
+            Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+        } catch (NoSuchMethodException ex) {
+            Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+        } catch (SecurityException ex) {
+            Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+        }
+    }
+
+}
diff --git a/engine/src/core/com/jme3/util/IntMap.java b/engine/src/core/com/jme3/util/IntMap.java
new file mode 100644
index 0000000..edf659b
--- /dev/null
+++ b/engine/src/core/com/jme3/util/IntMap.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import com.jme3.util.IntMap.Entry;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Similar to a {@link Map} except that ints are used as keys.
+ * 
+ * Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a>
+ * 
+ * @author Nate 
+ */
+public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
+
+    private final IntMapIterator iterator = new IntMapIterator();
+            
+    private Entry[] table;
+    private final float loadFactor;
+    private int size, mask, capacity, threshold;
+
+    public IntMap() {
+        this(16, 0.75f);
+    }
+
+    public IntMap(int initialCapacity) {
+        this(initialCapacity, 0.75f);
+    }
+
+    public IntMap(int initialCapacity, float loadFactor) {
+        if (initialCapacity > 1 << 30){
+            throw new IllegalArgumentException("initialCapacity is too large.");
+        }
+        if (initialCapacity < 0){
+            throw new IllegalArgumentException("initialCapacity must be greater than zero.");
+        }
+        if (loadFactor <= 0){
+            throw new IllegalArgumentException("initialCapacity must be greater than zero.");
+        }
+        capacity = 1;
+        while (capacity < initialCapacity){
+            capacity <<= 1;
+        }
+        this.loadFactor = loadFactor;
+        this.threshold = (int) (capacity * loadFactor);
+        this.table = new Entry[capacity];
+        this.mask = capacity - 1;
+    }
+
+    @Override
+    public IntMap<T> clone(){
+        try{
+            IntMap<T> clone = (IntMap<T>) super.clone();
+            Entry[] newTable = new Entry[table.length];
+            for (int i = table.length - 1; i >= 0; i--){
+                if (table[i] != null)
+                    newTable[i] = table[i].clone();
+            }
+            clone.table = newTable;
+            return clone;
+        }catch (CloneNotSupportedException ex){
+        }
+        return null;
+    }
+
+    public boolean containsValue(Object value) {
+        Entry[] table = this.table;
+        for (int i = table.length; i-- > 0;){
+            for (Entry e = table[i]; e != null; e = e.next){
+                if (e.value.equals(value)){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean containsKey(int key) {
+        int index = ((int) key) & mask;
+        for (Entry e = table[index]; e != null; e = e.next){
+            if (e.key == key){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public T get(int key) {
+        int index = key & mask;
+        for (Entry e = table[index]; e != null; e = e.next){
+            if (e.key == key){
+                return (T) e.value;
+            }
+        }
+        return null;
+    }
+
+    public T put(int key, T value) {
+        int index = key & mask;
+        // Check if key already exists.
+        for (Entry e = table[index]; e != null; e = e.next){
+            if (e.key != key){
+                continue;
+            }
+            Object oldValue = e.value;
+            e.value = value;
+            return (T) oldValue;
+        }
+        table[index] = new Entry(key, value, table[index]);
+        if (size++ >= threshold){
+            // Rehash.
+            int newCapacity = 2 * capacity;
+            Entry[] newTable = new Entry[newCapacity];
+            Entry[] src = table;
+            int bucketmask = newCapacity - 1;
+            for (int j = 0; j < src.length; j++){
+                Entry e = src[j];
+                if (e != null){
+                    src[j] = null;
+                    do{
+                        Entry next = e.next;
+                        index = e.key & bucketmask;
+                        e.next = newTable[index];
+                        newTable[index] = e;
+                        e = next;
+                    }while (e != null);
+                }
+            }
+            table = newTable;
+            capacity = newCapacity;
+            threshold = (int) (newCapacity * loadFactor);
+            mask = capacity - 1;
+        }
+        return null;
+    }
+
+    public T remove(int key) {
+        int index = key & mask;
+        Entry prev = table[index];
+        Entry e = prev;
+        while (e != null){
+            Entry next = e.next;
+            if (e.key == key){
+                size--;
+                if (prev == e){
+                    table[index] = next;
+                }else{
+                    prev.next = next;
+                }
+                return (T) e.value;
+            }
+            prev = e;
+            e = next;
+        }
+        return null;
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public void clear() {
+        Entry[] table = this.table;
+        for (int index = table.length; --index >= 0;){
+            table[index] = null;
+        }
+        size = 0;
+    }
+
+    public Iterator<Entry<T>> iterator() {
+        iterator.beginUse();
+        return iterator;
+    }
+
+    final class IntMapIterator implements Iterator<Entry<T>> {
+
+        /**
+         * Current entry.
+         */
+        private Entry cur;
+
+        /**
+         * Entry in the table
+         */
+        private int idx = 0;
+
+        /**
+         * Element in the entry
+         */
+        private int el  = 0;
+
+        public IntMapIterator() {
+        }
+
+        public void beginUse(){
+            cur = table[0];
+            idx = 0;
+            el = 0;
+        }
+        
+        public boolean hasNext() {
+            return el < size;
+        }
+
+        public Entry next() {
+            if (el >= size)
+                throw new IllegalStateException("No more elements!");
+
+            if (cur != null){
+                Entry e = cur;
+                cur = cur.next;
+                el++;
+                return e;
+            }
+//            if (cur != null && cur.next != null){
+                // if we have a current entry, continue to the next entry in the list
+//                cur = cur.next;
+//                el++;
+//                return cur;
+//            }
+
+            do {
+                // either we exhausted the current entry list, or
+                // the entry was null. find another non-null entry.
+                cur = table[++idx];
+            } while (cur == null);
+            
+            Entry e = cur;
+            cur = cur.next;
+            el ++;
+            
+            return e;
+        }
+
+        public void remove() {
+        }
+        
+    }
+    
+    public static final class Entry<T> implements Cloneable {
+
+        final int key;
+        T value;
+        Entry next;
+
+        Entry(int k, T v, Entry n) {
+            key = k;
+            value = v;
+            next = n;
+        }
+
+        public int getKey(){
+            return key;
+        }
+
+        public T getValue(){
+            return value;
+        }
+
+        @Override
+        public String toString(){
+            return key + " => " + value;
+        }
+
+        @Override
+        public Entry<T> clone(){
+            try{
+                Entry<T> clone = (Entry<T>) super.clone();
+                clone.next = next != null ? next.clone() : null;
+                return clone;
+            }catch (CloneNotSupportedException ex){
+            }
+            return null;
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/util/JmeFormatter.java b/engine/src/core/com/jme3/util/JmeFormatter.java
new file mode 100644
index 0000000..998438a
--- /dev/null
+++ b/engine/src/core/com/jme3/util/JmeFormatter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * More simple formatter than the default one used in Java logging.
+ * Example output: <br/>
+ * INFO Display3D 12:00 PM: Display created.
+ */
+public class JmeFormatter extends Formatter {
+
+    private Date calendar = new Date();
+    private String lineSeperator;
+    private MessageFormat format;
+    private Object args[] = new Object[1];
+    private StringBuffer store = new StringBuffer();
+
+    public JmeFormatter(){
+        lineSeperator = System.getProperty("line.separator");
+        format = new MessageFormat("{0,time}");
+    }
+
+    @Override
+    public String format(LogRecord record) {
+        StringBuffer sb = new StringBuffer();
+
+        calendar.setTime(record.getMillis());
+        args[0] = calendar;
+        store.setLength(0);
+        format.format(args, store, null);
+
+        String clazz = null;
+        try{
+            clazz = Class.forName(record.getSourceClassName()).getSimpleName();
+        } catch (ClassNotFoundException ex){
+        }
+        
+        sb.append(record.getLevel().getLocalizedName()).append(" ");
+        sb.append(clazz).append(" ");
+        sb.append(store.toString()).append(" ");
+        sb.append(formatMessage(record)).append(lineSeperator);
+
+        if (record.getThrown() != null) {
+            try {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new PrintWriter(sw);
+                record.getThrown().printStackTrace(pw);
+                pw.close();
+                sb.append(sw.toString());
+            } catch (Exception ex) {
+            }
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/engine/src/core/com/jme3/util/ListMap.java b/engine/src/core/com/jme3/util/ListMap.java
new file mode 100644
index 0000000..c5b6de4
--- /dev/null
+++ b/engine/src/core/com/jme3/util/ListMap.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Implementation of a Map that favors iteration speed rather than
+ * get/put speed.
+ *
+ * @author Kirill Vainer
+ */
+public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
+
+    public static void main(String[] args){
+        Map<String, String> map = new ListMap<String, String>();
+        map.put( "bob", "hello");
+        System.out.println(map.get("bob"));
+        map.remove("bob");
+        System.out.println(map.size());
+
+        map.put("abc", "1");
+        map.put("def", "2");
+        map.put("ghi", "3");
+        map.put("jkl", "4");
+        map.put("mno", "5");
+        System.out.println(map.get("ghi"));
+    }
+
+    private final static class ListMapEntry<K, V> implements Map.Entry<K, V>, Cloneable {
+
+        private final K key;
+        private V value;
+
+        public ListMapEntry(K key, V value){
+            this.key = key;
+            this.value = value;
+        }
+
+        public K getKey() {
+            return key;
+        }
+
+        public V getValue() {
+            return value;
+        }
+
+        public V setValue(V v) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ListMapEntry<K, V> clone(){
+            return new ListMapEntry<K, V>(key, value);
+        }
+
+    }
+    
+    private final HashMap<K, V> backingMap;
+    private ListMapEntry<K, V>[] entries;
+    
+//    private final ArrayList<ListMapEntry<K,V>> entries;
+
+    public ListMap(){
+        entries = new ListMapEntry[4];
+        backingMap = new HashMap<K, V>(4);
+//       entries = new ArrayList<ListMapEntry<K,V>>();
+    }
+
+    public ListMap(int initialCapacity){
+        entries = new ListMapEntry[initialCapacity];
+        backingMap = new HashMap<K, V>(initialCapacity);
+//        entries = new ArrayList<ListMapEntry<K, V>>(initialCapacity);
+    }
+
+    public ListMap(Map<? extends K, ? extends V> map){
+        entries = new ListMapEntry[map.size()];
+        backingMap = new HashMap<K, V>(map.size());
+//        entries = new ArrayList<ListMapEntry<K, V>>(map.size());
+        putAll(map);
+    }
+
+    public int size() {
+//        return entries.size();
+        return backingMap.size();
+    }
+
+    public Entry<K, V> getEntry(int index){
+//        return entries.get(index);
+        return entries[index];
+    }
+
+    public V getValue(int index){
+//        return entries.get(index).value;
+        return entries[index].value;
+    }
+
+    public K getKey(int index){
+//        return entries.get(index).key;
+        return entries[index].key;
+    }
+
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    private static boolean keyEq(Object keyA, Object keyB){
+        return keyA.hashCode() == keyB.hashCode() ? (keyA == keyB) || keyA.equals(keyB) : false;
+    }
+//
+//    private static boolean valEq(Object a, Object b){
+//        return a == null ? (b == null) : a.equals(b);
+//    }
+
+    public boolean containsKey(Object key) {
+        return backingMap.containsKey( (K) key); 
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key))
+//                return true;
+//        }
+//        return false;
+    }
+
+    public boolean containsValue(Object value) {
+        return backingMap.containsValue( (V) value); 
+//        for (int i = 0; i < entries.size(); i++){
+//            if (valEq(entries.get(i).value, value))
+//                return true;
+//        }
+//        return false;
+    }
+
+    public V get(Object key) {
+        return backingMap.get( (K) key); 
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key))
+//                return entry.value;
+//        }
+//        return null;
+    }
+
+    public V put(K key, V value) {
+        if (backingMap.containsKey(key)){
+            // set the value on the entry
+            int size = size();
+            for (int i = 0; i < size; i++){
+                ListMapEntry<K, V> entry = entries[i];
+                if (keyEq(entry.key, key)){
+                    entry.value = value;
+                    break;
+                }
+            }
+        }else{
+            int size = size();
+            // expand list as necessary
+            if (size == entries.length){
+                ListMapEntry<K, V>[] tmpEntries = entries;
+                entries = new ListMapEntry[size * 2];
+                System.arraycopy(tmpEntries, 0, entries, 0, size);
+            }
+            entries[size] = new ListMapEntry<K, V>(key, value);
+        }
+        return backingMap.put(key, value);
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        // check if entry exists, if yes, overwrite it with new value
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key)){
+//                V prevValue = entry.value;
+//                entry.value = value;
+//                return prevValue;
+//            }
+//        }
+//        
+//        // add a new entry
+//        entries.add(new ListMapEntry<K, V>(key, value));
+//        return null;
+    }
+
+    public V remove(Object key) {
+        V element = backingMap.remove( (K) key);
+        if (element != null){
+            // find removed element
+            int size = size() + 1; // includes removed element
+            int removedIndex = -1;
+            for (int i = 0; i < size; i++){
+                ListMapEntry<K, V> entry = entries[i];
+                if (keyEq(entry.key, key)){
+                    removedIndex = i;
+                    break;
+                }
+            }
+            assert removedIndex >= 0;
+            
+            size --;
+            for (int i = removedIndex; i < size; i++){
+                entries[i] = entries[i+1];
+            }
+        }
+        return element;
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key)){
+//                return entries.remove(i).value;
+//            }
+//        }
+//        return null;
+    }
+
+    public void putAll(Map<? extends K, ? extends V> map) {
+        for (Entry<? extends K, ? extends V> entry : map.entrySet()){
+            put(entry.getKey(), entry.getValue());
+        }
+        
+        
+//        if (map instanceof ListMap){
+//            ListMap<K, V> listMap = (ListMap<K, V>) map;
+//            ArrayList<ListMapEntry<K, V>> otherEntries = listMap.entries;
+//            for (int i = 0; i < otherEntries.size(); i++){
+//                ListMapEntry<K, V> entry = otherEntries.get(i);
+//                put(entry.key, entry.value);
+//            }
+//        }else{
+//            for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()){
+//                put(entry.getKey(), entry.getValue());
+//            }
+//        }
+    }
+
+    public void clear() {
+        backingMap.clear();
+//        entries.clear();
+    }
+
+    @Override
+    public ListMap<K, V> clone(){
+        ListMap<K, V> clone = new ListMap<K, V>(size());
+        clone.putAll(this);
+        return clone;
+    }
+
+    public Set<K> keySet() {
+        return backingMap.keySet();
+//        HashSet<K> keys = new HashSet<K>();
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            keys.add(entry.key);
+//        }
+//        return keys;
+    }
+
+    public Collection<V> values() {
+        return backingMap.values();
+//        ArrayList<V> values = new ArrayList<V>();
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            values.add(entry.value);
+//        }
+//        return values;
+    }
+
+    public Set<Entry<K, V>> entrySet() {
+        return backingMap.entrySet();
+//        HashSet<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
+//        entryset.addAll(entries);
+//        return entryset;
+    }
+
+}
diff --git a/engine/src/core/com/jme3/util/LittleEndien.java b/engine/src/core/com/jme3/util/LittleEndien.java
new file mode 100644
index 0000000..0f71596
--- /dev/null
+++ b/engine/src/core/com/jme3/util/LittleEndien.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import java.io.*;
+
+/**
+ * <code>LittleEndien</code> is a class to read littleendien stored data
+ * via a InputStream.  All functions work as defined in DataInput, but
+ * assume they come from a LittleEndien input stream.  Currently used to read .ms3d and .3ds files.
+ * @author Jack Lindamood
+ */
+public class LittleEndien extends InputStream implements DataInput {
+
+    private BufferedInputStream in;
+    private BufferedReader inRead;
+
+    /**
+     * Creates a new LittleEndien reader from the given input stream.  The
+     * stream is wrapped in a BufferedReader automatically.
+     * @param in The input stream to read from.
+     */
+    public LittleEndien(InputStream in) {
+        this.in = new BufferedInputStream(in);
+        inRead = new BufferedReader(new InputStreamReader(in));
+    }
+
+    public int read() throws IOException {
+        return in.read();
+    }
+
+    @Override
+    public int read(byte[] buf) throws IOException {
+        return in.read(buf);
+    }
+
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
+        return in.read(buf, off, len);
+    }
+
+    public int readUnsignedShort() throws IOException {
+        return (in.read() & 0xff) | ((in.read() & 0xff) << 8);
+    }
+
+    /**
+     * read an unsigned int as a long
+     */
+    public long readUInt() throws IOException {
+        return ((in.read() & 0xff)
+                | ((in.read() & 0xff) << 8)
+                | ((in.read() & 0xff) << 16)
+                | (((long) (in.read() & 0xff)) << 24));
+    }
+
+    public boolean readBoolean() throws IOException {
+        return (in.read() != 0);
+    }
+
+    public byte readByte() throws IOException {
+        return (byte) in.read();
+    }
+
+    public int readUnsignedByte() throws IOException {
+        return in.read();
+    }
+
+    public short readShort() throws IOException {
+        return (short) this.readUnsignedShort();
+    }
+
+    public char readChar() throws IOException {
+        return (char) this.readUnsignedShort();
+    }
+
+    public int readInt() throws IOException {
+        return ((in.read() & 0xff)
+                | ((in.read() & 0xff) << 8)
+                | ((in.read() & 0xff) << 16)
+                | ((in.read() & 0xff) << 24));
+    }
+
+    public long readLong() throws IOException {
+        return ((in.read() & 0xff)
+                | ((long) (in.read() & 0xff) << 8)
+                | ((long) (in.read() & 0xff) << 16)
+                | ((long) (in.read() & 0xff) << 24)
+                | ((long) (in.read() & 0xff) << 32)
+                | ((long) (in.read() & 0xff) << 40)
+                | ((long) (in.read() & 0xff) << 48)
+                | ((long) (in.read() & 0xff) << 56));
+    }
+
+    public float readFloat() throws IOException {
+        return Float.intBitsToFloat(readInt());
+    }
+
+    public double readDouble() throws IOException {
+        return Double.longBitsToDouble(readLong());
+    }
+
+    public void readFully(byte b[]) throws IOException {
+        in.read(b, 0, b.length);
+    }
+
+    public void readFully(byte b[], int off, int len) throws IOException {
+        in.read(b, off, len);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        return (int) in.skip(n);
+    }
+
+    public String readLine() throws IOException {
+        return inRead.readLine();
+    }
+
+    public String readUTF() throws IOException {
+        throw new IOException("Unsupported operation");
+    }
+
+    @Override
+    public void close() throws IOException {
+        in.close();
+    }
+
+    @Override
+    public int available() throws IOException {
+        return in.available();
+    }
+}
diff --git a/engine/src/core/com/jme3/util/NativeObject.java b/engine/src/core/com/jme3/util/NativeObject.java
new file mode 100644
index 0000000..a59cb05
--- /dev/null
+++ b/engine/src/core/com/jme3/util/NativeObject.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+/**
+ * Describes a native object. An encapsulation of a certain object 
+ * on the native side of the graphics or audio library.
+ * 
+ * This class is used to track when OpenGL and OpenAL native objects are 
+ * collected by the garbage collector, and then invoke the proper destructor
+ * on the OpenGL library to delete it from memory.
+ */
+public abstract class NativeObject implements Cloneable {
+
+    /**
+     * The ID of the object, usually depends on its type.
+     * Typically returned from calls like glGenTextures, glGenBuffers, etc.
+     */
+    protected int id = -1;
+
+    /**
+     * A reference to a "handle". By hard referencing a certain object, it's
+     * possible to find when a certain GLObject is no longer used, and to delete
+     * its instance from the graphics library.
+     */
+    protected Object handleRef = null;
+
+    /**
+     * True if the data represented by this GLObject has been changed
+     * and needs to be updated before used.
+     */
+    protected boolean updateNeeded = true;
+
+    /**
+     * The type of the GLObject, usually specified by a subclass.
+     */
+    protected final Class<?> type;
+
+    /**
+     * Creates a new GLObject with the given type. Should be
+     * called by the subclasses.
+     * 
+     * @param type The type that the subclass represents.
+     */
+    public NativeObject(Class<?> type){
+        this.type = type;
+        this.handleRef = new Object();
+    }
+
+    /**
+     * Protected constructor that doesn't allocate handle ref.
+     * This is used in subclasses for the createDestructableClone().
+     */
+    protected NativeObject(Class<?> type, int id){
+        this.type = type;
+        this.id = id;
+    }
+
+    /**
+     * Sets the ID of the GLObject. This method is used in Renderer and must
+     * not be called by the user.
+     * @param id The ID to set
+     */
+    public void setId(int id){
+        if (this.id != -1)
+            throw new IllegalStateException("ID has already been set for this GL object.");
+
+        this.id = id;
+    }
+
+    /**
+     * @return The ID of the object. Should not be used by user code in most
+     * cases.
+     */
+    public int getId(){
+        return id;
+    }
+
+    /**
+     * Internal use only. Indicates that the object has changed
+     * and its state needs to be updated.
+     */
+    public void setUpdateNeeded(){
+        updateNeeded = true;
+    }
+
+    /**
+     * Internal use only. Indicates that the state changes were applied.
+     */
+    public void clearUpdateNeeded(){
+        updateNeeded = false;
+    }
+
+    /**
+     * Internal use only. Check if {@link #setUpdateNeeded()} was called before.
+     */
+    public boolean isUpdateNeeded(){
+        return updateNeeded;
+    }
+
+    @Override
+    public String toString(){
+        return "Native" + type.getSimpleName() + " " + id;
+    }
+
+    /**
+     * This should create a deep clone. For a shallow clone, use
+     * createDestructableClone().
+     */
+    @Override
+    protected NativeObject clone(){
+        try{
+            NativeObject obj = (NativeObject) super.clone();
+            obj.handleRef = new Object();
+            obj.id = -1;
+            obj.updateNeeded = true;
+            return obj;
+        }catch (CloneNotSupportedException ex){
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Called when the GL context is restarted to reset all IDs. Prevents
+     * "white textures" on display restart.
+     */
+    public abstract void resetObject();
+
+    /**
+     * Deletes the GL object from the GPU when it is no longer used. Called
+     * automatically by the GL object manager.
+     * 
+     * @param rendererObject The renderer to be used to delete the object
+     */
+    public abstract void deleteObject(Object rendererObject);
+
+    /**
+     * Creates a shallow clone of this GL Object. The deleteObject method
+     * should be functional for this object.
+     */
+    public abstract NativeObject createDestructableClone();
+}
diff --git a/engine/src/core/com/jme3/util/NativeObjectManager.java b/engine/src/core/com/jme3/util/NativeObjectManager.java
new file mode 100644
index 0000000..f8d1d18
--- /dev/null
+++ b/engine/src/core/com/jme3/util/NativeObjectManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * GLObjectManager tracks all GLObjects used by the Renderer. Using a
+ * <code>ReferenceQueue</code> the <code>GLObjectManager</code> can delete
+ * unused objects from GPU when their counterparts on the CPU are no longer used.
+ *
+ * On restart, the renderer may request the objects to be reset, thus allowing
+ * the GLObjects to re-initialize with the new display context.
+ */
+public class NativeObjectManager {
+
+    private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName());
+
+    /**
+     * The queue will receive notifications of {@link NativeObject}s which are no longer
+     * referenced.
+     */
+    private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
+
+    /**
+     * List of currently active GLObjects.
+     */
+    private ArrayList<NativeObjectRef> refList
+            = new ArrayList<NativeObjectRef>();
+
+    private class NativeObjectRef extends PhantomReference<Object>{
+        
+        private NativeObject objClone;
+        private WeakReference<NativeObject> realObj;
+
+        public NativeObjectRef(NativeObject obj){
+            super(obj.handleRef, refQueue);
+            assert obj.handleRef != null;
+
+            this.realObj = new WeakReference<NativeObject>(obj);
+            this.objClone = obj.createDestructableClone();       
+        }
+    }
+
+    /**
+     * Register a GLObject with the manager.
+     */
+    public void registerForCleanup(NativeObject obj){
+        NativeObjectRef ref = new NativeObjectRef(obj);
+        refList.add(ref);
+        if (logger.isLoggable(Level.FINEST))
+            logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()});
+    }
+
+    /**
+     * Deletes unused GLObjects
+     */
+    public void deleteUnused(Object rendererObject){
+        while (true){
+            NativeObjectRef ref = (NativeObjectRef) refQueue.poll();
+            if (ref == null)
+                return;
+
+            refList.remove(ref);
+            ref.objClone.deleteObject(rendererObject);
+            if (logger.isLoggable(Level.FINEST))
+                logger.log(Level.FINEST, "Deleted: {0}", ref.objClone);
+        }
+    }
+
+    /**
+     * Deletes all objects. Must only be called when display is destroyed.
+     */
+    public void deleteAllObjects(Object rendererObject){
+        deleteUnused(rendererObject);
+        for (NativeObjectRef ref : refList){
+            ref.objClone.deleteObject(rendererObject);
+            NativeObject realObj = ref.realObj.get();
+            if (realObj != null){
+                // Note: make sure to reset them as well
+                // They may get used in a new renderer in the future
+                realObj.resetObject();
+            }
+        }
+        refList.clear();
+    }
+
+    /**
+     * Resets all {@link NativeObject}s.
+     */
+    public void resetObjects(){
+        for (NativeObjectRef ref : refList){
+            // here we use the actual obj not the clone,
+            // otherwise its useless
+            NativeObject realObj = ref.realObj.get();
+            if (realObj == null)
+                continue;
+            
+            realObj.resetObject();
+            if (logger.isLoggable(Level.FINEST))
+                logger.log(Level.FINEST, "Reset: {0}", realObj);
+        }
+        refList.clear();
+    }
+
+//    public void printObjects(){
+//        System.out.println(" ------------------- ");
+//        System.out.println(" GL Object count: "+ objectList.size());
+//        for (GLObject obj : objectList){
+//            System.out.println(obj);
+//        }
+//    }
+}
diff --git a/engine/src/core/com/jme3/util/PlaceholderAssets.java b/engine/src/core/com/jme3/util/PlaceholderAssets.java
new file mode 100644
index 0000000..c36abc9
--- /dev/null
+++ b/engine/src/core/com/jme3/util/PlaceholderAssets.java
@@ -0,0 +1,72 @@
+package com.jme3.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioBuffer;
+import com.jme3.audio.AudioData;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.nio.ByteBuffer;
+
+public class PlaceholderAssets {
+    
+    /**
+     * Checkerboard of white and red squares
+     */
+    private static final byte[] imageData = {
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+        (byte)0xFF, (byte)0x00, (byte)0x00,
+        (byte)0xFF, (byte)0xFF, (byte)0xFF,
+    };
+    
+    public static Image getPlaceholderImage(){
+        ByteBuffer tempData = BufferUtils.createByteBuffer(3 * 4 * 4);
+        tempData.put(imageData).flip();
+        return new Image(Format.RGB8, 4, 4, tempData);
+    }
+    
+    public static Material getPlaceholderMaterial(AssetManager assetManager){
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Red);
+        return mat;
+    }
+    
+    public static Spatial getPlaceholderModel(AssetManager assetManager){
+        // What should be the size? Nobody knows
+        // the user's expected scale...
+        Box box = new Box(1, 1, 1);
+        Geometry geom = new Geometry("placeholder", box);
+        geom.setMaterial(getPlaceholderMaterial(assetManager));
+        return geom;
+    }
+    
+    public static AudioData getPlaceholderAudio(){
+        AudioBuffer audioBuf = new AudioBuffer();
+        audioBuf.setupFormat(1, 8, 44100);
+        ByteBuffer bb = BufferUtils.createByteBuffer(1);
+        bb.put((byte)0).flip();
+        audioBuf.updateData(bb);
+        return audioBuf;
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/util/SafeArrayList.java b/engine/src/core/com/jme3/util/SafeArrayList.java
new file mode 100644
index 0000000..fcd6971
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SafeArrayList.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2009-2011 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;
+
+import java.util.*;
+
+/**
+ *  <p>Provides a list with similar modification semantics to java.util.concurrent's
+ *  CopyOnWriteArrayList except that it is not concurrent and also provides
+ *  direct access to the current array.  This List allows modification of the
+ *  contents while iterating as any iterators will be looking at a snapshot of
+ *  the list at the time they were created.  Similarly, access the raw internal
+ *  array is only presenting a snap shot and so can be safely iterated while
+ *  the list is changing.</p>
+ *
+ *  <p>All modifications, including set() operations will cause a copy of the
+ *  data to be created that replaces the old version.  Because this list is 
+ *  not designed for threading concurrency it further optimizes the "many modifications"
+ *  case by buffering them as a normal ArrayList until the next time the contents
+ *  are accessed.</p>
+ *
+ *  <p>Normal list modification performance should be equal to ArrayList in a
+ *  many situations and always better than CopyOnWriteArrayList.  Optimum usage
+ *  is when modifications are done infrequently or in batches... as is often the
+ *  case in a scene graph.  Read operations perform superior to all other methods
+ *  as the array can be accessed directly.</p>
+ *
+ *  <p>Important caveats over normal java.util.Lists:</p>
+ *  <ul>
+ *  <li>Even though this class supports modifying the list, the subList() method
+ *  returns a read-only list.  This technically breaks the List contract.</li>
+ *  <li>The ListIterators returned by this class only support the remove()
+ *  modification method.  add() and set() are not supported on the iterator.
+ *  Even after ListIterator.remove() or Iterator.remove() is called, this change
+ *  is not reflected in the iterator instance as it is still refering to its
+ *  original snapshot.
+ *  </ul>  
+ *
+ *  @version   $Revision: 8940 $
+ *  @author    Paul Speed
+ */
+public class SafeArrayList<E> implements List<E> {
+    
+    // Implementing List directly to avoid accidentally acquiring
+    // incorrect or non-optimal behavior from AbstractList.  For
+    // example, the default iterator() method will not work for 
+    // this list.
+
+    // Note: given the particular use-cases this was intended,
+    //       it would make sense to nerf the public mutators and
+    //       make this publicly act like a read-only list.
+    //       SafeArrayList-specific methods could then be exposed
+    //       for the classes like Node and Spatial to use to manage
+    //       the list.  This was the callers couldn't remove a child
+    //       without it being detached properly, for example.      
+
+    private Class<E> elementType; 
+    private List<E> buffer;
+    private E[] backingArray;
+    private int size = 0;
+ 
+    public SafeArrayList(Class<E> elementType) {
+        this.elementType = elementType;        
+    }
+    
+    public SafeArrayList(Class<E> elementType, Collection<? extends E> c) {
+        this.elementType = elementType;        
+        addAll(c);
+    }
+
+    protected final <T> T[] createArray(Class<T> type, int size) {
+        return (T[])java.lang.reflect.Array.newInstance(type, size);               
+    }
+    
+    protected final E[] createArray(int size) {
+        return createArray(elementType, size); 
+    }
+ 
+    /**
+     *  Returns a current snapshot of this List's backing array that
+     *  is guaranteed not to change through further List manipulation.
+     *  Changes to this array may or may not be reflected in the list and
+     *  should be avoided.
+     */
+    public final E[] getArray() {
+        if( backingArray != null )
+            return backingArray;
+            
+        if( buffer == null ) {
+            backingArray = createArray(0);
+        } else {            
+            // Only keep the array or the buffer but never both at
+            // the same time.  1) it saves space, 2) it keeps the rest
+            // of the code safer.
+            backingArray = buffer.toArray( createArray(buffer.size()) );
+            buffer = null;
+        }
+        return backingArray;
+    }
+ 
+    protected final List<E> getBuffer() {
+        if( buffer != null )
+            return buffer;
+            
+        if( backingArray == null ) {
+            buffer = new ArrayList();
+        } else {       
+            // Only keep the array or the buffer but never both at
+            // the same time.  1) it saves space, 2) it keeps the rest
+            // of the code safer.            
+            buffer = new ArrayList( Arrays.asList(backingArray) );
+            backingArray = null;
+        }
+        return buffer;
+    }
+    
+    public final int size() {
+        return size;            
+    }
+    
+    public final boolean isEmpty() {
+        return size == 0;
+    }
+    
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+    
+    public Iterator<E> iterator() {
+        return listIterator();
+    }
+
+    public Object[] toArray() {
+        return getArray();
+    }
+    
+    public <T> T[] toArray(T[] a) {
+ 
+        E[] array = getArray();
+        if (a.length < array.length) {
+            return (T[])Arrays.copyOf(array, array.length, a.getClass());
+        } 
+ 
+        System.arraycopy( array, 0, a, 0, array.length );
+        
+        if (a.length > array.length) {
+            a[array.length] = null;
+        }
+           
+        return a;
+    }
+    
+    public boolean add(E e) {
+        boolean result = getBuffer().add(e);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public boolean remove(Object o) {
+        boolean result = getBuffer().remove(o);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public boolean containsAll(Collection<?> c) {
+        return Arrays.asList(getArray()).containsAll(c);
+    }
+    
+    public boolean addAll(Collection<? extends E> c) {
+        boolean result = getBuffer().addAll(c);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public boolean addAll(int index, Collection<? extends E> c) {
+        boolean result = getBuffer().addAll(index, c);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public boolean removeAll(Collection<?> c) {
+        boolean result = getBuffer().removeAll(c);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public boolean retainAll(Collection<?> c) {
+        boolean result = getBuffer().retainAll(c);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public void clear() {
+        getBuffer().clear();
+        size = 0;
+    }
+    
+    public boolean equals(Object o) {
+        if( o == this ) 
+            return true;
+        if( !(o instanceof List) ) //covers null too
+            return false;
+        List other = (List)o;
+        Iterator i1 = iterator();
+        Iterator i2 = other.iterator();
+        while( i1.hasNext() && i2.hasNext() ) {
+            Object o1 = i1.next();
+            Object o2 = i2.next();
+            if( o1 == o2 )
+                continue;
+            if( o1 == null || !o1.equals(o2) )
+                return false;
+        }
+        return !(i1.hasNext() || !i2.hasNext());            
+    }
+    
+    public int hashCode() {
+        // Exactly the hash code described in the List interface, basically
+        E[] array = getArray();
+        int result = 1;
+        for( E e : array ) {
+            result = 31 * result + (e == null ? 0 : e.hashCode());
+        }
+        return result;
+    }
+    
+    public final E get(int index) {
+        if( backingArray != null )
+            return backingArray[index];
+        if( buffer != null )
+            return buffer.get(index);
+        throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );        
+    }
+    
+    public E set(int index, E element) {
+        return getBuffer().set(index, element);
+    }
+    
+    public void add(int index, E element) {
+        getBuffer().add(index, element);
+        size = getBuffer().size();
+    }
+    
+    public E remove(int index) {
+        E result = getBuffer().remove(index);
+        size = getBuffer().size();
+        return result;
+    }
+    
+    public int indexOf(Object o) {
+        E[] array = getArray();
+        for( int i = 0; i < array.length; i++ ) {
+            E element = array[i];
+            if( element == o ) {
+                return i;
+            }
+            if( element != null && element.equals(o) ) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    public int lastIndexOf(Object o) {
+        E[] array = getArray();
+        for( int i = array.length - 1; i >= 0; i-- ) {
+            E element = array[i];
+            if( element == o ) {
+                return i;
+            }
+            if( element != null && element.equals(o) ) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    public ListIterator<E> listIterator() {
+        return new ArrayIterator<E>(getArray(), 0);
+    }
+    
+    public ListIterator<E> listIterator(int index) {
+        return new ArrayIterator<E>(getArray(), index);
+    }
+    
+    public List<E> subList(int fromIndex, int toIndex) {
+    
+        // So far JME doesn't use subList that I can see so I'm nerfing it.
+        List<E> raw =  Arrays.asList(getArray()).subList(fromIndex, toIndex);
+        return Collections.unmodifiableList(raw);
+    }
+ 
+    public String toString() {
+ 
+        E[] array = getArray();
+        if( array.length == 0 ) {
+            return "[]";
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append('[');
+        for( int i = 0; i < array.length; i++ ) {
+            if( i > 0 )
+                sb.append( ", " );
+            E e = array[i];
+            sb.append( e == this ? "(this Collection)" : e );
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+ 
+    protected class ArrayIterator<E> implements ListIterator<E> {
+        private E[] array;
+        private int next;
+        private int lastReturned;
+        
+        protected ArrayIterator( E[] array, int index ) {
+            this.array = array;
+            this.next = index;
+            this.lastReturned = -1;
+        }
+        
+        public boolean hasNext() {
+            return next != array.length;
+        }
+        
+        public E next() {
+            if( !hasNext() )
+                throw new NoSuchElementException();
+            lastReturned = next++;
+            return array[lastReturned];
+        }
+        
+        public boolean hasPrevious() {
+            return next != 0;           
+        }        
+        
+        public E previous() {
+            if( !hasPrevious() )
+                throw new NoSuchElementException();
+            lastReturned = --next;
+            return array[lastReturned];
+        }
+        
+        public int nextIndex() {
+            return next;       
+        }
+        
+        public int previousIndex() {
+            return next - 1;
+        }
+        
+        public void remove() {
+            // This operation is not so easy to do but we will fake it.
+            // The issue is that the backing list could be completely
+            // different than the one this iterator is a snapshot of.
+            // We'll just remove(element) which in most cases will be 
+            // correct.  If the list had earlier .equals() equivalent
+            // elements then we'll remove one of those instead.  Either
+            // way, none of those changes are reflected in this iterator.
+            SafeArrayList.this.remove( array[lastReturned] );
+        }
+        
+        public void set(E e) {
+            throw new UnsupportedOperationException();
+        }
+        
+        public void add(E e) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/util/SkyFactory.java b/engine/src/core/com/jme3/util/SkyFactory.java
new file mode 100644
index 0000000..2808696
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SkyFactory.java
@@ -0,0 +1,214 @@
+package com.jme3.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.TextureCubeMap;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * <code>SkyFactory</code> is used to create jME {@link Spatial}s that can
+ * be attached to the scene to display a sky image in the background.
+ * 
+ * @author Kirill Vainer
+ */
+public class SkyFactory {
+
+    /**
+     * Creates a sky using the given texture (cubemap or spheremap). 
+     * 
+     * @param assetManager The asset manager to use to load materials
+     * @param texture Texture to use for the sky
+     * @param normalScale The normal scale is multiplied by the 3D normal
+     * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply 
+     * and transformation to the normal.
+     * @param sphereMap The way the texture is used
+     * depends on this value:<br>
+     * <ul>
+     * <li>true: Its a Texture2D with the pixels arranged for  
+     * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li>
+     * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D
+     * then the image is taken from it and is inserted into a TextureCubeMap</li>
+     * </ul>
+     * @return A spatial representing the sky
+     */
+    public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap) {
+        return createSky(assetManager, texture, normalScale, sphereMap, 10);
+    }
+
+    /**
+     * Creates a sky using the given texture (cubemap or spheremap). 
+     * 
+     * @param assetManager The asset manager to use to load materials
+     * @param texture Texture to use for the sky
+     * @param normalScale The normal scale is multiplied by the 3D normal
+     * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply 
+     * and transformation to the normal.
+     * @param sphereMap The way the texture is used
+     * depends on this value:<br>
+     * <ul>
+     * <li>true: Its a Texture2D with the pixels arranged for  
+     * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li>
+     * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D
+     * then the image is taken from it and is inserted into a TextureCubeMap</li>
+     * </ul>
+     * @param sphereRadius If specified, this will be the sky sphere's radius.
+     * This should be the camera's near plane for optimal quality.
+     * @return A spatial representing the sky
+     */
+    public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap, int sphereRadius) {
+        if (texture == null) {
+            throw new IllegalArgumentException("texture cannot be null");
+        }
+        final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true);
+
+        Geometry sky = new Geometry("Sky", sphereMesh);
+        sky.setQueueBucket(Bucket.Sky);
+        sky.setCullHint(Spatial.CullHint.Never);
+        sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO));
+
+        Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+
+        skyMat.setVector3("NormalScale", normalScale);
+        if (sphereMap) {
+            skyMat.setBoolean("SphereMap", sphereMap);
+        } else if (!(texture instanceof TextureCubeMap)) {
+            // make sure its a cubemap
+            Image img = texture.getImage();
+            texture = new TextureCubeMap();
+            texture.setImage(img);
+        }
+        skyMat.setTexture("Texture", texture);
+        sky.setMaterial(skyMat);
+
+        return sky;
+    }
+
+    private static void checkImage(Image image) {
+//        if (image.getDepth() != 1)
+//            throw new IllegalArgumentException("3D/Array images not allowed");
+
+        if (image.getWidth() != image.getHeight()) {
+            throw new IllegalArgumentException("Image width and height must be the same");
+        }
+
+        if (image.getMultiSamples() != 1) {
+            throw new IllegalArgumentException("Multisample textures not allowed");
+        }
+    }
+
+    private static void checkImagesForCubeMap(Image... images) {
+        if (images.length == 1) {
+            return;
+        }
+
+        Format fmt = images[0].getFormat();
+        int width = images[0].getWidth();
+        int height = images[0].getHeight();
+        
+        ByteBuffer data = images[0].getData(0);
+        int size = data != null ? data.capacity() : 0;
+
+        checkImage(images[0]);
+
+        for (int i = 1; i < images.length; i++) {
+            Image image = images[i];
+            checkImage(images[i]);
+            if (image.getFormat() != fmt) {
+                throw new IllegalArgumentException("Images must have same format");
+            }
+            if (image.getWidth() != width || image.getHeight() != height)  {
+                throw new IllegalArgumentException("Images must have same resolution");
+            }
+            ByteBuffer data2 = image.getData(0);
+            if (data2 != null){
+                if (data2.capacity() != size) {
+                    throw new IllegalArgumentException("Images must have same size");
+                }
+            }
+        }
+    }
+
+    public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale) {
+        return createSky(assetManager, west, east, north, south, up, down, normalScale, 10);
+    }
+
+    public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale, int sphereRadius) {
+        final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true);
+        Geometry sky = new Geometry("Sky", sphereMesh);
+        sky.setQueueBucket(Bucket.Sky);
+        sky.setCullHint(Spatial.CullHint.Never);
+        sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO));
+
+        Image westImg = west.getImage();
+        Image eastImg = east.getImage();
+        Image northImg = north.getImage();
+        Image southImg = south.getImage();
+        Image upImg = up.getImage();
+        Image downImg = down.getImage();
+
+        checkImagesForCubeMap(westImg, eastImg, northImg, southImg, upImg, downImg);
+
+        Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), null);
+
+        cubeImage.addData(westImg.getData(0));
+        cubeImage.addData(eastImg.getData(0));
+
+        cubeImage.addData(downImg.getData(0));
+        cubeImage.addData(upImg.getData(0));
+
+        cubeImage.addData(southImg.getData(0));
+        cubeImage.addData(northImg.getData(0));
+        
+        if (westImg.getEfficentData() != null){
+            // also consilidate efficient data
+            ArrayList<Object> efficientData = new ArrayList<Object>(6);
+            efficientData.add(westImg.getEfficentData());
+            efficientData.add(eastImg.getEfficentData());
+            efficientData.add(downImg.getEfficentData());
+            efficientData.add(upImg.getEfficentData());
+            efficientData.add(southImg.getEfficentData());
+            efficientData.add(northImg.getEfficentData());
+            cubeImage.setEfficentData(efficientData);
+        }
+
+        TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
+        cubeMap.setAnisotropicFilter(0);
+        cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
+        cubeMap.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+        cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
+
+        Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+        skyMat.setTexture("Texture", cubeMap);
+        skyMat.setVector3("NormalScale", normalScale);
+        sky.setMaterial(skyMat);
+
+        return sky;
+    }
+
+    public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) {
+        return createSky(assetManager, west, east, north, south, up, down, Vector3f.UNIT_XYZ);
+    }
+
+    public static Spatial createSky(AssetManager assetManager, Texture texture, boolean sphereMap) {
+        return createSky(assetManager, texture, Vector3f.UNIT_XYZ, sphereMap);
+    }
+
+    public static Spatial createSky(AssetManager assetManager, String textureName, boolean sphereMap) {
+        TextureKey key = new TextureKey(textureName, true);
+        key.setGenerateMips(true);
+        key.setAsCube(!sphereMap);
+        Texture tex = assetManager.loadTexture(key);
+        return createSky(assetManager, tex, sphereMap);
+    }
+}
diff --git a/engine/src/core/com/jme3/util/SortUtil.java b/engine/src/core/com/jme3/util/SortUtil.java
new file mode 100644
index 0000000..fabe3bf
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SortUtil.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Quick and merge sort implementations that create no garbage, unlike {@link
+ * Arrays#sort}. The merge sort is stable, the quick sort is not.
+ */
+public class SortUtil {
+
+    /** 
+     * The size at or below which we will use insertion sort because it's
+     * probably faster. 
+     */
+    private static final int INSERTION_SORT_THRESHOLD = 7;
+    
+    
+    /**
+ procedure optimizedGnomeSort(a[])
+    pos := 1
+    last := 0
+    while pos < length(a)
+        if (a[pos] >= a[pos-1])
+            if (last != 0)
+                pos := last
+                last := 0
+            end if
+            pos := pos + 1
+        else
+            swap a[pos] and a[pos-1]
+            if (pos > 1)
+                if (last == 0)
+                    last := pos
+                end if
+                pos := pos - 1
+            else
+                pos := pos + 1
+            end if
+        end if
+    end while
+end procedure
+     */
+    
+    public static void gsort(Object[] a, Comparator comp) {
+        int pos = 1;
+        int last = 0;
+        int length = a.length;
+        
+        while (pos < length){
+            if ( comp.compare(a[pos], a[pos-1]) >= 0 ){
+                if (last != 0){
+                    pos = last;
+                    last = 0;
+                }
+                pos ++;
+            }else{
+                Object tmp = a[pos];
+                a[pos] = a[pos-1];
+                a[pos-1] = tmp;
+                
+                if (pos > 1){
+                    if (last == 0){
+                        last = pos;
+                    }
+                    pos --;
+                }else{
+                    pos ++;
+                }
+            }
+        }
+        
+//        int p = 0;
+//        int l = a.length;
+//        while (p < l) {
+//            int pm1 = p - 1;
+//            if (p == 0 || comp.compare(a[p], a[pm1]) >= 0) {
+//                p++;
+//            } else {
+//                Object t = a[p];
+//                a[p] = a[pm1];
+//                a[pm1] = t;
+//                p--;
+//            }
+//        }
+    }
+
+    private static void test(Float[] original, Float[] sorted, Comparator<Float> ic) {
+        long time, dt;
+        
+        time = System.nanoTime();
+        for (int i = 0; i < 1000000; i++) {
+            System.arraycopy(original, 0, sorted, 0, original.length);
+            gsort(sorted, ic);
+        }
+        dt = System.nanoTime() - time;
+        System.out.println("GSort " + (dt/1000000.0) + " ms");
+
+        time = System.nanoTime();
+        for (int i = 0; i < 1000000; i++) {
+            System.arraycopy(original, 0, sorted, 0, original.length);
+            qsort(sorted, ic);
+        }
+        dt = System.nanoTime() - time;
+        System.out.println("QSort " + (dt/1000000.0) + " ms");
+
+        time = System.nanoTime();
+        for (int i = 0; i < 1000000; i++) {
+            System.arraycopy(original, 0, sorted, 0, original.length);
+            msort(original, sorted, ic);
+        }
+        dt = System.nanoTime() - time;
+        System.out.println("MSort " + (dt/1000000.0) + " ms");
+
+        time = System.nanoTime();
+        for (int i = 0; i < 1000000; i++) {
+            System.arraycopy(original, 0, sorted, 0, original.length);
+            Arrays.sort(sorted, ic);
+        }
+        dt = System.nanoTime() - time;
+        System.out.println("ASort " + (dt/1000000.0) + " ms");
+    }
+
+    public static void main(String[] args) {
+        Comparator<Float> ic = new Comparator<Float>() {
+
+            public int compare(Float o1, Float o2) {
+                return (int) (o1 - o2);
+            }
+        };
+        Float[] original = new Float[]{2f, 1f, 5f, 3f, 4f, 6f, 8f, 9f,
+            11f, 10f, 12f, 13f, 14f, 15f, 7f, 19f, 20f, 18f, 16f, 17f,
+            21f, 23f, 22f, 24f, 25f, 27f, 26f, 29f, 28f, 30f, 31f};
+        Float[] sorted = new Float[original.length];
+
+        while (true) {
+            test(original, sorted, ic);
+        }
+    }
+
+    /**
+     * Quick sorts the supplied array using the specified comparator.
+     */
+    public static void qsort(Object[] a, Comparator comp) {
+        qsort(a, 0, a.length - 1, comp);
+    }
+
+    /**
+     * Quick sorts the supplied array using the specified comparator.
+     *
+     * @param lo0 the index of the lowest element to include in the sort.
+     * @param hi0 the index of the highest element to include in the sort.
+     */
+    @SuppressWarnings("unchecked")
+    public static void qsort(Object[] a, int lo0, int hi0, Comparator comp) {
+        // bail out if we're already done
+        if (hi0 <= lo0) {
+            return;
+        }
+
+        // if this is a two element list, do a simple sort on it
+        Object t;
+        if (hi0 - lo0 == 1) {
+            // if they're not already sorted, swap them
+            if (comp.compare(a[hi0], a[lo0]) < 0) {
+                t = a[lo0];
+                a[lo0] = a[hi0];
+                a[hi0] = t;
+            }
+            return;
+        }
+
+        // the middle element in the array is our partitioning element
+        Object mid = a[(lo0 + hi0) / 2];
+
+        // set up our partitioning boundaries
+        int lo = lo0 - 1, hi = hi0 + 1;
+
+        // loop through the array until indices cross
+        for (;;) {
+            // find the first element that is greater than or equal to
+            // the partition element starting from the left Index.
+            while (comp.compare(a[++lo], mid) < 0);
+
+            // find an element that is smaller than or equal to
+            // the partition element starting from the right Index.
+            while (comp.compare(mid, a[--hi]) < 0);
+
+            // swap the two elements or bail out of the loop
+            if (hi > lo) {
+                t = a[lo];
+                a[lo] = a[hi];
+                a[hi] = t;
+            } else {
+                break;
+            }
+        }
+
+        // if the right index has not reached the left side of array
+        // must now sort the left partition
+        if (lo0 < lo - 1) {
+            qsort(a, lo0, lo - 1, comp);
+        }
+
+        // if the left index has not reached the right side of array
+        // must now sort the right partition
+        if (hi + 1 < hi0) {
+            qsort(a, hi + 1, hi0, comp);
+        }
+    }
+
+    public static void qsort(int[] a, int lo0, int hi0, Comparator comp) {
+        // bail out if we're already done
+        if (hi0 <= lo0) {
+            return;
+        }
+
+        // if this is a two element list, do a simple sort on it
+        int t;
+        if (hi0 - lo0 == 1) {
+            // if they're not already sorted, swap them
+            if (comp.compare(a[hi0], a[lo0]) < 0) {
+                t = a[lo0];
+                a[lo0] = a[hi0];
+                a[hi0] = t;
+            }
+            return;
+        }
+
+        // the middle element in the array is our partitioning element
+        int mid = a[(lo0 + hi0) / 2];
+
+        // set up our partitioning boundaries
+        int lo = lo0 - 1, hi = hi0 + 1;
+
+        // loop through the array until indices cross
+        for (;;) {
+            // find the first element that is greater than or equal to
+            // the partition element starting from the left Index.
+            while (comp.compare(a[++lo], mid) < 0);
+
+            // find an element that is smaller than or equal to
+            // the partition element starting from the right Index.
+            while (comp.compare(mid, a[--hi]) < 0);
+
+            // swap the two elements or bail out of the loop
+            if (hi > lo) {
+                t = a[lo];
+                a[lo] = a[hi];
+                a[hi] = t;
+            } else {
+                break;
+            }
+        }
+
+        // if the right index has not reached the left side of array
+        // must now sort the left partition
+        if (lo0 < lo - 1) {
+            qsort(a, lo0, lo - 1, comp);
+        }
+
+        // if the left index has not reached the right side of array
+        // must now sort the right partition
+        if (hi + 1 < hi0) {
+            qsort(a, hi + 1, hi0, comp);
+        }
+    }
+    
+    /**
+     * Merge sort
+     */
+    public static void msort(Object[] src, Object[] dest, Comparator comp){
+        msort(src, dest, 0, src.length - 1, comp);
+    }
+    
+    /**
+     * Merge sort
+     * 
+     * @param src Source array
+     * @param dest Destination array
+     * @param low Index of beginning element
+     * @param high Index of end element
+     * @param comp Comparator
+     */
+    public static void msort(Object[] src, Object[] dest, int low, int high,
+            Comparator comp) {
+        if(low < high) {
+            int center = (low + high) / 2;
+            msort(src, dest, low, center, comp);
+            msort(src, dest, center + 1, high, comp);
+            merge(src, dest, low, center + 1, high, comp);
+        }
+    }
+    
+    private static void merge(Object[] src, Object[] dest,
+            int low, int middle, int high, Comparator comp) {
+        int leftEnd = middle - 1;
+        int pos = low;
+        int numElements = high - low + 1;
+
+        while (low <= leftEnd && middle <= high) {
+            if (comp.compare(src[low], src[middle]) <= 0) {
+                dest[pos++] = src[low++];
+            } else {
+                dest[pos++] = src[middle++];
+            }
+        }
+
+        while (low <= leftEnd) {
+            dest[pos++] = src[low++];
+        }
+
+        while (middle <= high) {
+            dest[pos++] = src[middle++];
+        }
+
+        for (int i = 0; i < numElements; i++, high--) {
+            src[high] = dest[high];
+        }
+    }
+}
diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
new file mode 100644
index 0000000..88f6822
--- /dev/null
+++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.IndexBuffer;
+import static com.jme3.util.BufferUtils.*;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * 
+ * @author Lex (Aleksey Nikiforov)
+  */
+public class TangentBinormalGenerator {
+    
+    private static final float ZERO_TOLERANCE = 0.0000001f;
+    private static final Logger log = Logger.getLogger(
+            TangentBinormalGenerator.class.getName());
+    private static float toleranceAngle;
+    private static float toleranceDot;
+    
+    static {
+        setToleranceAngle(45);
+    }
+    
+    
+    private static class VertexInfo {
+        public final Vector3f position;
+        public final Vector3f normal;
+        public final ArrayList<Integer> indices = new ArrayList<Integer>();
+        
+        public VertexInfo(Vector3f position, Vector3f normal) {
+            this.position = position;
+            this.normal = normal;
+        }
+    }
+    
+    /** Collects all the triangle data for one vertex.
+     */
+    private static class VertexData {
+        public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>();
+        
+        public VertexData() { }
+    }
+    
+    /** Keeps track of tangent, binormal, and normal for one triangle.
+     */
+    public static class TriangleData {
+        public final Vector3f tangent;
+        public final Vector3f binormal;
+        public final Vector3f normal;
+        
+        public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
+            this.tangent = tangent;
+            this.binormal = binormal;
+            this.normal = normal;
+        }
+    }
+    
+    private static VertexData[] initVertexData(int size) {
+        VertexData[] vertices = new VertexData[size];
+        for (int i = 0; i < size; i++) {
+            vertices[i] = new VertexData();
+        }
+        return vertices;
+    }
+    
+    public static void generate(Mesh mesh) {
+        generate(mesh, true);
+    }
+    
+    public static void generate(Spatial scene) {
+        if (scene instanceof Node) {
+            Node node = (Node) scene;
+            for (Spatial child : node.getChildren()) {
+                generate(child);
+            }
+        } else {
+            Geometry geom = (Geometry) scene;
+            generate(geom.getMesh());
+        }
+    }
+    
+    public static void generate(Mesh mesh, boolean approxTangents) {
+        int[] index = new int[3];
+        Vector3f[] v = new Vector3f[3];
+        Vector2f[] t = new Vector2f[3];
+        for (int i = 0; i < 3; i++) {
+            v[i] = new Vector3f();
+            t[i] = new Vector2f();
+        }
+        
+        if (mesh.getBuffer(Type.Normal) == null) {
+            throw new IllegalArgumentException("The given mesh has no normal data!");
+        }
+        
+        VertexData[] vertices;
+        switch (mesh.getMode()) {
+            case Triangles:
+                vertices = processTriangles(mesh, index, v, t);
+                break;
+            case TriangleStrip:
+                vertices = processTriangleStrip(mesh, index, v, t);
+                break;
+            case TriangleFan:
+                vertices = processTriangleFan(mesh, index, v, t);
+                break;
+            default:
+                throw new UnsupportedOperationException(
+                        mesh.getMode() + " is not supported.");
+        }
+        
+        processTriangleData(mesh, vertices, approxTangents);
+
+        //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer
+        if (mesh.getBuffer(Type.BindPosePosition) != null) {
+            
+            VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+            if (tangents != null) {
+                VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+                bindTangents.setupData(Usage.CpuOnly,
+                        4,
+                        Format.Float,
+                        BufferUtils.clone(tangents.getData()));
+                
+                if (mesh.getBuffer(Type.BindPoseTangent) != null) {
+                    mesh.clearBuffer(Type.BindPoseTangent);
+                }
+                mesh.setBuffer(bindTangents);
+                tangents.setUsage(Usage.Stream);
+            }
+        }
+    }
+    
+    private static VertexData[] processTriangles(Mesh mesh,
+            int[] index, Vector3f[] v, Vector2f[] t) {
+        IndexBuffer indexBuffer = mesh.getIndexBuffer();
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        if (mesh.getBuffer(Type.TexCoord) == null) {
+            throw new IllegalArgumentException("Can only generate tangents for "
+                    + "meshes with texture coordinates");
+        }
+        
+        FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+        
+        VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+        
+        for (int i = 0; i < indexBuffer.size() / 3; i++) {
+            for (int j = 0; j < 3; j++) {
+                index[j] = indexBuffer.get(i * 3 + j);
+                populateFromBuffer(v[j], vertexBuffer, index[j]);
+                populateFromBuffer(t[j], textureBuffer, index[j]);
+            }
+            
+            TriangleData triData = processTriangle(index, v, t);
+            if (triData != null) {
+                vertices[index[0]].triangles.add(triData);
+                vertices[index[1]].triangles.add(triData);
+                vertices[index[2]].triangles.add(triData);
+            }
+        }
+        
+        return vertices;
+    }
+    
+    private static VertexData[] processTriangleStrip(Mesh mesh,
+            int[] index, Vector3f[] v, Vector2f[] t) {
+        IndexBuffer indexBuffer = mesh.getIndexBuffer();
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+        
+        VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+        
+        index[0] = indexBuffer.get(0);
+        index[1] = indexBuffer.get(1);
+        
+        populateFromBuffer(v[0], vertexBuffer, index[0]);
+        populateFromBuffer(v[1], vertexBuffer, index[1]);
+        
+        populateFromBuffer(t[0], textureBuffer, index[0]);
+        populateFromBuffer(t[1], textureBuffer, index[1]);
+        
+        for (int i = 2; i < indexBuffer.size(); i++) {
+            index[2] = indexBuffer.get(i);
+            BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]);
+            BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]);
+            
+            boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]);
+            TriangleData triData = processTriangle(index, v, t);
+            
+            if (triData != null && !isDegenerate) {
+                vertices[index[0]].triangles.add(triData);
+                vertices[index[1]].triangles.add(triData);
+                vertices[index[2]].triangles.add(triData);
+            }
+            
+            Vector3f vTemp = v[0];
+            v[0] = v[1];
+            v[1] = v[2];
+            v[2] = vTemp;
+            
+            Vector2f tTemp = t[0];
+            t[0] = t[1];
+            t[1] = t[2];
+            t[2] = tTemp;
+            
+            index[0] = index[1];
+            index[1] = index[2];
+        }
+        
+        return vertices;
+    }
+    
+    private static VertexData[] processTriangleFan(Mesh mesh,
+            int[] index, Vector3f[] v, Vector2f[] t) {
+        IndexBuffer indexBuffer = mesh.getIndexBuffer();
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+        
+        VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+        
+        index[0] = indexBuffer.get(0);
+        index[1] = indexBuffer.get(1);
+        
+        populateFromBuffer(v[0], vertexBuffer, index[0]);
+        populateFromBuffer(v[1], vertexBuffer, index[1]);
+        
+        populateFromBuffer(t[0], textureBuffer, index[0]);
+        populateFromBuffer(t[1], textureBuffer, index[1]);
+        
+        for (int i = 2; i < vertexBuffer.capacity() / 3; i++) {
+            index[2] = indexBuffer.get(i);
+            populateFromBuffer(v[2], vertexBuffer, index[2]);
+            populateFromBuffer(t[2], textureBuffer, index[2]);
+            
+            TriangleData triData = processTriangle(index, v, t);
+            if (triData != null) {
+                vertices[index[0]].triangles.add(triData);
+                vertices[index[1]].triangles.add(triData);
+                vertices[index[2]].triangles.add(triData);
+            }
+            
+            Vector3f vTemp = v[1];
+            v[1] = v[2];
+            v[2] = vTemp;
+            
+            Vector2f tTemp = t[1];
+            t[1] = t[2];
+            t[2] = tTemp;
+            
+            index[1] = index[2];
+        }
+        
+        return vertices;
+    }
+
+    // check if the area is greater than zero
+    private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) {
+        return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0;
+    }
+    
+    public static TriangleData processTriangle(int[] index,
+            Vector3f[] v, Vector2f[] t) {
+        Vector3f edge1 = new Vector3f();
+        Vector3f edge2 = new Vector3f();
+        Vector2f edge1uv = new Vector2f();
+        Vector2f edge2uv = new Vector2f();
+        
+        Vector3f tangent = new Vector3f();
+        Vector3f binormal = new Vector3f();
+        Vector3f normal = new Vector3f();
+        
+        t[1].subtract(t[0], edge1uv);
+        t[2].subtract(t[0], edge2uv);
+        float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x;
+        
+        boolean normalize = false;
+        if (Math.abs(det) < ZERO_TOLERANCE) {
+            log.log(Level.WARNING, "Colinear uv coordinates for triangle "
+                    + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], "
+                    + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]",
+                    new Object[]{index[0], index[1], index[2],
+                        t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y});
+            det = 1;
+            normalize = true;
+        }
+        
+        v[1].subtract(v[0], edge1);
+        v[2].subtract(v[0], edge2);
+        
+        tangent.set(edge1);
+        tangent.normalizeLocal();
+        binormal.set(edge2);
+        binormal.normalizeLocal();
+        
+        if (Math.abs(Math.abs(tangent.dot(binormal)) - 1)
+                < ZERO_TOLERANCE) {
+            log.log(Level.WARNING, "Vertices are on the same line "
+                    + "for triangle [{0}, {1}, {2}].",
+                    new Object[]{index[0], index[1], index[2]});
+        }
+        
+        float factor = 1 / det;
+        tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor;
+        tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor;
+        tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor;
+        if (normalize) {
+            tangent.normalizeLocal();
+        }
+        
+        binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor;
+        binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor;
+        binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor;
+        if (normalize) {
+            binormal.normalizeLocal();
+        }
+        
+        tangent.cross(binormal, normal);
+        normal.normalizeLocal();
+        
+        return new TriangleData(
+                tangent,
+                binormal,
+                normal);
+    }
+    
+    public static void setToleranceAngle(float angle) {
+        if (angle < 0 || angle > 179) {
+            throw new IllegalArgumentException(
+                    "The angle must be between 0 and 179 degrees.");
+        }
+        toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD);
+        toleranceAngle = angle;
+    }
+    
+    
+    private static boolean approxEqual(Vector3f u, Vector3f v) {
+        float tolerance = 1E-4f;
+        return (FastMath.abs(u.x - v.x) < tolerance) &&
+               (FastMath.abs(u.y - v.y) < tolerance) &&
+               (FastMath.abs(u.z - v.z) < tolerance);
+    }
+    
+    private static ArrayList<VertexInfo> linkVertices(Mesh mesh) {
+        ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>();
+        
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+        
+        Vector3f position = new Vector3f();
+        Vector3f normal = new Vector3f();
+        
+        final int size = vertexBuffer.capacity() / 3;
+        for (int i = 0; i < size; i++) {
+            
+            populateFromBuffer(position, vertexBuffer, i);
+            populateFromBuffer(normal, normalBuffer, i);
+            
+            boolean found = false;
+            
+            for (int j = 0; j < vertexMap.size(); j++) {
+                VertexInfo vertexInfo = vertexMap.get(j);
+                if (approxEqual(vertexInfo.position, position) &&
+                    approxEqual(vertexInfo.normal, normal))
+                {
+                    vertexInfo.indices.add(i);
+                    found = true;
+                    break;  
+                }
+            }
+            
+            if (!found) {
+                VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone());
+                vertexInfo.indices.add(i);
+                vertexMap.add(vertexInfo);
+            }
+        }
+        
+        return vertexMap;
+    }
+    
+    private static void processTriangleData(Mesh mesh, VertexData[] vertices,
+            boolean approxTangent)
+    {
+        ArrayList<VertexInfo> vertexMap = linkVertices(mesh);
+        
+        FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+        
+        FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4);
+//        FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3);
+
+        Vector3f tangent = new Vector3f();
+        Vector3f binormal = new Vector3f();
+        Vector3f normal = new Vector3f();
+        Vector3f givenNormal = new Vector3f();
+        
+        Vector3f tangentUnit = new Vector3f();
+        Vector3f binormalUnit = new Vector3f();
+        
+        for (int k = 0; k < vertexMap.size(); k++) {
+            float wCoord = -1;
+            
+            VertexInfo vertexInfo = vertexMap.get(k);
+            
+            givenNormal.set(vertexInfo.normal);
+            givenNormal.normalizeLocal();
+            
+            TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0);
+
+            // check tangent and binormal consistency
+            tangent.set(firstTriangle.tangent);
+            tangent.normalizeLocal();
+            binormal.set(firstTriangle.binormal);
+            binormal.normalizeLocal();
+            
+            for (int i : vertexInfo.indices) {
+                ArrayList<TriangleData> triangles = vertices[i].triangles;
+                
+                for (int j = 0; j < triangles.size(); j++) {
+                    TriangleData triangleData = triangles.get(j);
+
+                    tangentUnit.set(triangleData.tangent);
+                    tangentUnit.normalizeLocal();
+                    if (tangent.dot(tangentUnit) < toleranceDot) {
+                        log.log(Level.WARNING,
+                                "Angle between tangents exceeds tolerance "
+                                + "for vertex {0}.", i);
+                        break;
+                    }
+
+                    if (!approxTangent) {
+                        binormalUnit.set(triangleData.binormal);
+                        binormalUnit.normalizeLocal();
+                        if (binormal.dot(binormalUnit) < toleranceDot) {
+                            log.log(Level.WARNING,
+                                    "Angle between binormals exceeds tolerance "
+                                    + "for vertex {0}.", i);
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            
+            // find average tangent
+            tangent.set(0, 0, 0);
+            binormal.set(0, 0, 0);
+            
+            int triangleCount = 0;
+            for (int i : vertexInfo.indices) {
+                ArrayList<TriangleData> triangles = vertices[i].triangles;
+                triangleCount += triangles.size();
+                
+                boolean flippedNormal = false;
+                for (int j = 0; j < triangles.size(); j++) {
+                    TriangleData triangleData = triangles.get(j);
+                    tangent.addLocal(triangleData.tangent);
+                    binormal.addLocal(triangleData.binormal);
+
+                    if (givenNormal.dot(triangleData.normal) < 0) {
+                        flippedNormal = true;
+                    }
+                }
+                if (flippedNormal /*&& approxTangent*/) {
+                    // Generated normal is flipped for this vertex,
+                    // so binormal = normal.cross(tangent) will be flipped in the shader
+    //                log.log(Level.WARNING,
+    //                        "Binormal is flipped for vertex {0}.", i);
+
+                    wCoord = 1;
+                }
+            }
+
+            
+            int blameVertex = vertexInfo.indices.get(0);
+            
+            if (tangent.length() < ZERO_TOLERANCE) {
+                log.log(Level.WARNING,
+                        "Shared tangent is zero for vertex {0}.", blameVertex);
+                // attempt to fix from binormal
+                if (binormal.length() >= ZERO_TOLERANCE) {
+                    binormal.cross(givenNormal, tangent);
+                    tangent.normalizeLocal();
+                } // if all fails use the tangent from the first triangle
+                else {
+                    tangent.set(firstTriangle.tangent);
+                }
+            } else {
+                tangent.divideLocal(triangleCount);
+            }
+
+            tangentUnit.set(tangent);
+            tangentUnit.normalizeLocal();
+            if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1)
+                    < ZERO_TOLERANCE) {
+                log.log(Level.WARNING,
+                        "Normal and tangent are parallel for vertex {0}.", blameVertex);
+            }
+
+
+            if (!approxTangent) {
+                if (binormal.length() < ZERO_TOLERANCE) {
+                    log.log(Level.WARNING,
+                            "Shared binormal is zero for vertex {0}.", blameVertex);
+                    // attempt to fix from tangent
+                    if (tangent.length() >= ZERO_TOLERANCE) {
+                        givenNormal.cross(tangent, binormal);
+                        binormal.normalizeLocal();
+                    } // if all fails use the binormal from the first triangle
+                    else {
+                        binormal.set(firstTriangle.binormal);
+                    }
+                } else {
+                    binormal.divideLocal(triangleCount);
+                }
+
+                binormalUnit.set(binormal);
+                binormalUnit.normalizeLocal();
+                if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1)
+                        < ZERO_TOLERANCE) {
+                    log.log(Level.WARNING,
+                            "Normal and binormal are parallel for vertex {0}.", blameVertex);
+                }
+
+                if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1)
+                        < ZERO_TOLERANCE) {
+                    log.log(Level.WARNING,
+                            "Tangent and binormal are parallel for vertex {0}.", blameVertex);
+                }
+            }
+            
+            for (int i : vertexInfo.indices) {
+                if (approxTangent) {
+                    // This calculation ensures that normal and tagent have a 90 degree angle.
+                    // Removing this will lead to visual artifacts.
+                    givenNormal.cross(tangent, binormal);
+                    binormal.cross(givenNormal, tangent);
+
+                    tangent.normalizeLocal();
+
+                    tangents.put((i * 4), tangent.x);
+                    tangents.put((i * 4) + 1, tangent.y);
+                    tangents.put((i * 4) + 2, tangent.z);
+                    tangents.put((i * 4) + 3, wCoord);
+                } else {
+                    tangents.put((i * 4), tangent.x);
+                    tangents.put((i * 4) + 1, tangent.y);
+                    tangents.put((i * 4) + 2, tangent.z);
+                    tangents.put((i * 4) + 3, wCoord);
+
+                    //setInBuffer(binormal, binormals, i);
+                }
+            }
+        }
+        
+        mesh.setBuffer(Type.Tangent, 4, tangents);
+//        if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals);
+    }
+    
+    public static Mesh genTbnLines(Mesh mesh, float scale) {
+        if (mesh.getBuffer(Type.Tangent) == null) {
+            return genNormalLines(mesh, scale);
+        } else {
+            return genTangentLines(mesh, scale);
+        }
+    }
+    
+    public static Mesh genNormalLines(Mesh mesh, float scale) {
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+        
+        ColorRGBA originColor = ColorRGBA.White;
+        ColorRGBA normalColor = ColorRGBA.Blue;
+        
+        Mesh lineMesh = new Mesh();
+        lineMesh.setMode(Mesh.Mode.Lines);
+        
+        Vector3f origin = new Vector3f();
+        Vector3f point = new Vector3f();
+        
+        FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2);
+        FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2);
+        
+        for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+            populateFromBuffer(origin, vertexBuffer, i);
+            populateFromBuffer(point, normalBuffer, i);
+            
+            int index = i * 2;
+            
+            setInBuffer(origin, lineVertex, index);
+            setInBuffer(originColor, lineColor, index);
+            
+            point.multLocal(scale);
+            point.addLocal(origin);
+            setInBuffer(point, lineVertex, index + 1);
+            setInBuffer(normalColor, lineColor, index + 1);
+        }
+        
+        lineMesh.setBuffer(Type.Position, 3, lineVertex);
+        lineMesh.setBuffer(Type.Color, 4, lineColor);
+        
+        lineMesh.setStatic();
+        lineMesh.setInterleaved();
+        return lineMesh;
+    }
+    
+    private static Mesh genTangentLines(Mesh mesh, float scale) {
+        FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+        FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+        FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData();
+        
+        FloatBuffer binormalBuffer = null;
+        if (mesh.getBuffer(Type.Binormal) != null) {
+            binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData();
+        }
+        
+        ColorRGBA originColor = ColorRGBA.White;
+        ColorRGBA tangentColor = ColorRGBA.Red;
+        ColorRGBA binormalColor = ColorRGBA.Green;
+        ColorRGBA normalColor = ColorRGBA.Blue;
+        
+        Mesh lineMesh = new Mesh();
+        lineMesh.setMode(Mesh.Mode.Lines);
+        
+        Vector3f origin = new Vector3f();
+        Vector3f point = new Vector3f();
+        Vector3f tangent = new Vector3f();
+        Vector3f normal = new Vector3f();
+        
+        IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.capacity() / 3 * 6);
+        FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 4);
+        FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 4);
+        
+        boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4;
+        float tangentW = 1;
+        
+        for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+            populateFromBuffer(origin, vertexBuffer, i);
+            populateFromBuffer(normal, normalBuffer, i);
+            
+            if (hasParity) {
+                tangent.x = tangentBuffer.get(i * 4);
+                tangent.y = tangentBuffer.get(i * 4 + 1);
+                tangent.z = tangentBuffer.get(i * 4 + 2);
+                tangentW = tangentBuffer.get(i * 4 + 3);
+            } else {
+                populateFromBuffer(tangent, tangentBuffer, i);
+            }
+            
+            int index = i * 4;
+            
+            int id = i * 6;
+            lineIndex.put(id, index);
+            lineIndex.put(id + 1, index + 1);
+            lineIndex.put(id + 2, index);
+            lineIndex.put(id + 3, index + 2);
+            lineIndex.put(id + 4, index);
+            lineIndex.put(id + 5, index + 3);
+            
+            setInBuffer(origin, lineVertex, index);
+            setInBuffer(originColor, lineColor, index);
+            
+            point.set(tangent);
+            point.multLocal(scale);
+            point.addLocal(origin);
+            setInBuffer(point, lineVertex, index + 1);
+            setInBuffer(tangentColor, lineColor, index + 1);
+
+            // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w
+
+            if (binormalBuffer == null) {
+                normal.cross(tangent, point);
+                point.multLocal(-tangentW);
+                point.normalizeLocal();
+            } else {
+                populateFromBuffer(point, binormalBuffer, i);
+            }
+            
+            point.multLocal(scale);
+            point.addLocal(origin);
+            setInBuffer(point, lineVertex, index + 2);
+            setInBuffer(binormalColor, lineColor, index + 2);
+            
+            point.set(normal);
+            point.multLocal(scale);
+            point.addLocal(origin);
+            setInBuffer(point, lineVertex, index + 3);
+            setInBuffer(normalColor, lineColor, index + 3);
+        }
+        
+        lineMesh.setBuffer(Type.Index, 1, lineIndex);
+        lineMesh.setBuffer(Type.Position, 3, lineVertex);
+        lineMesh.setBuffer(Type.Color, 4, lineColor);
+        
+        lineMesh.setStatic();
+        lineMesh.setInterleaved();
+        return lineMesh;
+    }
+}
diff --git a/engine/src/core/com/jme3/util/TempVars.java b/engine/src/core/com/jme3/util/TempVars.java
new file mode 100644
index 0000000..2fdea36
--- /dev/null
+++ b/engine/src/core/com/jme3/util/TempVars.java
@@ -0,0 +1,221 @@
+/*

+ * Copyright (c) 2009-2010 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;

+

+import com.jme3.collision.bih.BIHNode.BIHStackData;

+import com.jme3.math.*;

+import com.jme3.scene.Spatial;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+import java.util.ArrayList;

+

+/**

+ * Temporary variables assigned to each thread. Engine classes may access

+ * these temp variables with TempVars.get(), all retrieved TempVars

+ * instances must be returned via TempVars.release().

+ * This returns an available instance of the TempVar class ensuring this 

+ * particular instance is never used elsewhere in the mean time.

+ */

+public class TempVars {

+

+    /**

+     * Allow X instances of TempVars in a single thread.

+     */

+    private static final int STACK_SIZE = 5;

+

+    /**

+     * <code>TempVarsStack</code> contains a stack of TempVars.

+     * Every time TempVars.get() is called, a new entry is added to the stack,

+     * and the index incremented.

+     * When TempVars.release() is called, the entry is checked against

+     * the current instance and  then the index is decremented.

+     */

+    private static class TempVarsStack {

+

+        int index = 0;

+        TempVars[] tempVars = new TempVars[STACK_SIZE];

+    }

+    /**

+     * ThreadLocal to store a TempVarsStack for each thread.

+     * This ensures each thread has a single TempVarsStack that is

+     * used only in method calls in that thread.

+     */

+    private static final ThreadLocal<TempVarsStack> varsLocal = new ThreadLocal<TempVarsStack>() {

+

+        @Override

+        public TempVarsStack initialValue() {

+            return new TempVarsStack();

+        }

+    };

+    /**

+     * This instance of TempVars has been retrieved but not released yet.

+     */

+    private boolean isUsed = false;

+

+    private TempVars() {

+    }

+

+    /**

+     * Acquire an instance of the TempVar class.

+     * You have to release the instance after use by calling the 

+     * release() method. 

+     * If more than STACK_SIZE (currently 5) instances are requested 

+     * in a single thread then an ArrayIndexOutOfBoundsException will be thrown.

+     * 

+     * @return A TempVar instance

+     */

+    public static TempVars get() {

+        TempVarsStack stack = varsLocal.get();

+

+        TempVars instance = stack.tempVars[stack.index];

+

+        if (instance == null) {

+            // Create new

+            instance = new TempVars();

+

+            // Put it in there

+            stack.tempVars[stack.index] = instance;

+        }

+

+        stack.index++;

+

+        instance.isUsed = true;

+

+        return instance;

+    }

+

+    /**

+     * Releases this instance of TempVars.

+     * Once released, the contents of the TempVars are undefined.

+     * The TempVars must be released in the opposite order that they are retrieved,

+     * e.g. Acquiring vars1, then acquiring vars2, vars2 MUST be released 

+     * first otherwise an exception will be thrown.

+     */

+    public void release() {

+        if (!isUsed) {

+            throw new IllegalStateException("This instance of TempVars was already released!");

+        }

+

+        isUsed = false;

+

+        TempVarsStack stack = varsLocal.get();

+

+        // Return it to the stack

+        stack.index--;

+

+        // Check if it is actually there

+        if (stack.tempVars[stack.index] != this) {

+            throw new IllegalStateException("An instance of TempVars has not been released in a called method!");

+        }

+    }

+    /**

+     * For interfacing with OpenGL in Renderer.

+     */

+    public final IntBuffer intBuffer1 = BufferUtils.createIntBuffer(1);

+    public final IntBuffer intBuffer16 = BufferUtils.createIntBuffer(16);

+    public final FloatBuffer floatBuffer16 = BufferUtils.createFloatBuffer(16);

+    /**

+     * Skinning buffers

+     */

+    public final float[] skinPositions = new float[512 * 3];

+    public final float[] skinNormals = new float[512 * 3];

+     //tangent buffer as 4 components by elements

+    public final float[] skinTangents = new float[512 * 4];

+    /**

+     * Fetching triangle from mesh

+     */

+    public final Triangle triangle = new Triangle();

+    /**

+     * Color

+     */

+    public final ColorRGBA color = new ColorRGBA();

+    /**

+     * General vectors.

+     */

+    public final Vector3f vect1 = new Vector3f();

+    public final Vector3f vect2 = new Vector3f();

+    public final Vector3f vect3 = new Vector3f();

+    public final Vector3f vect4 = new Vector3f();

+    public final Vector3f vect5 = new Vector3f();

+    public final Vector3f vect6 = new Vector3f();

+    public final Vector3f vect7 = new Vector3f();

+    //seems the maximum number of vector used is 7 in com.jme3.bounding.java

+    public final Vector3f vect8 = new Vector3f();

+    public final Vector3f vect9 = new Vector3f();

+    public final Vector3f vect10 = new Vector3f();

+    public final Vector4f vect4f = new Vector4f();

+    public final Vector3f[] tri = {new Vector3f(),

+        new Vector3f(),

+        new Vector3f()};

+    /**

+     * 2D vector

+     */

+    public final Vector2f vect2d = new Vector2f();

+    public final Vector2f vect2d2 = new Vector2f();

+    /**

+     * General matrices.

+     */

+    public final Matrix3f tempMat3 = new Matrix3f();

+    public final Matrix4f tempMat4 = new Matrix4f();

+    public final Matrix4f tempMat42 = new Matrix4f();    

+    /**

+     * General quaternions.

+     */

+    public final Quaternion quat1 = new Quaternion();

+    public final Quaternion quat2 = new Quaternion();

+    /**

+     * Eigen

+     */

+    public final Eigen3f eigen = new Eigen3f();

+    /**

+     * Plane

+     */

+    public final Plane plane = new Plane();

+    /**

+     * BoundingBox ray collision

+     */

+    public final float[] fWdU = new float[3];

+    public final float[] fAWdU = new float[3];

+    public final float[] fDdU = new float[3];

+    public final float[] fADdU = new float[3];

+    public final float[] fAWxDdU = new float[3];

+    /**

+     * Maximum tree depth .. 32 levels??

+     */

+    public final Spatial[] spatialStack = new Spatial[32];

+    public final float[] matrixWrite = new float[16];

+    /**

+     * BIHTree

+     */

+    public final float[] bihSwapTmp = new float[9];

+    public final ArrayList<BIHStackData> bihStack = new ArrayList<BIHStackData>();

+}

diff --git a/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java
new file mode 100644
index 0000000..6dfa6b8
--- /dev/null
+++ b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java
@@ -0,0 +1,92 @@
+package com.jme3.util.blockparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BlockLanguageParser {
+    
+    private Reader reader;
+    private ArrayList<Statement> statementStack = new ArrayList<Statement>();
+    private Statement lastStatement;
+    private int lineNumber = 1;
+    
+    private BlockLanguageParser(){
+    }
+    
+    private void reset(){
+        statementStack.clear();
+        statementStack.add(new Statement(0, "<root>"));
+        lastStatement = null;
+        lineNumber = 1;
+    }
+    
+    private void pushStatement(StringBuilder buffer){
+        String content = buffer.toString().trim();
+        if (content.length() > 0){
+            // push last statement onto the list
+            lastStatement = new Statement(lineNumber, content);
+
+            Statement parent = statementStack.get(statementStack.size()-1);
+            parent.addStatement(lastStatement);
+
+            buffer.setLength(0);
+        }
+    }
+    
+    private void load(InputStream in) throws IOException{
+        reset();
+        
+        reader = new InputStreamReader(in);
+        
+        StringBuilder buffer = new StringBuilder();
+        boolean insideComment = false;
+        char lastChar = '\0';
+        
+        while (true){
+            int ci = reader.read();
+            char c = (char) ci;
+            if (c == '\r'){
+                continue;
+            }
+            if (insideComment && c == '\n'){
+                insideComment = false;
+            }else if (c == '/' && lastChar == '/'){
+                buffer.deleteCharAt(buffer.length()-1);
+                insideComment = true;
+                pushStatement(buffer);
+                lastChar = '\0';
+            }else if (!insideComment){
+                if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){
+                    pushStatement(buffer);
+                    lastChar = '\0';
+                    if (c == '{'){
+                        // push last statement onto the stack
+                        statementStack.add(lastStatement);
+                        continue;
+                    }else if (c == '}'){
+                        // pop statement from stack
+                        statementStack.remove(statementStack.size()-1);
+                        continue;
+                    }else if (c == '\n'){
+                        lineNumber++;
+                    }else if (ci == -1){
+                        break;
+                    }
+                }else{
+                    buffer.append(c);
+                    lastChar = c;
+                }
+            }
+        }
+    }
+    
+    public static List<Statement> parse(InputStream in) throws IOException {
+        BlockLanguageParser parser = new BlockLanguageParser();
+        parser.load(in);
+        return parser.statementStack.get(0).getContents();
+    }
+}
diff --git a/engine/src/core/com/jme3/util/blockparser/Statement.java b/engine/src/core/com/jme3/util/blockparser/Statement.java
new file mode 100644
index 0000000..d1309ad
--- /dev/null
+++ b/engine/src/core/com/jme3/util/blockparser/Statement.java
@@ -0,0 +1,61 @@
+package com.jme3.util.blockparser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Statement {
+    
+    private int lineNumber;
+    private String line;
+    private List<Statement> contents = new ArrayList<Statement>();
+
+    Statement(int lineNumber, String line) {
+        this.lineNumber = lineNumber;
+        this.line = line;
+    }
+    
+    void addStatement(Statement statement){
+//        if (contents == null){
+//            contents = new ArrayList<Statement>();
+//        }
+        contents.add(statement);
+    }
+
+    public int getLineNumber(){
+        return lineNumber;
+    }
+    
+    public String getLine() {
+        return line;
+    }
+
+    public List<Statement> getContents() {
+        return contents;
+    }
+    
+    private String getIndent(int indent){
+        return "                               ".substring(0, indent);
+    }
+    
+    private String toString(int indent){
+        StringBuilder sb = new StringBuilder();
+        sb.append(getIndent(indent));
+        sb.append(line);
+        if (contents != null){
+            sb.append(" {\n");
+            for (Statement statement : contents){
+                sb.append(statement.toString(indent+4));
+                sb.append("\n");
+            }
+            sb.append(getIndent(indent));
+            sb.append("}");
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    public String toString(){
+        return toString(0);
+    }
+    
+}
diff --git a/engine/src/core/com/jme3/util/xml/SAXUtil.java b/engine/src/core/com/jme3/util/xml/SAXUtil.java
new file mode 100644
index 0000000..1ac4936
--- /dev/null
+++ b/engine/src/core/com/jme3/util/xml/SAXUtil.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 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.xml;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Utility methods for parsing XML data using SAX.
+ */
+public final class SAXUtil {
+
+    /**
+     * Parses an integer from a string, if the string is null returns
+     * def.
+     * 
+     * @param i
+     * @param def
+     * @return
+     * @throws SAXException 
+     */
+    public static int parseInt(String i, int def) throws SAXException{
+        if (i == null)
+            return def;
+        else{
+            try {
+                return Integer.parseInt(i);
+            } catch (NumberFormatException ex){
+                throw new SAXException("Expected an integer, got '"+i+"'");
+            }
+        }
+    }
+
+    public static int parseInt(String i) throws SAXException{
+        if (i == null)
+            throw new SAXException("Expected an integer");
+        else{
+            try {
+                return Integer.parseInt(i);
+            } catch (NumberFormatException ex){
+                throw new SAXException("Expected an integer, got '"+i+"'");
+            }
+        }
+    }
+
+    public static float parseFloat(String f, float def) throws SAXException{
+        if (f == null)
+            return def;
+        else{
+            try {
+                return Float.parseFloat(f);
+            } catch (NumberFormatException ex){
+                throw new SAXException("Expected a decimal, got '"+f+"'");
+            }
+        }
+    }
+
+    public static float parseFloat(String f) throws SAXException{
+        if (f == null)
+            throw new SAXException("Expected a decimal");
+        else{
+            try {
+                return Float.parseFloat(f);
+            } catch (NumberFormatException ex){
+                throw new SAXException("Expected a decimal, got '"+f+"'");
+            }
+        }
+    }
+
+    public static boolean parseBool(String bool, boolean def) throws SAXException{
+        if (bool == null || bool.equals(""))
+            return def;
+        else
+            return Boolean.valueOf(bool); 
+        //else
+        //else
+        //    throw new SAXException("Expected a boolean, got'"+bool+"'");
+    }
+
+    public static String parseString(String str, String def){
+        if (str == null)
+            return def;
+        else
+            return str;
+    }
+
+    public static String parseString(String str) throws SAXException{
+        if (str == null)
+            throw new SAXException("Expected a string");
+        else
+            return str;
+    }
+
+    public static Vector3f parseVector3(Attributes attribs) throws SAXException{
+        float x = parseFloat(attribs.getValue("x"));
+        float y = parseFloat(attribs.getValue("y"));
+        float z = parseFloat(attribs.getValue("z"));
+        return new Vector3f(x,y,z);
+    }
+
+    public static ColorRGBA parseColor(Attributes attribs) throws SAXException{
+        float r = parseFloat(attribs.getValue("r"));
+        float g = parseFloat(attribs.getValue("g"));
+        float b = parseFloat(attribs.getValue("b"));
+        return new ColorRGBA(r, g, b, 1f);
+    }
+
+}
diff --git a/engine/src/desktop/com/jme3/app/AppletHarness.java b/engine/src/desktop/com/jme3/app/AppletHarness.java
new file mode 100644
index 0000000..7e14c39
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/AppletHarness.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2009-2010 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.app;
+
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeCanvasContext;
+import com.jme3.system.JmeSystem;
+import java.applet.Applet;
+import java.awt.Canvas;
+import java.awt.Graphics;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * @author Kirill Vainer
+ */
+public class AppletHarness extends Applet {
+
+    public static final HashMap<Application, Applet> appToApplet
+                         = new HashMap<Application, Applet>();
+
+    private JmeCanvasContext context;
+    private Canvas canvas;
+    private Application app;
+
+    private String appClass;
+    private URL appCfg = null;
+    private URL assetCfg = null;
+
+    public static Applet getApplet(Application app){
+        return appToApplet.get(app);
+    }
+
+    private void createCanvas(){
+        AppSettings settings = new AppSettings(true);
+
+        // load app cfg
+        if (appCfg != null){
+            InputStream in = null;
+            try {
+                in = appCfg.openStream();
+                settings.load(in);
+                in.close();
+            } catch (IOException ex){
+                // Called before application has been created ....
+                // Display error message through AWT
+                JOptionPane.showMessageDialog(this, "An error has occured while "
+                                                  + "loading applet configuration"
+                                                  + ex.getMessage(),
+                                              "jME3 Applet",
+                                              JOptionPane.ERROR_MESSAGE);
+                ex.printStackTrace();
+            } finally {
+                if (in != null)
+                    try {
+                    in.close();
+                } catch (IOException ex) {
+                }
+            }
+        }
+
+        if (assetCfg != null){
+            settings.putString("AssetConfigURL", assetCfg.toString());
+        }
+
+        settings.setWidth(getWidth());
+        settings.setHeight(getHeight());
+
+        JmeSystem.setLowPermissions(true);
+
+        try{
+            Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
+            app = clazz.newInstance();
+        }catch (ClassNotFoundException ex){
+            ex.printStackTrace();
+        }catch (InstantiationException ex){
+            ex.printStackTrace();
+        }catch (IllegalAccessException ex){
+            ex.printStackTrace();
+        }
+
+        appToApplet.put(app, this);
+        app.setSettings(settings);
+        app.createCanvas();
+
+        context = (JmeCanvasContext) app.getContext();
+        canvas = context.getCanvas();
+        canvas.setSize(getWidth(), getHeight());
+
+        add(canvas);
+        app.startCanvas();
+    }
+
+    @Override
+    public final void update(Graphics g) {
+        canvas.setSize(getWidth(), getHeight());
+    }
+
+    @Override
+    public void init(){
+        appClass = getParameter("AppClass");
+        if (appClass == null)
+            throw new RuntimeException("The required parameter AppClass isn't specified!");
+
+        try {
+            appCfg = new URL(getParameter("AppSettingsURL"));
+        } catch (MalformedURLException ex) {
+            System.out.println(ex.getMessage());
+            appCfg = null;
+        }
+
+        try {
+            assetCfg = new URL(getParameter("AssetConfigURL"));
+        } catch (MalformedURLException ex){
+            System.out.println(ex.getMessage());
+            assetCfg = getClass().getResource("/com/jme3/asset/Desktop.cfg");
+        }
+
+        createCanvas();
+        System.out.println("applet:init");
+    }
+
+    @Override
+    public void start(){
+        context.setAutoFlushFrames(true);
+        System.out.println("applet:start");
+    }
+
+    @Override
+    public void stop(){
+        context.setAutoFlushFrames(false);
+        System.out.println("applet:stop");
+    }
+
+    @Override
+    public void destroy(){
+        System.out.println("applet:destroyStart");
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                removeAll();
+                System.out.println("applet:destroyRemoved");
+            }
+        });
+        app.stop(true);
+        System.out.println("applet:destroyDone");
+
+        appToApplet.remove(app);
+    }
+
+}
diff --git a/engine/src/desktop/com/jme3/app/Monkey.png b/engine/src/desktop/com/jme3/app/Monkey.png
new file mode 100644
index 0000000..e1c8c3d
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/Monkey.png
Binary files differ
diff --git a/engine/src/desktop/com/jme3/app/SettingsDialog.java b/engine/src/desktop/com/jme3/app/SettingsDialog.java
new file mode 100644
index 0000000..4402604
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/SettingsDialog.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.system.AppSettings;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.BackingStoreException;
+import javax.swing.*;
+
+/**
+ * <code>PropertiesDialog</code> provides an interface to make use of the
+ * <code>GameSettings</code> class. The <code>GameSettings</code> object
+ * is still created by the client application, and passed during construction.
+ * 
+ * @see com.jme.system.GameSettings
+ * @author Mark Powell
+ * @author Eric Woroshow
+ * @author Joshua Slack - reworked for proper use of GL commands.
+ * @version $Id: LWJGLPropertiesDialog.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public final class SettingsDialog extends JDialog {
+
+    public static interface SelectionListener {
+
+        public void onSelection(int selection);
+    }
+    private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName());
+    private static final long serialVersionUID = 1L;
+    public static final int NO_SELECTION = 0,
+            APPROVE_SELECTION = 1,
+            CANCEL_SELECTION = 2;
+    // connection to properties file.
+    private final AppSettings source;
+    // Title Image
+    private URL imageFile = null;
+    // Array of supported display modes
+    private DisplayMode[] modes = null;
+    // Array of windowed resolutions
+    private String[] windowedResolutions = {"320 x 240", "640 x 480", "800 x 600",
+        "1024 x 768", "1152 x 864", "1280 x 720"};
+    // UI components
+    private JCheckBox vsyncBox = null;
+    private JCheckBox fullscreenBox = null;
+    private JComboBox displayResCombo = null;
+    private JComboBox colorDepthCombo = null;
+    private JComboBox displayFreqCombo = null;
+//    private JComboBox rendererCombo = null;
+    private JComboBox antialiasCombo = null;
+    private JLabel icon = null;
+    private int selection = 0;
+    private SelectionListener selectionListener = null;
+
+    /**
+     * Constructor for the <code>PropertiesDialog</code>. Creates a
+     * properties dialog initialized for the primary display.
+     *
+     * @param source
+     *            the <code>AppSettings</code> object to use for working with
+     *            the properties file.
+     * @param imageFile
+     *            the image file to use as the title of the dialog;
+     *            <code>null</code> will result in to image being displayed
+     * @throws NullPointerException
+     *             if the source is <code>null</code>
+     */
+    public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
+        this(source, getURL(imageFile), loadSettings);
+    }
+
+    /**
+     * Constructor for the <code>PropertiesDialog</code>. Creates a
+     * properties dialog initialized for the primary display.
+     * 
+     * @param source
+     *            the <code>GameSettings</code> object to use for working with
+     *            the properties file.
+     * @param imageFile
+     *            the image file to use as the title of the dialog;
+     *            <code>null</code> will result in to image being displayed
+     * @param loadSettings 
+     * @throws JmeException
+     *             if the source is <code>null</code>
+     */
+    public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
+        if (source == null) {
+            throw new NullPointerException("Settings source cannot be null");
+        }
+
+        this.source = source;
+        this.imageFile = imageFile;
+
+//        setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+        setModal(true);
+
+        AppSettings registrySettings = new AppSettings(true);
+
+        String appTitle;
+        if(source.getTitle()!=null){
+            appTitle = source.getTitle();
+        }else{
+           appTitle = registrySettings.getTitle();
+        }
+        try {
+            registrySettings.load(appTitle);
+        } catch (BackingStoreException ex) {
+            logger.log(Level.WARNING,
+                    "Failed to load settings", ex);
+        }
+
+        if (loadSettings) {
+            source.copyFrom(registrySettings);
+        } else if(!registrySettings.isEmpty()) {
+            source.mergeFrom(registrySettings);
+        }
+
+        GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+
+        modes = device.getDisplayModes();
+        Arrays.sort(modes, new DisplayModeSorter());
+
+        createUI();
+    }
+
+    public void setSelectionListener(SelectionListener sl) {
+        this.selectionListener = sl;
+    }
+
+    public int getUserSelection() {
+        return selection;
+    }
+
+    private void setUserSelection(int selection) {
+        this.selection = selection;
+        selectionListener.onSelection(selection);
+    }
+
+    /**
+     * <code>setImage</code> sets the background image of the dialog.
+     * 
+     * @param image
+     *            <code>String</code> representing the image file.
+     */
+    public void setImage(String image) {
+        try {
+            URL file = new URL("file:" + image);
+            setImage(file);
+            // We can safely ignore the exception - it just means that the user
+            // gave us a bogus file
+        } catch (MalformedURLException e) {
+        }
+    }
+
+    /**
+     * <code>setImage</code> sets the background image of this dialog.
+     * 
+     * @param image
+     *            <code>URL</code> pointing to the image file.
+     */
+    public void setImage(URL image) {
+        icon.setIcon(new ImageIcon(image));
+        pack(); // Resize to accomodate the new image
+        setLocationRelativeTo(null); // put in center
+    }
+
+    /**
+     * <code>showDialog</code> sets this dialog as visble, and brings it to
+     * the front.
+     */
+    public void showDialog() {
+        setLocationRelativeTo(null);
+        setVisible(true);
+        toFront();
+    }
+
+    /**
+     * <code>init</code> creates the components to use the dialog.
+     */
+    private void createUI() {
+        try {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        } catch (Exception e) {
+            logger.warning("Could not set native look and feel.");
+        }
+
+        addWindowListener(new WindowAdapter() {
+
+            public void windowClosing(WindowEvent e) {
+                setUserSelection(CANCEL_SELECTION);
+                dispose();
+            }
+        });
+
+        if (source.getIcons() != null) {
+            safeSetIconImages( (List<BufferedImage>) Arrays.asList((BufferedImage[]) source.getIcons()) );
+        }
+
+        setTitle("Select Display Settings");
+
+        // The panels...
+        JPanel mainPanel = new JPanel();
+        JPanel centerPanel = new JPanel();
+        JPanel optionsPanel = new JPanel();
+        JPanel buttonPanel = new JPanel();
+        // The buttons...
+        JButton ok = new JButton("Ok");
+        JButton cancel = new JButton("Cancel");
+
+        icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
+
+        mainPanel.setLayout(new BorderLayout());
+
+        centerPanel.setLayout(new BorderLayout());
+
+        KeyListener aListener = new KeyAdapter() {
+
+            public void keyPressed(KeyEvent e) {
+                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+                    if (verifyAndSaveCurrentSelection()) {
+                        setUserSelection(APPROVE_SELECTION);
+                        dispose();
+                    }
+                }
+                else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                    setUserSelection(CANCEL_SELECTION);
+                    dispose();
+                }
+            }
+        };
+
+        displayResCombo = setUpResolutionChooser();
+        displayResCombo.addKeyListener(aListener);
+        colorDepthCombo = new JComboBox();
+        colorDepthCombo.addKeyListener(aListener);
+        displayFreqCombo = new JComboBox();
+        displayFreqCombo.addKeyListener(aListener);
+        antialiasCombo = new JComboBox();
+        antialiasCombo.addKeyListener(aListener);
+        fullscreenBox = new JCheckBox("Fullscreen?");
+        fullscreenBox.setSelected(source.isFullscreen());
+        fullscreenBox.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                updateResolutionChoices();
+            }
+        });
+        vsyncBox = new JCheckBox("VSync?");
+        vsyncBox.setSelected(source.isVSync());
+//        rendererCombo = setUpRendererChooser();
+//        rendererCombo.addKeyListener(aListener);
+
+       
+
+        updateResolutionChoices();
+        updateAntialiasChoices();
+        displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight());
+        colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp");
+
+        optionsPanel.add(displayResCombo);
+        optionsPanel.add(colorDepthCombo);
+        optionsPanel.add(displayFreqCombo);
+        optionsPanel.add(antialiasCombo);
+        optionsPanel.add(fullscreenBox);
+        optionsPanel.add(vsyncBox);
+//        optionsPanel.add(rendererCombo);
+
+        // Set the button action listeners. Cancel disposes without saving, OK
+        // saves.
+        ok.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                if (verifyAndSaveCurrentSelection()) {
+                    setUserSelection(APPROVE_SELECTION);
+                    dispose();
+                }
+            }
+        });
+
+        cancel.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                setUserSelection(CANCEL_SELECTION);
+                dispose();
+            }
+        });
+
+        buttonPanel.add(ok);
+        buttonPanel.add(cancel);
+
+        if (icon != null) {
+            centerPanel.add(icon, BorderLayout.NORTH);
+        }
+        centerPanel.add(optionsPanel, BorderLayout.SOUTH);
+
+        mainPanel.add(centerPanel, BorderLayout.CENTER);
+        mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+
+        this.getContentPane().add(mainPanel);
+
+        pack();
+    }
+
+    /* Access JDialog.setIconImages by reflection in case we're running on JRE < 1.6 */
+    private void safeSetIconImages(List<? extends Image> icons) {
+        try {
+            // Due to Java bug 6445278, we try to set icon on our shared owner frame first.
+            // Otherwise, our alt-tab icon will be the Java default under Windows.
+            Window owner = getOwner();
+            if (owner != null) {
+                Method setIconImages = owner.getClass().getMethod("setIconImages", List.class);
+                setIconImages.invoke(owner, icons);
+                return;
+            }
+
+            Method setIconImages = getClass().getMethod("setIconImages", List.class);
+            setIconImages.invoke(this, icons);
+        } catch (Exception e) {
+            return;
+        }
+    }
+
+    /**
+     * <code>verifyAndSaveCurrentSelection</code> first verifies that the
+     * display mode is valid for this system, and then saves the current
+     * selection as a properties.cfg file.
+     * 
+     * @return if the selection is valid
+     */
+    private boolean verifyAndSaveCurrentSelection() {
+        String display = (String) displayResCombo.getSelectedItem();
+        boolean fullscreen = fullscreenBox.isSelected();
+        boolean vsync = vsyncBox.isSelected();
+
+        int width = Integer.parseInt(display.substring(0, display.indexOf(" x ")));
+        display = display.substring(display.indexOf(" x ") + 3);
+        int height = Integer.parseInt(display);
+
+        String depthString = (String) colorDepthCombo.getSelectedItem();
+        int depth = -1;
+        if (depthString.equals("???")) {
+            depth = 0;
+        } else {
+            depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' ')));
+        }
+
+        String freqString = (String) displayFreqCombo.getSelectedItem();
+        int freq = -1;
+        if (fullscreen) {
+            if (freqString.equals("???")) {
+                freq = 0;
+            } else {
+                freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' ')));
+            }
+        }
+
+        String aaString = (String) antialiasCombo.getSelectedItem();
+        int multisample = -1;
+        if (aaString.equals("Disabled")) {
+            multisample = 0;
+        } else {
+            multisample = Integer.parseInt(aaString.substring(0, aaString.indexOf('x')));
+        }
+
+        // FIXME: Does not work in Linux
+        /*
+         * if (!fullscreen) { //query the current bit depth of the desktop int
+         * curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment()
+         * .getDefaultScreenDevice().getDisplayMode().getBitDepth(); if (depth >
+         * curDepth) { showError(this,"Cannot choose a higher bit depth in
+         * windowed " + "mode than your current desktop bit depth"); return
+         * false; } }
+         */
+
+        String renderer = "LWJGL-OpenGL2";//(String) rendererCombo.getSelectedItem();
+
+        boolean valid = false;
+
+        // test valid display mode when going full screen
+        if (!fullscreen) {
+            valid = true;
+        } else {
+            GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+            valid = device.isFullScreenSupported();
+        }
+
+        if (valid) {
+            //use the GameSettings class to save it.
+            source.setWidth(width);
+            source.setHeight(height);
+            source.setBitsPerPixel(depth);
+            source.setFrequency(freq);
+            source.setFullscreen(fullscreen);
+            source.setVSync(vsync);
+            //source.setRenderer(renderer);
+            source.setSamples(multisample);
+
+            String appTitle = source.getTitle();
+
+            try {
+                source.save(appTitle);
+            } catch (BackingStoreException ex) {
+                logger.log(Level.WARNING,
+                        "Failed to save setting changes", ex);
+            }
+        } else {
+            showError(
+                    this,
+                    "Your monitor claims to not support the display mode you've selected.\n"
+                    + "The combination of bit depth and refresh rate is not supported.");
+        }
+
+        return valid;
+    }
+
+    /**
+     * <code>setUpChooser</code> retrieves all available display modes and
+     * places them in a <code>JComboBox</code>. The resolution specified by
+     * GameSettings is used as the default value.
+     * 
+     * @return the combo box of display modes.
+     */
+    private JComboBox setUpResolutionChooser() {
+        String[] res = getResolutions(modes);
+        JComboBox resolutionBox = new JComboBox(res);
+
+        resolutionBox.setSelectedItem(source.getWidth() + " x "
+                + source.getHeight());
+        resolutionBox.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                updateDisplayChoices();
+            }
+        });
+
+        return resolutionBox;
+    }
+
+    /**
+     * <code>setUpRendererChooser</code> sets the list of available renderers.
+     * Data is obtained from the <code>DisplaySystem</code> class. The
+     * renderer specified by GameSettings is used as the default value.
+     * 
+     * @return the list of renderers.
+     */
+    private JComboBox setUpRendererChooser() {
+        String modes[] = {"NULL", "JOGL-OpenGL1", "LWJGL-OpenGL2", "LWJGL-OpenGL3", "LWJGL-OpenGL3.1"};
+        JComboBox nameBox = new JComboBox(modes);
+        nameBox.setSelectedItem(source.getRenderer());
+        return nameBox;
+    }
+
+    /**
+     * <code>updateDisplayChoices</code> updates the available color depth and
+     * display frequency options to match the currently selected resolution.
+     */
+    private void updateDisplayChoices() {
+        if (!fullscreenBox.isSelected()) {
+            // don't run this function when changing windowed settings
+            return;
+        }
+        String resolution = (String) displayResCombo.getSelectedItem();
+        String colorDepth = (String) colorDepthCombo.getSelectedItem();
+        if (colorDepth == null) {
+            colorDepth = source.getBitsPerPixel() + " bpp";
+        }
+        String displayFreq = (String) displayFreqCombo.getSelectedItem();
+        if (displayFreq == null) {
+            displayFreq = source.getFrequency() + " Hz";
+        }
+
+        // grab available depths
+        String[] depths = getDepths(resolution, modes);
+        colorDepthCombo.setModel(new DefaultComboBoxModel(depths));
+        colorDepthCombo.setSelectedItem(colorDepth);
+        // grab available frequencies
+        String[] freqs = getFrequencies(resolution, modes);
+        displayFreqCombo.setModel(new DefaultComboBoxModel(freqs));
+        // Try to reset freq
+        displayFreqCombo.setSelectedItem(displayFreq);
+    }
+
+    /**
+     * <code>updateResolutionChoices</code> updates the available resolutions
+     * list to match the currently selected window mode (fullscreen or
+     * windowed). It then sets up a list of standard options (if windowed) or
+     * calls <code>updateDisplayChoices</code> (if fullscreen).
+     */
+    private void updateResolutionChoices() {
+        if (!fullscreenBox.isSelected()) {
+            displayResCombo.setModel(new DefaultComboBoxModel(
+                    windowedResolutions));
+            colorDepthCombo.setModel(new DefaultComboBoxModel(new String[]{
+                        "24 bpp", "16 bpp"}));
+            displayFreqCombo.setModel(new DefaultComboBoxModel(
+                    new String[]{"n/a"}));
+            displayFreqCombo.setEnabled(false);
+        } else {
+            displayResCombo.setModel(new DefaultComboBoxModel(
+                    getResolutions(modes)));
+            displayFreqCombo.setEnabled(true);
+            updateDisplayChoices();
+        }
+    }
+
+    private void updateAntialiasChoices() {
+        // maybe in the future will add support for determining this info
+        // through pbuffer
+        String[] choices = new String[]{"Disabled", "2x", "4x", "6x", "8x", "16x"};
+        antialiasCombo.setModel(new DefaultComboBoxModel(choices));
+        antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples()/2,5)]);
+    }
+
+    //
+    // Utility methods
+    //
+    /**
+     * Utility method for converting a String denoting a file into a URL.
+     * 
+     * @return a URL pointing to the file or null
+     */
+    private static URL getURL(String file) {
+        URL url = null;
+        try {
+            url = new URL("file:" + file);
+        } catch (MalformedURLException e) {
+        }
+        return url;
+    }
+
+    private static void showError(java.awt.Component parent, String message) {
+        JOptionPane.showMessageDialog(parent, message, "Error",
+                JOptionPane.ERROR_MESSAGE);
+    }
+
+    /**
+     * Returns every unique resolution from an array of <code>DisplayMode</code>s.
+     */
+    private static String[] getResolutions(DisplayMode[] modes) {
+        ArrayList<String> resolutions = new ArrayList<String>(modes.length);
+        for (int i = 0; i < modes.length; i++) {
+            String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+            if (!resolutions.contains(res)) {
+                resolutions.add(res);
+            }
+        }
+
+        String[] res = new String[resolutions.size()];
+        resolutions.toArray(res);
+        return res;
+    }
+
+    /**
+     * Returns every possible bit depth for the given resolution.
+     */
+    private static String[] getDepths(String resolution, DisplayMode[] modes) {
+        ArrayList<String> depths = new ArrayList<String>(4);
+        for (int i = 0; i < modes.length; i++) {
+            // Filter out all bit depths lower than 16 - Java incorrectly
+            // reports
+            // them as valid depths though the monitor does not support them
+            if (modes[i].getBitDepth() < 16 && modes[i].getBitDepth() > 0) {
+                continue;
+            }
+
+            String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+            String depth = modes[i].getBitDepth() + " bpp";
+            if (res.equals(resolution) && !depths.contains(depth)) {
+                depths.add(depth);
+            }
+        }
+
+        if (depths.size() == 1 && depths.contains("-1 bpp")) {
+            // add some default depths, possible system is multi-depth supporting
+            depths.clear();
+            depths.add("24 bpp");
+        }
+
+        String[] res = new String[depths.size()];
+        depths.toArray(res);
+        return res;
+    }
+
+    /**
+     * Returns every possible refresh rate for the given resolution.
+     */
+    private static String[] getFrequencies(String resolution,
+            DisplayMode[] modes) {
+        ArrayList<String> freqs = new ArrayList<String>(4);
+        for (int i = 0; i < modes.length; i++) {
+            String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+            String freq;
+            if (modes[i].getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) {
+                freq = "???";
+            } else {
+                freq = modes[i].getRefreshRate() + " Hz";
+            }
+
+            if (res.equals(resolution) && !freqs.contains(freq)) {
+                freqs.add(freq);
+            }
+        }
+
+        String[] res = new String[freqs.size()];
+        freqs.toArray(res);
+        return res;
+    }
+
+    /**
+     * Utility class for sorting <code>DisplayMode</code>s. Sorts by
+     * resolution, then bit depth, and then finally refresh rate.
+     */
+    private class DisplayModeSorter implements Comparator<DisplayMode> {
+
+        /**
+         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+         */
+        public int compare(DisplayMode a, DisplayMode b) {
+            // Width
+            if (a.getWidth() != b.getWidth()) {
+                return (a.getWidth() > b.getWidth()) ? 1 : -1;
+            }
+            // Height
+            if (a.getHeight() != b.getHeight()) {
+                return (a.getHeight() > b.getHeight()) ? 1 : -1;
+            }
+            // Bit depth
+            if (a.getBitDepth() != b.getBitDepth()) {
+                return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1;
+            }
+            // Refresh rate
+            if (a.getRefreshRate() != b.getRefreshRate()) {
+                return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1;
+            }
+            // All fields are equal
+            return 0;
+        }
+    }
+}
diff --git a/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java b/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java
new file mode 100644
index 0000000..e39bca1
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/state/MjpegFileWriter.java
@@ -0,0 +1,490 @@
+package com.jme3.app.state;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.imageio.ImageIO;
+
+/**
+ * Released under BSD License
+ * @author monceaux, normenhansen
+ */
+public class MjpegFileWriter {
+
+    int width = 0;
+    int height = 0;
+    double framerate = 0;
+    int numFrames = 0;
+    File aviFile = null;
+    FileOutputStream aviOutput = null;
+    FileChannel aviChannel = null;
+    long riffOffset = 0;
+    long aviMovieOffset = 0;
+    AVIIndexList indexlist = null;
+
+    public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception {
+        this(aviFile, width, height, framerate, 0);
+    }
+
+    public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception {
+        this.aviFile = aviFile;
+        this.width = width;
+        this.height = height;
+        this.framerate = framerate;
+        this.numFrames = numFrames;
+        aviOutput = new FileOutputStream(aviFile);
+        aviChannel = aviOutput.getChannel();
+
+        RIFFHeader rh = new RIFFHeader();
+        aviOutput.write(rh.toBytes());
+        aviOutput.write(new AVIMainHeader().toBytes());
+        aviOutput.write(new AVIStreamList().toBytes());
+        aviOutput.write(new AVIStreamHeader().toBytes());
+        aviOutput.write(new AVIStreamFormat().toBytes());
+        aviOutput.write(new AVIJunk().toBytes());
+        aviMovieOffset = aviChannel.position();
+        aviOutput.write(new AVIMovieList().toBytes());
+        indexlist = new AVIIndexList();
+    }
+
+    public void addImage(Image image) throws Exception {
+        addImage(writeImageToBytes(image));
+    }
+
+    public void addImage(byte[] imagedata) throws Exception {
+        byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
+        int useLength = imagedata.length;
+        long position = aviChannel.position();
+        int extra = (useLength + (int) position) % 4;
+        if (extra > 0) {
+            useLength = useLength + extra;
+        }
+
+        indexlist.addAVIIndex((int) position, useLength);
+
+        aviOutput.write(fcc);
+        aviOutput.write(intBytes(swapInt(useLength)));
+        aviOutput.write(imagedata);
+        if (extra > 0) {
+            for (int i = 0; i < extra; i++) {
+                aviOutput.write(0);
+            }
+        }
+        imagedata = null;
+    }
+
+    public void finishAVI() throws Exception {
+        byte[] indexlistBytes = indexlist.toBytes();
+        aviOutput.write(indexlistBytes);
+        aviOutput.close();
+        long size = aviFile.length();
+        RandomAccessFile raf = new RandomAccessFile(aviFile, "rw");
+        raf.seek(4);
+        raf.write(intBytes(swapInt((int) size - 8)));
+        raf.seek(aviMovieOffset + 4);
+        raf.write(intBytes(swapInt((int) (size - 8 - aviMovieOffset - indexlistBytes.length))));
+        raf.close();
+    }
+
+    // public void writeAVI(File file) throws Exception
+    // {
+    // OutputStream os = new FileOutputStream(file);
+    //
+    // // RIFFHeader
+    // // AVIMainHeader
+    // // AVIStreamList
+    // // AVIStreamHeader
+    // // AVIStreamFormat
+    // // write 00db and image bytes...
+    // }
+    public static int swapInt(int v) {
+        return (v >>> 24) | (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00);
+    }
+
+    public static short swapShort(short v) {
+        return (short) ((v >>> 8) | (v << 8));
+    }
+
+    public static byte[] intBytes(int i) {
+        byte[] b = new byte[4];
+        b[0] = (byte) (i >>> 24);
+        b[1] = (byte) ((i >>> 16) & 0x000000FF);
+        b[2] = (byte) ((i >>> 8) & 0x000000FF);
+        b[3] = (byte) (i & 0x000000FF);
+
+        return b;
+    }
+
+    public static byte[] shortBytes(short i) {
+        byte[] b = new byte[2];
+        b[0] = (byte) (i >>> 8);
+        b[1] = (byte) (i & 0x000000FF);
+
+        return b;
+    }
+
+    private class RIFFHeader {
+
+        public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'};
+        public int fileSize = 0;
+        public byte[] fcc2 = new byte[]{'A', 'V', 'I', ' '};
+        public byte[] fcc3 = new byte[]{'L', 'I', 'S', 'T'};
+        public int listSize = 200;
+        public byte[] fcc4 = new byte[]{'h', 'd', 'r', 'l'};
+
+        public RIFFHeader() {
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(fileSize)));
+            baos.write(fcc2);
+            baos.write(fcc3);
+            baos.write(intBytes(swapInt(listSize)));
+            baos.write(fcc4);
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIMainHeader {
+        /*
+         * 
+         * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD
+         * dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD
+         * dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD
+         * dwSuggestedBufferSize; DWORD dwWidth; DWORD dwHeight; DWORD
+         * dwReserved[4];
+         */
+
+        public byte[] fcc = new byte[]{'a', 'v', 'i', 'h'};
+        public int cb = 56;
+        public int dwMicroSecPerFrame = 0;                                // (1
+        // /
+        // frames
+        // per
+        // sec)
+        // *
+        // 1,000,000
+        public int dwMaxBytesPerSec = 10000000;
+        public int dwPaddingGranularity = 0;
+        public int dwFlags = 65552;
+        public int dwTotalFrames = 0;                                // replace
+        // with
+        // correct
+        // value
+        public int dwInitialFrames = 0;
+        public int dwStreams = 1;
+        public int dwSuggestedBufferSize = 0;
+        public int dwWidth = 0;                                // replace
+        // with
+        // correct
+        // value
+        public int dwHeight = 0;                                // replace
+        // with
+        // correct
+        // value
+        public int[] dwReserved = new int[4];
+
+        public AVIMainHeader() {
+            dwMicroSecPerFrame = (int) ((1.0 / framerate) * 1000000.0);
+            dwWidth = width;
+            dwHeight = height;
+            dwTotalFrames = numFrames;
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(cb)));
+            baos.write(intBytes(swapInt(dwMicroSecPerFrame)));
+            baos.write(intBytes(swapInt(dwMaxBytesPerSec)));
+            baos.write(intBytes(swapInt(dwPaddingGranularity)));
+            baos.write(intBytes(swapInt(dwFlags)));
+            baos.write(intBytes(swapInt(dwTotalFrames)));
+            baos.write(intBytes(swapInt(dwInitialFrames)));
+            baos.write(intBytes(swapInt(dwStreams)));
+            baos.write(intBytes(swapInt(dwSuggestedBufferSize)));
+            baos.write(intBytes(swapInt(dwWidth)));
+            baos.write(intBytes(swapInt(dwHeight)));
+            baos.write(intBytes(swapInt(dwReserved[0])));
+            baos.write(intBytes(swapInt(dwReserved[1])));
+            baos.write(intBytes(swapInt(dwReserved[2])));
+            baos.write(intBytes(swapInt(dwReserved[3])));
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIStreamList {
+
+        public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'};
+        public int size = 124;
+        public byte[] fcc2 = new byte[]{'s', 't', 'r', 'l'};
+
+        public AVIStreamList() {
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(size)));
+            baos.write(fcc2);
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIStreamHeader {
+        /*
+         * FOURCC fcc; DWORD cb; FOURCC fccType; FOURCC fccHandler; DWORD
+         * dwFlags; WORD wPriority; WORD wLanguage; DWORD dwInitialFrames; DWORD
+         * dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD
+         * dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; struct {
+         * short int left; short int top; short int right; short int bottom; }
+         * rcFrame;
+         */
+
+        public byte[] fcc = new byte[]{'s', 't', 'r', 'h'};
+        public int cb = 64;
+        public byte[] fccType = new byte[]{'v', 'i', 'd', 's'};
+        public byte[] fccHandler = new byte[]{'M', 'J', 'P', 'G'};
+        public int dwFlags = 0;
+        public short wPriority = 0;
+        public short wLanguage = 0;
+        public int dwInitialFrames = 0;
+        public int dwScale = 0;                                // microseconds
+        // per
+        // frame
+        public int dwRate = 1000000;                          // dwRate
+        // /
+        // dwScale
+        // =
+        // frame
+        // rate
+        public int dwStart = 0;
+        public int dwLength = 0;                                // num
+        // frames
+        public int dwSuggestedBufferSize = 0;
+        public int dwQuality = -1;
+        public int dwSampleSize = 0;
+        public int left = 0;
+        public int top = 0;
+        public int right = 0;
+        public int bottom = 0;
+
+        public AVIStreamHeader() {
+            dwScale = (int) ((1.0 / framerate) * 1000000.0);
+            dwLength = numFrames;
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(cb)));
+            baos.write(fccType);
+            baos.write(fccHandler);
+            baos.write(intBytes(swapInt(dwFlags)));
+            baos.write(shortBytes(swapShort(wPriority)));
+            baos.write(shortBytes(swapShort(wLanguage)));
+            baos.write(intBytes(swapInt(dwInitialFrames)));
+            baos.write(intBytes(swapInt(dwScale)));
+            baos.write(intBytes(swapInt(dwRate)));
+            baos.write(intBytes(swapInt(dwStart)));
+            baos.write(intBytes(swapInt(dwLength)));
+            baos.write(intBytes(swapInt(dwSuggestedBufferSize)));
+            baos.write(intBytes(swapInt(dwQuality)));
+            baos.write(intBytes(swapInt(dwSampleSize)));
+            baos.write(intBytes(swapInt(left)));
+            baos.write(intBytes(swapInt(top)));
+            baos.write(intBytes(swapInt(right)));
+            baos.write(intBytes(swapInt(bottom)));
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIStreamFormat {
+        /*
+         * FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD
+         * biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage;
+         * LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD
+         * biClrImportant;
+         */
+
+        public byte[] fcc = new byte[]{'s', 't', 'r', 'f'};
+        public int cb = 40;
+        public int biSize = 40;                               // same
+        // as
+        // cb
+        public int biWidth = 0;
+        public int biHeight = 0;
+        public short biPlanes = 1;
+        public short biBitCount = 24;
+        public byte[] biCompression = new byte[]{'M', 'J', 'P', 'G'};
+        public int biSizeImage = 0;                                // width
+        // x
+        // height
+        // in
+        // pixels
+        public int biXPelsPerMeter = 0;
+        public int biYPelsPerMeter = 0;
+        public int biClrUsed = 0;
+        public int biClrImportant = 0;
+
+        public AVIStreamFormat() {
+            biWidth = width;
+            biHeight = height;
+            biSizeImage = width * height;
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(cb)));
+            baos.write(intBytes(swapInt(biSize)));
+            baos.write(intBytes(swapInt(biWidth)));
+            baos.write(intBytes(swapInt(biHeight)));
+            baos.write(shortBytes(swapShort(biPlanes)));
+            baos.write(shortBytes(swapShort(biBitCount)));
+            baos.write(biCompression);
+            baos.write(intBytes(swapInt(biSizeImage)));
+            baos.write(intBytes(swapInt(biXPelsPerMeter)));
+            baos.write(intBytes(swapInt(biYPelsPerMeter)));
+            baos.write(intBytes(swapInt(biClrUsed)));
+            baos.write(intBytes(swapInt(biClrImportant)));
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIMovieList {
+
+        public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'};
+        public int listSize = 0;
+        public byte[] fcc2 = new byte[]{'m', 'o', 'v', 'i'};
+
+        // 00db size jpg image data ...
+        public AVIMovieList() {
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(listSize)));
+            baos.write(fcc2);
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIIndexList {
+
+        public byte[] fcc = new byte[]{'i', 'd', 'x', '1'};
+        public int cb = 0;
+        public List<AVIIndex> ind = new ArrayList<AVIIndex>();
+
+        public AVIIndexList() {
+        }
+
+        @SuppressWarnings("unused")
+        public void addAVIIndex(AVIIndex ai) {
+            ind.add(ai);
+        }
+
+        public void addAVIIndex(int dwOffset, int dwSize) {
+            ind.add(new AVIIndex(dwOffset, dwSize));
+        }
+
+        public byte[] toBytes() throws Exception {
+            cb = 16 * ind.size();
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(cb)));
+            for (int i = 0; i < ind.size(); i++) {
+                AVIIndex in = (AVIIndex) ind.get(i);
+                baos.write(in.toBytes());
+            }
+
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIIndex {
+
+        public byte[] fcc = new byte[]{'0', '0', 'd', 'b'};
+        public int dwFlags = 16;
+        public int dwOffset = 0;
+        public int dwSize = 0;
+
+        public AVIIndex(int dwOffset, int dwSize) {
+            this.dwOffset = dwOffset;
+            this.dwSize = dwSize;
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(dwFlags)));
+            baos.write(intBytes(swapInt(dwOffset)));
+            baos.write(intBytes(swapInt(dwSize)));
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    private class AVIJunk {
+
+        public byte[] fcc = new byte[]{'J', 'U', 'N', 'K'};
+        public int size = 1808;
+        public byte[] data = new byte[size];
+
+        public AVIJunk() {
+            Arrays.fill(data, (byte) 0);
+        }
+
+        public byte[] toBytes() throws Exception {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(fcc);
+            baos.write(intBytes(swapInt(size)));
+            baos.write(data);
+            baos.close();
+
+            return baos.toByteArray();
+        }
+    }
+
+    public byte[] writeImageToBytes(Image image) throws Exception {
+        BufferedImage bi;
+        if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) {
+            bi = (BufferedImage) image;
+        } else {
+            bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+            Graphics2D g = bi.createGraphics();
+            g.drawImage(image, 0, 0, width, height, null);
+        }
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(bi, "jpg", baos);
+        baos.close();
+        return baos.toByteArray();
+    }
+}
diff --git a/engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java b/engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java
new file mode 100644
index 0000000..ba2556d
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/state/ScreenshotAppState.java
@@ -0,0 +1,94 @@
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.Screenshots;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.imageio.ImageIO;
+
+public class ScreenshotAppState extends AbstractAppState implements ActionListener, SceneProcessor {
+
+    private static final Logger logger = Logger.getLogger(ScreenshotAppState.class.getName());
+    private boolean capture = false;
+    private Renderer renderer;
+    private ByteBuffer outBuf;
+    private String appName;
+    private int shotIndex = 0;
+    private BufferedImage awtImage;
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        if (!super.isInitialized()){
+            InputManager inputManager = app.getInputManager();
+            inputManager.addMapping("ScreenShot", new KeyTrigger(KeyInput.KEY_SYSRQ));
+            inputManager.addListener(this, "ScreenShot");
+
+            List<ViewPort> vps = app.getRenderManager().getPostViews();
+            ViewPort last = vps.get(vps.size()-1);
+            last.addProcessor(this);
+
+            appName = app.getClass().getSimpleName();
+        }
+        
+        super.initialize(stateManager, app);
+    }
+
+    public void onAction(String name, boolean value, float tpf) {
+        if (value){
+            capture = true;
+        }
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        renderer = rm.getRenderer();
+        reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return super.isInitialized() && renderer != null;
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+        outBuf = BufferUtils.createByteBuffer(w*h*4);
+        awtImage = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+    }
+
+    public void postFrame(FrameBuffer out) {
+        if (capture){
+            capture = false;
+            shotIndex++;
+
+            renderer.readFrameBuffer(out, outBuf);
+            Screenshots.convertScreenShot(outBuf, awtImage);
+
+            try {
+                ImageIO.write(awtImage, "png", new File(appName + shotIndex + ".png"));
+            } catch (IOException ex){
+                logger.log(Level.SEVERE, "Error while saving screenshot", ex);
+            }
+        }
+    }
+}
diff --git a/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java b/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java
new file mode 100644
index 0000000..6ae8019
--- /dev/null
+++ b/engine/src/desktop/com/jme3/app/state/VideoRecorderAppState.java
@@ -0,0 +1,241 @@
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.system.NanoTimer;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.Screenshots;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A Video recording AppState that records the screen output into an AVI file with
+ * M-JPEG content. The file should be playable on any OS in any video player.<br/>
+ * The video recording starts when the state is attached and stops when it is detached
+ * or the application is quit. You can set the fileName of the file to be written when the
+ * state is detached, else the old file will be overwritten. If you specify no file
+ * the AppState will attempt to write a file into the user home directory, made unique
+ * by a timestamp.
+ * @author normenhansen, Robert McIntyre
+ */
+public class VideoRecorderAppState extends AbstractAppState {
+
+    private int framerate = 30;
+    private VideoProcessor processor;
+    private File file;
+    private Application app;
+    private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
+
+        public Thread newThread(Runnable r) {
+            Thread th = new Thread(r);
+            th.setName("jME Video Processing Thread");
+            th.setDaemon(true);
+            return th;
+        }
+    });
+    private int numCpus = Runtime.getRuntime().availableProcessors();
+    private ViewPort lastViewPort;
+
+    public VideoRecorderAppState() {
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
+    }
+
+    public VideoRecorderAppState(File file) {
+        this.file = file;
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "JME3 VideoRecorder running on {0} CPU's", numCpus);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File file) {
+        if (isInitialized()) {
+            throw new IllegalStateException("Cannot set file while attached!");
+        }
+        this.file = file;
+    }
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        super.initialize(stateManager, app);
+        this.app = app;
+        app.setTimer(new IsoTimer(framerate));
+        if (file == null) {
+            String filename = System.getProperty("user.home") + File.separator + "jMonkey-" + System.currentTimeMillis() / 1000 + ".avi";
+            file = new File(filename);
+        }
+        processor = new VideoProcessor();
+        List<ViewPort> vps = app.getRenderManager().getPostViews();
+        lastViewPort = vps.get(vps.size()-1);
+        lastViewPort.addProcessor(processor);
+    }
+
+    @Override
+    public void cleanup() {
+        lastViewPort.removeProcessor(processor);
+        app.setTimer(new NanoTimer());
+        initialized = false;
+        file = null;
+        super.cleanup();
+    }
+
+    private class WorkItem {
+
+        ByteBuffer buffer;
+        BufferedImage image;
+        byte[] data;
+
+        public WorkItem(int width, int height) {
+            image = new BufferedImage(width, height,
+                    BufferedImage.TYPE_4BYTE_ABGR);
+            buffer = BufferUtils.createByteBuffer(width * height * 4);
+        }
+    }
+
+    private class VideoProcessor implements SceneProcessor {
+
+        private Camera camera;
+        private int width;
+        private int height;
+        private RenderManager renderManager;
+        private boolean isInitilized = false;
+        private LinkedBlockingQueue<WorkItem> freeItems;
+        private LinkedBlockingQueue<WorkItem> usedItems = new LinkedBlockingQueue<WorkItem>();
+        private MjpegFileWriter writer;
+
+        public void addImage(Renderer renderer, FrameBuffer out) {
+            if (freeItems == null) {
+                return;
+            }
+            try {
+                final WorkItem item = freeItems.take();
+                usedItems.add(item);
+                item.buffer.clear();
+                renderer.readFrameBuffer(out, item.buffer);
+                executor.submit(new Callable<Void>() {
+
+                    public Void call() throws Exception {
+                        Screenshots.convertScreenShot(item.buffer, item.image);
+                        item.data = writer.writeImageToBytes(item.image);
+                        while (usedItems.peek() != item) {
+                            Thread.sleep(1);
+                        }
+                        writer.addImage(item.data);
+                        usedItems.poll();
+                        freeItems.add(item);
+                        return null;
+                    }
+                });
+            } catch (InterruptedException ex) {
+                Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+
+        public void initialize(RenderManager rm, ViewPort viewPort) {
+            this.camera = viewPort.getCamera();
+            this.width = camera.getWidth();
+            this.height = camera.getHeight();
+            this.renderManager = rm;
+            this.isInitilized = true;
+            if (freeItems == null) {
+                freeItems = new LinkedBlockingQueue<WorkItem>();
+                for (int i = 0; i < numCpus; i++) {
+                    freeItems.add(new WorkItem(width, height));
+                }
+            }
+        }
+
+        public void reshape(ViewPort vp, int w, int h) {
+        }
+
+        public boolean isInitialized() {
+            return this.isInitilized;
+        }
+
+        public void preFrame(float tpf) {
+            if (null == writer) {
+                try {
+                    writer = new MjpegFileWriter(file, width, height, framerate);
+                } catch (Exception ex) {
+                    Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error creating file writer: {0}", ex);
+                }
+            }
+        }
+
+        public void postQueue(RenderQueue rq) {
+        }
+
+        public void postFrame(FrameBuffer out) {
+            addImage(renderManager.getRenderer(), out);
+        }
+
+        public void cleanup() {
+            try {
+                while (freeItems.size() < numCpus) {
+                    Thread.sleep(10);
+                }
+                writer.finishAVI();
+            } catch (Exception ex) {
+                Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error closing video: {0}", ex);
+            }
+            writer = null;
+        }
+    }
+
+    public static final class IsoTimer extends com.jme3.system.Timer {
+
+        private float framerate;
+        private int ticks;
+        private long lastTime = 0;
+
+        public IsoTimer(float framerate) {
+            this.framerate = framerate;
+            this.ticks = 0;
+        }
+
+        public long getTime() {
+            return (long) (this.ticks * (1.0f / this.framerate) * 1000f);
+        }
+
+        public long getResolution() {
+            return 1000000000L;
+        }
+
+        public float getFrameRate() {
+            return this.framerate;
+        }
+
+        public float getTimePerFrame() {
+            return (float) (1.0f / this.framerate);
+        }
+
+        public void update() {
+            long time = System.currentTimeMillis();
+            long difference = time - lastTime;
+            lastTime = time;
+            if (difference < (1.0f / this.framerate) * 1000.0f) {
+                try {
+                    Thread.sleep(difference);
+                } catch (InterruptedException ex) {
+                }
+            }
+            this.ticks++;
+        }
+
+        public void reset() {
+            this.ticks = 0;
+        }
+    }
+}
diff --git a/engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java b/engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java
new file mode 100644
index 0000000..8667074
--- /dev/null
+++ b/engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.awt;
+
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.KeyInputEvent;
+import java.awt.Component;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+/**
+ * <code>AwtKeyInput</code>
+ *
+ * @author Joshua Slack
+ * @author Kirill Vainer
+ * @version $Revision: 4133 $
+ */
+public class AwtKeyInput implements KeyInput, KeyListener {
+
+    private static final Logger logger = Logger.getLogger(AwtKeyInput.class.getName());
+    
+    private final ArrayList<KeyInputEvent> eventQueue = new ArrayList<KeyInputEvent>();
+    private RawInputListener listener;
+    private Component component;
+
+    public AwtKeyInput(){
+    }
+
+    public void initialize() {
+    }
+    
+    public void destroy() {
+    }
+
+    public void setInputSource(Component comp){
+        synchronized (eventQueue){
+            if (component != null){
+                component.removeKeyListener(this);
+                eventQueue.clear();
+            }
+            component = comp;
+            component.addKeyListener(this);
+        }
+    }
+    
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    public int getKeyCount() {
+        return KeyEvent.KEY_LAST+1;
+    }
+
+    public void update() {
+        synchronized (eventQueue){
+            // flush events to listener
+            for (int i = 0; i < eventQueue.size(); i++){
+                listener.onKeyEvent(eventQueue.get(i));
+            }
+            eventQueue.clear();
+        }
+    }
+
+    public boolean isInitialized() {
+        return true;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public void keyTyped(KeyEvent evt) {
+        // key code is zero for typed events
+//        int code = 0;
+//        KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, true);
+//        keyEvent.setTime(evt.getWhen());
+//        synchronized (eventQueue){
+//            eventQueue.add(keyEvent);
+//        }
+    }
+
+    public void keyPressed(KeyEvent evt) {
+        int code = convertAwtKey(evt.getKeyCode());
+        KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, false);
+        keyEvent.setTime(evt.getWhen());
+        synchronized (eventQueue){
+            eventQueue.add(keyEvent);
+        }
+    }
+
+    public void keyReleased(KeyEvent evt) {
+        int code = convertAwtKey(evt.getKeyCode());
+        KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, false);
+        keyEvent.setTime(evt.getWhen());
+        synchronized (eventQueue){
+            eventQueue.add(keyEvent);
+        }
+    }
+
+    /**
+     * <code>convertJmeCode</code> converts KeyInput key codes to AWT key codes.
+     *
+     * @param key jme KeyInput key code
+     * @return awt KeyEvent key code
+     */
+    public static int convertJmeCode( int key ) {
+        switch ( key ) {
+            case KEY_ESCAPE:
+                return KeyEvent.VK_ESCAPE;
+            case KEY_1:
+                return KeyEvent.VK_1;
+            case KEY_2:
+                return KeyEvent.VK_2;
+            case KEY_3:
+                return KeyEvent.VK_3;
+            case KEY_4:
+                return KeyEvent.VK_4;
+            case KEY_5:
+                return KeyEvent.VK_5;
+            case KEY_6:
+                return KeyEvent.VK_6;
+            case KEY_7:
+                return KeyEvent.VK_7;
+            case KEY_8:
+                return KeyEvent.VK_8;
+            case KEY_9:
+                return KeyEvent.VK_9;
+            case KEY_0:
+                return KeyEvent.VK_0;
+            case KEY_MINUS:
+                return KeyEvent.VK_MINUS;
+            case KEY_EQUALS:
+                return KeyEvent.VK_EQUALS;
+            case KEY_BACK:
+                return KeyEvent.VK_BACK_SPACE;
+            case KEY_TAB:
+                return KeyEvent.VK_TAB;
+            case KEY_Q:
+                return KeyEvent.VK_Q;
+            case KEY_W:
+                return KeyEvent.VK_W;
+            case KEY_E:
+                return KeyEvent.VK_E;
+            case KEY_R:
+                return KeyEvent.VK_R;
+            case KEY_T:
+                return KeyEvent.VK_T;
+            case KEY_Y:
+                return KeyEvent.VK_Y;
+            case KEY_U:
+                return KeyEvent.VK_U;
+            case KEY_I:
+                return KeyEvent.VK_I;
+            case KEY_O:
+                return KeyEvent.VK_O;
+            case KEY_P:
+                return KeyEvent.VK_P;
+            case KEY_LBRACKET:
+                return KeyEvent.VK_OPEN_BRACKET;
+            case KEY_RBRACKET:
+                return KeyEvent.VK_CLOSE_BRACKET;
+            case KEY_RETURN:
+                return KeyEvent.VK_ENTER;
+            case KEY_LCONTROL:
+                return KeyEvent.VK_CONTROL;
+            case KEY_A:
+                return KeyEvent.VK_A;
+            case KEY_S:
+                return KeyEvent.VK_S;
+            case KEY_D:
+                return KeyEvent.VK_D;
+            case KEY_F:
+                return KeyEvent.VK_F;
+            case KEY_G:
+                return KeyEvent.VK_G;
+            case KEY_H:
+                return KeyEvent.VK_H;
+            case KEY_J:
+                return KeyEvent.VK_J;
+            case KEY_K:
+                return KeyEvent.VK_K;
+            case KEY_L:
+                return KeyEvent.VK_L;
+            case KEY_SEMICOLON:
+                return KeyEvent.VK_SEMICOLON;
+            case KEY_APOSTROPHE:
+                return KeyEvent.VK_QUOTE;
+            case KEY_GRAVE:
+                return KeyEvent.VK_DEAD_GRAVE;
+            case KEY_LSHIFT:
+                return KeyEvent.VK_SHIFT;
+            case KEY_BACKSLASH:
+                return KeyEvent.VK_BACK_SLASH;
+            case KEY_Z:
+                return KeyEvent.VK_Z;
+            case KEY_X:
+                return KeyEvent.VK_X;
+            case KEY_C:
+                return KeyEvent.VK_C;
+            case KEY_V:
+                return KeyEvent.VK_V;
+            case KEY_B:
+                return KeyEvent.VK_B;
+            case KEY_N:
+                return KeyEvent.VK_N;
+            case KEY_M:
+                return KeyEvent.VK_M;
+            case KEY_COMMA:
+                return KeyEvent.VK_COMMA;
+            case KEY_PERIOD:
+                return KeyEvent.VK_PERIOD;
+            case KEY_SLASH:
+                return KeyEvent.VK_SLASH;
+            case KEY_RSHIFT:
+                return KeyEvent.VK_SHIFT;
+            case KEY_MULTIPLY:
+                return KeyEvent.VK_MULTIPLY;
+            case KEY_SPACE:
+                return KeyEvent.VK_SPACE;
+            case KEY_CAPITAL:
+                return KeyEvent.VK_CAPS_LOCK;
+            case KEY_F1:
+                return KeyEvent.VK_F1;
+            case KEY_F2:
+                return KeyEvent.VK_F2;
+            case KEY_F3:
+                return KeyEvent.VK_F3;
+            case KEY_F4:
+                return KeyEvent.VK_F4;
+            case KEY_F5:
+                return KeyEvent.VK_F5;
+            case KEY_F6:
+                return KeyEvent.VK_F6;
+            case KEY_F7:
+                return KeyEvent.VK_F7;
+            case KEY_F8:
+                return KeyEvent.VK_F8;
+            case KEY_F9:
+                return KeyEvent.VK_F9;
+            case KEY_F10:
+                return KeyEvent.VK_F10;
+            case KEY_NUMLOCK:
+                return KeyEvent.VK_NUM_LOCK;
+            case KEY_SCROLL:
+                return KeyEvent.VK_SCROLL_LOCK;
+            case KEY_NUMPAD7:
+                return KeyEvent.VK_NUMPAD7;
+            case KEY_NUMPAD8:
+                return KeyEvent.VK_NUMPAD8;
+            case KEY_NUMPAD9:
+                return KeyEvent.VK_NUMPAD9;
+            case KEY_SUBTRACT:
+                return KeyEvent.VK_SUBTRACT;
+            case KEY_NUMPAD4:
+                return KeyEvent.VK_NUMPAD4;
+            case KEY_NUMPAD5:
+                return KeyEvent.VK_NUMPAD5;
+            case KEY_NUMPAD6:
+                return KeyEvent.VK_NUMPAD6;
+            case KEY_ADD:
+                return KeyEvent.VK_ADD;
+            case KEY_NUMPAD1:
+                return KeyEvent.VK_NUMPAD1;
+            case KEY_NUMPAD2:
+                return KeyEvent.VK_NUMPAD2;
+            case KEY_NUMPAD3:
+                return KeyEvent.VK_NUMPAD3;
+            case KEY_NUMPAD0:
+                return KeyEvent.VK_NUMPAD0;
+            case KEY_DECIMAL:
+                return KeyEvent.VK_DECIMAL;
+            case KEY_F11:
+                return KeyEvent.VK_F11;
+            case KEY_F12:
+                return KeyEvent.VK_F12;
+            case KEY_F13:
+                return KeyEvent.VK_F13;
+            case KEY_F14:
+                return KeyEvent.VK_F14;
+            case KEY_F15:
+                return KeyEvent.VK_F15;
+            case KEY_KANA:
+                return KeyEvent.VK_KANA;
+            case KEY_CONVERT:
+                return KeyEvent.VK_CONVERT;
+            case KEY_NOCONVERT:
+                return KeyEvent.VK_NONCONVERT;
+            case KEY_NUMPADEQUALS:
+                return KeyEvent.VK_EQUALS;
+            case KEY_CIRCUMFLEX:
+                return KeyEvent.VK_CIRCUMFLEX;
+            case KEY_AT:
+                return KeyEvent.VK_AT;
+            case KEY_COLON:
+                return KeyEvent.VK_COLON;
+            case KEY_UNDERLINE:
+                return KeyEvent.VK_UNDERSCORE;
+            case KEY_STOP:
+                return KeyEvent.VK_STOP;
+            case KEY_NUMPADENTER:
+                return KeyEvent.VK_ENTER;
+            case KEY_RCONTROL:
+                return KeyEvent.VK_CONTROL;
+            case KEY_NUMPADCOMMA:
+                return KeyEvent.VK_COMMA;
+            case KEY_DIVIDE:
+                return KeyEvent.VK_DIVIDE;
+            case KEY_PAUSE:
+                return KeyEvent.VK_PAUSE;
+            case KEY_HOME:
+                return KeyEvent.VK_HOME;
+            case KEY_UP:
+                return KeyEvent.VK_UP;
+            case KEY_PRIOR:
+                return KeyEvent.VK_PAGE_UP;
+            case KEY_LEFT:
+                return KeyEvent.VK_LEFT;
+            case KEY_RIGHT:
+                return KeyEvent.VK_RIGHT;
+            case KEY_END:
+                return KeyEvent.VK_END;
+            case KEY_DOWN:
+                return KeyEvent.VK_DOWN;
+            case KEY_NEXT:
+                return KeyEvent.VK_PAGE_DOWN;
+            case KEY_INSERT:
+                return KeyEvent.VK_INSERT;
+            case KEY_DELETE:
+                return KeyEvent.VK_DELETE;
+            case KEY_LMENU:
+                return KeyEvent.VK_ALT; //todo: location left
+            case KEY_RMENU:
+                return KeyEvent.VK_ALT; //todo: location right
+        }
+        logger.warning("unsupported key:" + key);
+        return 0x10000 + key;
+    }
+
+    /**
+     * <code>convertAwtKey</code> converts AWT key codes to KeyInput key codes.
+     *
+     * @param key awt KeyEvent key code
+     * @return jme KeyInput key code
+     */
+    public static int convertAwtKey(int key) {
+        switch ( key ) {
+            case KeyEvent.VK_ESCAPE:
+                return KEY_ESCAPE;
+            case KeyEvent.VK_1:
+                return KEY_1;
+            case KeyEvent.VK_2:
+                return KEY_2;
+            case KeyEvent.VK_3:
+                return KEY_3;
+            case KeyEvent.VK_4:
+                return KEY_4;
+            case KeyEvent.VK_5:
+                return KEY_5;
+            case KeyEvent.VK_6:
+                return KEY_6;
+            case KeyEvent.VK_7:
+                return KEY_7;
+            case KeyEvent.VK_8:
+                return KEY_8;
+            case KeyEvent.VK_9:
+                return KEY_9;
+            case KeyEvent.VK_0:
+                return KEY_0;
+            case KeyEvent.VK_MINUS:
+                return KEY_MINUS;
+            case KeyEvent.VK_EQUALS:
+                return KEY_EQUALS;
+            case KeyEvent.VK_BACK_SPACE:
+                return KEY_BACK;
+            case KeyEvent.VK_TAB:
+                return KEY_TAB;
+            case KeyEvent.VK_Q:
+                return KEY_Q;
+            case KeyEvent.VK_W:
+                return KEY_W;
+            case KeyEvent.VK_E:
+                return KEY_E;
+            case KeyEvent.VK_R:
+                return KEY_R;
+            case KeyEvent.VK_T:
+                return KEY_T;
+            case KeyEvent.VK_Y:
+                return KEY_Y;
+            case KeyEvent.VK_U:
+                return KEY_U;
+            case KeyEvent.VK_I:
+                return KEY_I;
+            case KeyEvent.VK_O:
+                return KEY_O;
+            case KeyEvent.VK_P:
+                return KEY_P;
+            case KeyEvent.VK_OPEN_BRACKET:
+                return KEY_LBRACKET;
+            case KeyEvent.VK_CLOSE_BRACKET:
+                return KEY_RBRACKET;
+            case KeyEvent.VK_ENTER:
+                return KEY_RETURN;
+            case KeyEvent.VK_CONTROL:
+                return KEY_LCONTROL;
+            case KeyEvent.VK_A:
+                return KEY_A;
+            case KeyEvent.VK_S:
+                return KEY_S;
+            case KeyEvent.VK_D:
+                return KEY_D;
+            case KeyEvent.VK_F:
+                return KEY_F;
+            case KeyEvent.VK_G:
+                return KEY_G;
+            case KeyEvent.VK_H:
+                return KEY_H;
+            case KeyEvent.VK_J:
+                return KEY_J;
+            case KeyEvent.VK_K:
+                return KEY_K;
+            case KeyEvent.VK_L:
+                return KEY_L;
+            case KeyEvent.VK_SEMICOLON:
+                return KEY_SEMICOLON;
+            case KeyEvent.VK_QUOTE:
+                return KEY_APOSTROPHE;
+            case KeyEvent.VK_DEAD_GRAVE:
+                return KEY_GRAVE;
+            case KeyEvent.VK_SHIFT:
+                return KEY_LSHIFT;
+            case KeyEvent.VK_BACK_SLASH:
+                return KEY_BACKSLASH;
+            case KeyEvent.VK_Z:
+                return KEY_Z;
+            case KeyEvent.VK_X:
+                return KEY_X;
+            case KeyEvent.VK_C:
+                return KEY_C;
+            case KeyEvent.VK_V:
+                return KEY_V;
+            case KeyEvent.VK_B:
+                return KEY_B;
+            case KeyEvent.VK_N:
+                return KEY_N;
+            case KeyEvent.VK_M:
+                return KEY_M;
+            case KeyEvent.VK_COMMA:
+                return KEY_COMMA;
+            case KeyEvent.VK_PERIOD:
+                return KEY_PERIOD;
+            case KeyEvent.VK_SLASH:
+                return KEY_SLASH;
+            case KeyEvent.VK_MULTIPLY:
+                return KEY_MULTIPLY;
+            case KeyEvent.VK_SPACE:
+                return KEY_SPACE;
+            case KeyEvent.VK_CAPS_LOCK:
+                return KEY_CAPITAL;
+            case KeyEvent.VK_F1:
+                return KEY_F1;
+            case KeyEvent.VK_F2:
+                return KEY_F2;
+            case KeyEvent.VK_F3:
+                return KEY_F3;
+            case KeyEvent.VK_F4:
+                return KEY_F4;
+            case KeyEvent.VK_F5:
+                return KEY_F5;
+            case KeyEvent.VK_F6:
+                return KEY_F6;
+            case KeyEvent.VK_F7:
+                return KEY_F7;
+            case KeyEvent.VK_F8:
+                return KEY_F8;
+            case KeyEvent.VK_F9:
+                return KEY_F9;
+            case KeyEvent.VK_F10:
+                return KEY_F10;
+            case KeyEvent.VK_NUM_LOCK:
+                return KEY_NUMLOCK;
+            case KeyEvent.VK_SCROLL_LOCK:
+                return KEY_SCROLL;
+            case KeyEvent.VK_NUMPAD7:
+                return KEY_NUMPAD7;
+            case KeyEvent.VK_NUMPAD8:
+                return KEY_NUMPAD8;
+            case KeyEvent.VK_NUMPAD9:
+                return KEY_NUMPAD9;
+            case KeyEvent.VK_SUBTRACT:
+                return KEY_SUBTRACT;
+            case KeyEvent.VK_NUMPAD4:
+                return KEY_NUMPAD4;
+            case KeyEvent.VK_NUMPAD5:
+                return KEY_NUMPAD5;
+            case KeyEvent.VK_NUMPAD6:
+                return KEY_NUMPAD6;
+            case KeyEvent.VK_ADD:
+                return KEY_ADD;
+            case KeyEvent.VK_NUMPAD1:
+                return KEY_NUMPAD1;
+            case KeyEvent.VK_NUMPAD2:
+                return KEY_NUMPAD2;
+            case KeyEvent.VK_NUMPAD3:
+                return KEY_NUMPAD3;
+            case KeyEvent.VK_NUMPAD0:
+                return KEY_NUMPAD0;
+            case KeyEvent.VK_DECIMAL:
+                return KEY_DECIMAL;
+            case KeyEvent.VK_F11:
+                return KEY_F11;
+            case KeyEvent.VK_F12:
+                return KEY_F12;
+            case KeyEvent.VK_F13:
+                return KEY_F13;
+            case KeyEvent.VK_F14:
+                return KEY_F14;
+            case KeyEvent.VK_F15:
+                return KEY_F15;
+            case KeyEvent.VK_KANA:
+                return KEY_KANA;
+            case KeyEvent.VK_CONVERT:
+                return KEY_CONVERT;
+            case KeyEvent.VK_NONCONVERT:
+                return KEY_NOCONVERT;
+            case KeyEvent.VK_CIRCUMFLEX:
+                return KEY_CIRCUMFLEX;
+            case KeyEvent.VK_AT:
+                return KEY_AT;
+            case KeyEvent.VK_COLON:
+                return KEY_COLON;
+            case KeyEvent.VK_UNDERSCORE:
+                return KEY_UNDERLINE;
+            case KeyEvent.VK_STOP:
+                return KEY_STOP;
+            case KeyEvent.VK_DIVIDE:
+                return KEY_DIVIDE;
+            case KeyEvent.VK_PAUSE:
+                return KEY_PAUSE;
+            case KeyEvent.VK_HOME:
+                return KEY_HOME;
+            case KeyEvent.VK_UP:
+                return KEY_UP;
+            case KeyEvent.VK_PAGE_UP:
+                return KEY_PRIOR;
+            case KeyEvent.VK_LEFT:
+                return KEY_LEFT;
+            case KeyEvent.VK_RIGHT:
+                return KEY_RIGHT;
+            case KeyEvent.VK_END:
+                return KEY_END;
+            case KeyEvent.VK_DOWN:
+                return KEY_DOWN;
+            case KeyEvent.VK_PAGE_DOWN:
+                return KEY_NEXT;
+            case KeyEvent.VK_INSERT:
+                return KEY_INSERT;
+            case KeyEvent.VK_DELETE:
+                return KEY_DELETE;
+            case KeyEvent.VK_ALT:
+                return KEY_LMENU; //Left vs. Right need to improve
+            case KeyEvent.VK_META:
+            	return KEY_RCONTROL;
+
+        }
+        logger.warning( "unsupported key:" + key );
+        if ( key >= 0x10000 ) {
+            return key - 0x10000;
+        }
+
+        return 0;
+    }
+
+}
diff --git a/engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java b/engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java
new file mode 100644
index 0000000..e83c92a
--- /dev/null
+++ b/engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.awt;
+
+import com.jme3.input.MouseInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.SwingUtilities;
+
+/**
+ * <code>AwtMouseInput</code>
+ *
+ * @author Joshua Slack
+ * @author MHenze (cylab)
+ * 
+ * @version $Revision$
+ */
+public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListener, MouseMotionListener {
+
+    public static int WHEEL_AMP = 40;   // arbitrary...  Java's mouse wheel seems to report something a lot lower than lwjgl's
+
+    private static final Logger logger = Logger.getLogger(AwtMouseInput.class.getName());
+
+    private boolean visible = true;
+
+    private RawInputListener listener;
+
+    private Component component;
+
+    private final ArrayList<MouseButtonEvent> eventQueue = new ArrayList<MouseButtonEvent>();
+    private final ArrayList<MouseButtonEvent> eventQueueCopy = new ArrayList<MouseButtonEvent>();
+    
+    private int lastEventX;
+    private int lastEventY;
+    private int lastEventWheel;
+
+    private Cursor transparentCursor;
+
+    private Robot robot;
+    private int wheelPos;
+    private Point location;
+    private Point centerLocation;
+    private Point centerLocationOnScreen;
+    private Point lastKnownLocation;
+    private boolean isRecentering;
+    private boolean cursorMoved;
+    private int eventsSinceRecenter;
+
+    public AwtMouseInput() {
+        location = new Point();
+        centerLocation = new Point();
+        centerLocationOnScreen = new Point();
+        lastKnownLocation = new Point();
+
+        try{
+            robot = new Robot();
+        }catch (java.awt.AWTException e){
+            logger.log(Level.SEVERE, "Could not create a robot, so the mouse cannot be grabbed! ", e);
+        }
+    }
+    
+    public void setInputSource(Component comp){
+        if (component != null){
+            component.removeMouseListener(this);
+            component.removeMouseMotionListener(this);
+            component.removeMouseWheelListener(this);
+            
+            eventQueue.clear();
+            
+            wheelPos = 0;
+            isRecentering = false;
+            eventsSinceRecenter = 0;
+            lastEventX = 0;
+            lastEventY = 0;
+            lastEventWheel = 0;
+            location = new Point();
+            centerLocation = new Point();
+            centerLocationOnScreen = new Point();
+            lastKnownLocation = new Point();
+        }
+
+        component = comp;
+        component.addMouseListener(this);
+        component.addMouseMotionListener(this);
+        component.addMouseWheelListener(this);
+    }
+
+    public void initialize() {
+    }
+
+    public void destroy() {
+    }
+
+    public boolean isInitialized() {
+        return true;
+    }
+
+    public void setInputListener(RawInputListener listener){
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    public void setCursorVisible(boolean visible){
+        if (this.visible != visible){
+            
+            lastKnownLocation.x = lastKnownLocation.y = 0;
+            
+            this.visible = visible;
+            final boolean newVisible = visible;
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    component.setCursor(newVisible ? null : getTransparentCursor());
+                    if (!newVisible)
+                        recenterMouse(component);
+                }
+            });
+        }
+    }
+
+    public void update() {
+        if (cursorMoved){
+            int newX = location.x;
+            int newY = location.y;
+            int newWheel = wheelPos;
+
+            // invert DY
+            int actualX = lastKnownLocation.x;
+            int actualY = component.getHeight() - lastKnownLocation.y;
+            MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY,
+                                                        newX - lastEventX,
+                                                        lastEventY - newY,
+                                                        wheelPos, lastEventWheel - wheelPos);
+            listener.onMouseMotionEvent(evt);
+
+            lastEventX = newX;
+            lastEventY = newY;
+            lastEventWheel = newWheel;
+            
+            cursorMoved = false;
+        }
+
+        synchronized (eventQueue){
+            eventQueueCopy.clear();
+            eventQueueCopy.addAll(eventQueue);
+            eventQueue.clear();
+        }
+        
+        int size = eventQueueCopy.size();
+        for (int i = 0; i < size; i++){
+            listener.onMouseButtonEvent(eventQueueCopy.get(i));
+        }
+    }
+
+    private Cursor getTransparentCursor() {
+        if (transparentCursor == null){
+            BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+            cursorImage.setRGB(0, 0, 0);
+            transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "empty cursor");
+        }
+        return transparentCursor;
+    }
+
+//	public void setHardwareCursor(URL file, int xHotspot, int yHotspot) {
+//	    //Create the image from the provided url
+//	    java.awt.Image cursorImage = new ImageIcon( file ).getImage( );
+//	    //Create a custom cursor with this image
+//	    opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" );
+//	    //Use this cursor
+//	    setCursorVisible( isCursorVisible );
+//	}
+
+
+    public int getButtonCount() {
+        return 3;
+    }
+
+    public void mouseClicked(MouseEvent arg0) {
+//        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false);
+//        listener.onMouseButtonEvent(evt);
+    }
+
+    public void mousePressed(MouseEvent arg0) {
+        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), true, arg0.getX(), arg0.getY());
+        evt.setTime(arg0.getWhen());
+        synchronized (eventQueue){
+            eventQueue.add(evt);
+        }
+    }
+
+    public void mouseReleased(MouseEvent arg0) {
+        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false, arg0.getX(), arg0.getY());
+        evt.setTime(arg0.getWhen());
+        synchronized (eventQueue){
+            eventQueue.add(evt);
+        }
+    }
+
+    public void mouseEntered(MouseEvent arg0) {
+        if (!visible)
+            recenterMouse(arg0.getComponent());
+    }
+
+    public void mouseExited(MouseEvent arg0) {
+        if (!visible)
+            recenterMouse(arg0.getComponent());
+    }
+
+    public void mouseWheelMoved(MouseWheelEvent arg0) {
+        int dwheel = arg0.getUnitsToScroll();
+        wheelPos += dwheel * WHEEL_AMP;
+        cursorMoved = true;
+    }
+
+    public void mouseDragged(MouseEvent arg0) {
+        mouseMoved(arg0);
+    }
+
+    public void mouseMoved(MouseEvent arg0) {
+        if (isRecentering) {
+            // MHenze (cylab) Fix Issue 35:
+            // As long as the MouseInput is in recentering mode, nothing is done until the mouse is entered in the component
+            // by the events generated by the robot. If this happens, the last known location is resetted.
+            if ((centerLocation.x == arg0.getX() && centerLocation.y == arg0.getY()) || eventsSinceRecenter++ == 5) {
+                lastKnownLocation.x = arg0.getX();
+                lastKnownLocation.y = arg0.getY();
+                isRecentering = false;
+            }
+        } else {
+            // MHenze (cylab) Fix Issue 35:
+            // Compute the delta and absolute coordinates and recenter the mouse if necessary
+            int dx = arg0.getX() - lastKnownLocation.x;
+            int dy = arg0.getY() - lastKnownLocation.y;
+            location.x += dx;
+            location.y += dy;
+            if (!visible) {
+                recenterMouse(arg0.getComponent());
+            }
+            lastKnownLocation.x = arg0.getX();
+            lastKnownLocation.y = arg0.getY();
+            
+            cursorMoved = true;
+        }
+    }
+
+    // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse
+    private void recenterMouse(final Component component) {
+        if (robot != null) {
+            eventsSinceRecenter = 0;
+            isRecentering = true;
+            centerLocation.setLocation(component.getWidth()/2, component.getHeight()/2);
+            centerLocationOnScreen.setLocation(centerLocation);
+            SwingUtilities.convertPointToScreen(centerLocationOnScreen, component);
+            robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y);
+        }
+    }
+
+    private int getJMEButtonIndex( MouseEvent arg0 ) {
+        int index;
+        switch (arg0.getButton()) {
+            default:
+            case MouseEvent.BUTTON1: //left
+                index = MouseInput.BUTTON_LEFT;
+                break;
+            case MouseEvent.BUTTON2: //middle
+                index = MouseInput.BUTTON_MIDDLE;
+                break;
+            case MouseEvent.BUTTON3: //right
+                index = MouseInput.BUTTON_RIGHT;
+                break;
+        }
+        return index;
+    }
+}
diff --git a/engine/src/desktop/com/jme3/system/JmeCanvasContext.java b/engine/src/desktop/com/jme3/system/JmeCanvasContext.java
new file mode 100644
index 0000000..1d250b5
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/JmeCanvasContext.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import java.awt.Canvas;
+
+public interface JmeCanvasContext extends JmeContext {
+    public Canvas getCanvas();
+}
diff --git a/engine/src/desktop/com/jme3/system/JmeDesktopSystem.java b/engine/src/desktop/com/jme3/system/JmeDesktopSystem.java
new file mode 100644
index 0000000..9a8eac4
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/JmeDesktopSystem.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import com.jme3.app.SettingsDialog;
+import com.jme3.app.SettingsDialog.SelectionListener;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.system.JmeContext.Type;
+import java.io.IOException;
+import java.net.URL;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import javax.swing.SwingUtilities;
+
+/**
+ *
+ * @author Kirill Vainer, normenhansen
+ */
+public class JmeDesktopSystem extends JmeSystemDelegate {
+
+    @Override
+    public AssetManager newAssetManager(URL configFile) {
+        return new DesktopAssetManager(configFile);
+    }
+
+    @Override
+    public AssetManager newAssetManager() {
+        return new DesktopAssetManager(null);
+    }
+
+    @Override
+    public boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
+        if (SwingUtilities.isEventDispatchThread()) {
+            throw new IllegalStateException("Cannot run from EDT");
+        }
+
+        final AppSettings settings = new AppSettings(false);
+        settings.copyFrom(sourceSettings);
+        String iconPath = sourceSettings.getSettingsDialogImage();
+        final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath);
+        if (iconUrl == null) {
+            throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage());
+        }
+
+        final AtomicBoolean done = new AtomicBoolean();
+        final AtomicInteger result = new AtomicInteger();
+        final Object lock = new Object();
+
+        final SelectionListener selectionListener = new SelectionListener() {
+
+            public void onSelection(int selection) {
+                synchronized (lock) {
+                    done.set(true);
+                    result.set(selection);
+                    lock.notifyAll();
+                }
+            }
+        };
+        SwingUtilities.invokeLater(new Runnable() {
+
+            public void run() {
+                synchronized (lock) {
+                    SettingsDialog dialog = new SettingsDialog(settings, iconUrl, loadFromRegistry);
+                    dialog.setSelectionListener(selectionListener);
+                    dialog.showDialog();
+                }
+            }
+        });
+
+        synchronized (lock) {
+            while (!done.get()) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+
+        sourceSettings.copyFrom(settings);
+
+        return result.get() == SettingsDialog.APPROVE_SELECTION;
+    }
+
+    private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) {
+        try {
+            Class<? extends JmeContext> ctxClazz = null;
+            switch (type) {
+                case Canvas:
+                    ctxClazz = (Class<? extends JmeContext>) Class.forName("com.jme3.system.lwjgl.LwjglCanvas");
+                    break;
+                case Display:
+                    ctxClazz = (Class<? extends JmeContext>) Class.forName("com.jme3.system.lwjgl.LwjglDisplay");
+                    break;
+                case OffscreenSurface:
+                    ctxClazz = (Class<? extends JmeContext>) Class.forName("com.jme3.system.lwjgl.LwjglOffscreenBuffer");
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported context type " + type);
+            }
+
+            return ctxClazz.newInstance();
+        } catch (InstantiationException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (IllegalAccessException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (ClassNotFoundException ex) {
+            logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n"
+                    + "Make sure jme3_lwjgl-ogl is on the classpath.", ex);
+        }
+
+        return null;
+    }
+
+    private JmeContext newContextJogl(AppSettings settings, JmeContext.Type type) {
+        try {
+            Class<? extends JmeContext> ctxClazz = null;
+            switch (type) {
+                case Display:
+                    ctxClazz = (Class<? extends JmeContext>) Class.forName("com.jme3.system.jogl.JoglDisplay");
+                    break;
+                case Canvas:
+                    ctxClazz = (Class<? extends JmeContext>) Class.forName("com.jme3.system.jogl.JoglCanvas");
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported context type " + type);
+            }
+
+            return ctxClazz.newInstance();
+        } catch (InstantiationException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (IllegalAccessException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (ClassNotFoundException ex) {
+            logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n"
+                    + "Make sure jme3_jogl is on the classpath.", ex);
+        }
+
+        return null;
+    }
+
+    private JmeContext newContextCustom(AppSettings settings, JmeContext.Type type) {
+        try {
+            String className = settings.getRenderer().substring("CUSTOM".length());
+
+            Class<? extends JmeContext> ctxClazz = null;
+            ctxClazz = (Class<? extends JmeContext>) Class.forName(className);
+            return ctxClazz.newInstance();
+        } catch (InstantiationException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (IllegalAccessException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (ClassNotFoundException ex) {
+            logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!", ex);
+        }
+
+        return null;
+    }
+
+    @Override
+    public JmeContext newContext(AppSettings settings, Type contextType) {
+        initialize(settings);
+        JmeContext ctx;
+        if (settings.getRenderer() == null
+                || settings.getRenderer().equals("NULL")
+                || contextType == JmeContext.Type.Headless) {
+            ctx = new NullContext();
+            ctx.setSettings(settings);
+        } else if (settings.getRenderer().startsWith("LWJGL")) {
+            ctx = newContextLwjgl(settings, contextType);
+            ctx.setSettings(settings);
+        } else if (settings.getRenderer().startsWith("JOGL")) {
+            ctx = newContextJogl(settings, contextType);
+            ctx.setSettings(settings);
+        } else if (settings.getRenderer().startsWith("CUSTOM")) {
+            ctx = newContextCustom(settings, contextType);
+            ctx.setSettings(settings);
+        } else {
+            throw new UnsupportedOperationException(
+                    "Unrecognizable renderer specified: "
+                    + settings.getRenderer());
+        }
+        return ctx;
+    }
+
+    @Override
+    public AudioRenderer newAudioRenderer(AppSettings settings) {
+        initialize(settings);
+        Class<? extends AudioRenderer> clazz = null;
+        try {
+            if (settings.getAudioRenderer().startsWith("LWJGL")) {
+                clazz = (Class<? extends AudioRenderer>) Class.forName("com.jme3.audio.lwjgl.LwjglAudioRenderer");
+            } else if (settings.getAudioRenderer().startsWith("JOAL")) {
+                clazz = (Class<? extends AudioRenderer>) Class.forName("com.jme3.audio.joal.JoalAudioRenderer");
+            } else {
+                throw new UnsupportedOperationException(
+                        "Unrecognizable audio renderer specified: "
+                        + settings.getAudioRenderer());
+            }
+
+            AudioRenderer ar = clazz.newInstance();
+            return ar;
+        } catch (InstantiationException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (IllegalAccessException ex) {
+            logger.log(Level.SEVERE, "Failed to create context", ex);
+        } catch (ClassNotFoundException ex) {
+            logger.log(Level.SEVERE, "CRITICAL ERROR: Audio implementation class is missing!\n"
+                    + "Make sure jme3_lwjgl-oal or jm3_joal is on the classpath.", ex);
+        }
+        return null;
+    }
+
+    @Override
+    public void initialize(AppSettings settings) {
+        if (initialized) {
+            return;
+        }
+
+        initialized = true;
+        try {
+            if (!lowPermissions) {
+                // can only modify logging settings
+                // if permissions are available
+//                JmeFormatter formatter = new JmeFormatter();
+//                Handler fileHandler = new FileHandler("jme.log");
+//                fileHandler.setFormatter(formatter);
+//                Logger.getLogger("").addHandler(fileHandler);
+//                Handler consoleHandler = new ConsoleHandler();
+//                consoleHandler.setFormatter(formatter);
+//                Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
+//                Logger.getLogger("").addHandler(consoleHandler);
+            }
+//        } catch (IOException ex){
+//            logger.log(Level.SEVERE, "I/O Error while creating log file", ex);
+        } catch (SecurityException ex) {
+            logger.log(Level.SEVERE, "Security error in creating log file", ex);
+        }
+        logger.log(Level.INFO, "Running on {0}", getFullName());
+
+        if (!lowPermissions) {
+            try {
+                Natives.extractNativeLibs(getPlatform(), settings);
+            } catch (IOException ex) {
+                logger.log(Level.SEVERE, "Error while copying native libraries", ex);
+            }
+        }
+    }
+}
diff --git a/engine/src/desktop/com/jme3/system/Natives.java b/engine/src/desktop/com/jme3/system/Natives.java
new file mode 100644
index 0000000..979c606
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/Natives.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Helper class for extracting the natives (dll, so) from the jars.
+ * This class should only be used internally.
+ */
+public final class Natives {
+
+    private static final Logger logger = Logger.getLogger(Natives.class.getName());
+    private static final byte[] buf = new byte[1024];
+    private static File extractionDirOverride = null;
+    private static File extractionDir = null;
+
+    public static void setExtractionDir(String name) {
+        extractionDirOverride = new File(name).getAbsoluteFile();
+    }
+
+    public static File getExtractionDir() {
+        if (extractionDirOverride != null) {
+            return extractionDirOverride;
+        }
+        if (extractionDir == null) {
+            File workingFolder = new File("").getAbsoluteFile();
+            if (!workingFolder.canWrite()) {
+                setStorageExtractionDir();
+            } else {
+                try {
+                    File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite");
+                    file.createNewFile();
+                    file.delete();
+                    extractionDir = workingFolder;
+                } catch (Exception e) {
+                    setStorageExtractionDir();
+                }
+            }
+        }
+        return extractionDir;
+    }
+
+    private static void setStorageExtractionDir() {
+        logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead.");
+        extractionDir = new File(JmeSystem.getStorageFolder(),
+                "natives_" + Integer.toHexString(computeNativesHash()));
+        if (!extractionDir.exists()) {
+            extractionDir.mkdir();
+        }
+    }
+
+    private static int computeNativesHash() {
+        try {
+            String classpath = System.getProperty("java.class.path");
+            URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class");
+
+            StringBuilder sb = new StringBuilder(url.toString());
+            if (sb.indexOf("jar:") == 0) {
+                sb.delete(0, 4);
+                sb.delete(sb.indexOf("!"), sb.length());
+                sb.delete(sb.lastIndexOf("/") + 1, sb.length());
+            }
+            try {
+                url = new URL(sb.toString());
+            } catch (MalformedURLException ex) {
+                throw new UnsupportedOperationException(ex);
+            }
+
+            URLConnection conn = url.openConnection();
+            int hash = classpath.hashCode() ^ (int) conn.getLastModified();
+            return hash;
+        } catch (IOException ex) {
+            throw new UnsupportedOperationException(ex);
+        }
+    }
+
+    public static void extractNativeLib(String sysName, String name) throws IOException {
+        extractNativeLib(sysName, name, false, true);
+    }
+
+    public static void extractNativeLib(String sysName, String name, boolean load) throws IOException {
+        extractNativeLib(sysName, name, load, true);
+    }
+
+    public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException {
+        String fullname = System.mapLibraryName(name);
+
+        String path = "native/" + sysName + "/" + fullname;
+        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
+
+        if (url == null) {
+            if (!warning) {
+                logger.log(Level.WARNING, "Cannot locate native library: {0}/{1}",
+                        new String[]{sysName, fullname});
+            }
+            return;
+        }
+
+        URLConnection conn = url.openConnection();
+        InputStream in = conn.getInputStream();
+        File targetFile = new File(getExtractionDir(), fullname);
+        OutputStream out = null;
+        try {
+            if (targetFile.exists()) {
+                // OK, compare last modified date of this file to 
+                // file in jar
+                long targetLastModified = targetFile.lastModified();
+                long sourceLastModified = conn.getLastModified();
+
+                // Allow ~1 second range for OSes that only support low precision
+                if (targetLastModified + 1000 > sourceLastModified) {
+                    logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname);
+                    return;
+                }
+            }
+
+            out = new FileOutputStream(targetFile);
+            int len;
+            while ((len = in.read(buf)) > 0) {
+                out.write(buf, 0, len);
+            }
+            in.close();
+            in = null;
+            out.close();
+            out = null;
+
+            // NOTE: On OSes that support "Date Created" property, 
+            // this will cause the last modified date to be lower than
+            // date created which makes no sense
+            targetFile.setLastModified(conn.getLastModified());
+        } catch (FileNotFoundException ex) {
+            if (ex.getMessage().contains("used by another process")) {
+                return;
+            }
+
+            throw ex;
+        } finally {
+            if (load) {
+                System.load(targetFile.getAbsolutePath());
+            }
+            if(in != null){
+                in.close();
+            }
+            if(out != null){
+                out.close();
+            }
+        }
+        logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile});
+    }
+
+    protected static boolean isUsingNativeBullet() {
+        try {
+            Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil");
+            return clazz != null;
+        } catch (ClassNotFoundException ex) {
+            return false;
+        }
+    }
+
+    public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException {
+        String renderer = settings.getRenderer();
+        String audioRenderer = settings.getAudioRenderer();
+        boolean needLWJGL = false;
+        boolean needOAL = false;
+        boolean needJInput = false;
+        boolean needNativeBullet = isUsingNativeBullet();
+        
+        if (renderer != null) {
+            if (renderer.startsWith("LWJGL")) {
+                needLWJGL = true;
+            }
+        }
+        if (audioRenderer != null) {
+            if (audioRenderer.equals("LWJGL")) {
+                needLWJGL = true;
+                needOAL = true;
+            }
+        }
+        needJInput = settings.useJoysticks();
+
+        String libraryPath = getExtractionDir().toString();
+        if (needLWJGL) {
+            logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString());
+
+            // LWJGL supports this feature where
+            // it can load libraries from this path.
+            System.setProperty("org.lwjgl.librarypath", libraryPath);
+        }
+        if (needJInput) {
+            // AND Luckily enough JInput supports the same feature.
+            System.setProperty("net.java.games.input.librarypath", libraryPath);
+        }
+
+        switch (platform) {
+            case Windows64:
+                if (needLWJGL) {
+                    extractNativeLib("windows", "lwjgl64");
+                }
+                if (needOAL) {
+                    extractNativeLib("windows", "OpenAL64");
+                }
+                if (needJInput) {
+                    extractNativeLib("windows", "jinput-dx8_64");
+                    extractNativeLib("windows", "jinput-raw_64");
+                }
+                if (needNativeBullet) {
+                    extractNativeLib("windows", "bulletjme64", true, false);
+                }
+                break;
+            case Windows32:
+                if (needLWJGL) {
+                    extractNativeLib("windows", "lwjgl");
+                }
+                if (needOAL) {
+                    extractNativeLib("windows", "OpenAL32");
+                }
+                if (needJInput) {
+                    extractNativeLib("windows", "jinput-dx8");
+                    extractNativeLib("windows", "jinput-raw");
+                }
+                if (needNativeBullet) {
+                    extractNativeLib("windows", "bulletjme", true, false);
+                }
+                break;
+            case Linux64:
+                if (needLWJGL) {
+                    extractNativeLib("linux", "lwjgl64");
+                }
+                if (needJInput) {
+                    extractNativeLib("linux", "jinput-linux64");
+                }
+                if (needOAL) {
+                    extractNativeLib("linux", "openal64");
+                }
+                if (needNativeBullet) {
+                    extractNativeLib("linux", "bulletjme64", true, false);
+                }
+                break;
+            case Linux32:
+                if (needLWJGL) {
+                    extractNativeLib("linux", "lwjgl");
+                }
+                if (needJInput) {
+                    extractNativeLib("linux", "jinput-linux");
+                }
+                if (needOAL) {
+                    extractNativeLib("linux", "openal");
+                }
+                if (needNativeBullet) {
+                    extractNativeLib("linux", "bulletjme", true, false);
+                }
+                break;
+            case MacOSX_PPC32:
+            case MacOSX32:
+            case MacOSX_PPC64:
+            case MacOSX64:
+                if (needLWJGL) {
+                    extractNativeLib("macosx", "lwjgl");
+                }
+//                if (needOAL)
+//                    extractNativeLib("macosx", "openal");
+                if (needJInput) {
+                    extractNativeLib("macosx", "jinput-osx");
+                }
+                if (needNativeBullet) {
+                    extractNativeLib("macosx", "bulletjme", true, false);
+                }
+                break;
+        }
+    }
+}
diff --git a/engine/src/desktop/com/jme3/system/awt/AwtPanel.java b/engine/src/desktop/com/jme3/system/awt/AwtPanel.java
new file mode 100644
index 0000000..b84565b
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/awt/AwtPanel.java
@@ -0,0 +1,293 @@
+package com.jme3.system.awt;
+
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.Screenshots;
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferStrategy;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class AwtPanel extends Canvas implements SceneProcessor {
+
+    private boolean attachAsMain = false;
+    
+    private BufferedImage img;
+    private FrameBuffer fb;
+    private ByteBuffer byteBuf;
+    private IntBuffer intBuf;
+    private RenderManager rm;
+    private PaintMode paintMode;
+    private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); 
+    
+    // Visibility/drawing vars
+    private BufferStrategy strategy;
+    private AffineTransformOp transformOp;
+    private AtomicBoolean hasNativePeer = new AtomicBoolean(false);
+    private AtomicBoolean showing = new AtomicBoolean(false);
+    private AtomicBoolean repaintRequest = new AtomicBoolean(false);
+    
+    // Reshape vars
+    private int newWidth  = 1;
+    private int newHeight = 1;
+    private AtomicBoolean reshapeNeeded  = new AtomicBoolean(false);
+    private final Object lock = new Object();
+    
+    public AwtPanel(PaintMode paintMode){
+        this.paintMode = paintMode;
+        
+        if (paintMode == PaintMode.Accelerated){
+            setIgnoreRepaint(true);
+        }
+        
+        addComponentListener(new ComponentAdapter(){
+            @Override
+            public void componentResized(ComponentEvent e) {
+                synchronized (lock){
+                    int newWidth2 = Math.max(getWidth(), 1);
+                    int newHeight2 = Math.max(getHeight(), 1);
+                    if (newWidth != newWidth2 || newHeight != newHeight2){
+                        newWidth = newWidth2;
+                        newHeight = newHeight2;
+                        reshapeNeeded.set(true);
+                        System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
+                    }
+                }
+            }
+        });
+    }
+    
+    @Override
+    public void addNotify(){
+        super.addNotify();
+
+        synchronized (lock){
+            hasNativePeer.set(true);
+            System.out.println("EDT: addNotify");
+        }
+        
+        requestFocusInWindow();
+    }
+
+    @Override
+    public void removeNotify(){
+        synchronized (lock){
+            hasNativePeer.set(false);
+            System.out.println("EDT: removeNotify");
+        }
+        
+        super.removeNotify();
+    }
+    
+    @Override
+    public void paint(Graphics g){
+        Graphics2D g2d = (Graphics2D) g;
+        synchronized (lock){
+            g2d.drawImage(img, transformOp, 0, 0);
+        }
+    }
+    
+    public boolean checkVisibilityState(){
+        if (!hasNativePeer.get()){
+            if (strategy != null){
+//                strategy.dispose();
+                strategy = null;
+                System.out.println("OGL: Not visible. Destroy strategy.");
+            }
+            return false;
+        }
+        
+        boolean currentShowing = isShowing();
+        if (showing.getAndSet(currentShowing) != currentShowing){
+            if (currentShowing){
+                System.out.println("OGL: Enter showing state.");
+            }else{
+                System.out.println("OGL: Exit showing state.");
+            }
+        }
+        return currentShowing;
+    }
+    
+    public void repaintInThread(){
+        // Convert screenshot.
+        byteBuf.clear();
+        rm.getRenderer().readFrameBuffer(fb, byteBuf);
+        
+        synchronized (lock){
+            // All operations on img must be synchronized
+            // as it is accessed from EDT.
+            Screenshots.convertScreenShot2(intBuf, img);
+            repaint();
+        }
+    }
+    
+    public void drawFrameInThread(){
+        // Convert screenshot.
+        byteBuf.clear();
+        rm.getRenderer().readFrameBuffer(fb, byteBuf);
+        Screenshots.convertScreenShot2(intBuf, img);
+        
+        synchronized (lock){
+            // All operations on strategy should be synchronized (?)
+            if (strategy == null){
+                try {
+                    createBufferStrategy(1, 
+                            new BufferCapabilities(
+                                new ImageCapabilities(true), 
+                                new ImageCapabilities(true), 
+                                BufferCapabilities.FlipContents.UNDEFINED)
+                                        );
+                } catch (AWTException ex) {
+                    ex.printStackTrace();
+                }
+                strategy = getBufferStrategy();
+                System.out.println("OGL: Visible. Create strategy.");
+            }
+            
+            // Draw screenshot.
+            do {
+                do {
+                    Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
+                    if (g2d == null){
+                        System.out.println("OGL: DrawGraphics was null.");
+                        return;
+                    }
+                    
+                    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+                                         RenderingHints.VALUE_RENDER_SPEED);
+                    
+                    g2d.drawImage(img, transformOp, 0, 0);
+                    g2d.dispose();
+                    strategy.show();
+                } while (strategy.contentsRestored());
+            } while (strategy.contentsLost());
+        }
+    }
+    
+    public boolean isActiveDrawing(){
+        return paintMode != PaintMode.OnRequest && showing.get();
+    }
+    
+    public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){
+        if (viewPorts.size() > 0){
+            for (ViewPort vp : viewPorts){
+                vp.setOutputFrameBuffer(null);
+            }
+            viewPorts.get(viewPorts.size()-1).removeProcessor(this);
+        }
+        
+        viewPorts.addAll(Arrays.asList(vps));
+        viewPorts.get(viewPorts.size()-1).addProcessor(this);
+        
+        this.attachAsMain = overrideMainFramebuffer;
+    }
+    
+    public void initialize(RenderManager rm, ViewPort vp) {
+        if (this.rm == null){
+            // First time called in OGL thread
+            this.rm = rm;
+            reshapeInThread(1, 1);
+        }
+    }
+
+    private void reshapeInThread(int width, int height) {
+        byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
+        intBuf = byteBuf.asIntBuffer();
+        
+        fb = new FrameBuffer(width, height, 1);
+        fb.setDepthBuffer(Format.Depth);
+        fb.setColorBuffer(Format.RGB8);
+        
+        if (attachAsMain){
+            rm.getRenderer().setMainFrameBufferOverride(fb);
+        }
+        
+        synchronized (lock){
+            img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        }
+        
+//        synchronized (lock){
+//            img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
+//        }
+        
+        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
+        tx.translate(0, -img.getHeight());
+        transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
+        
+        for (ViewPort vp : viewPorts){
+            if (!attachAsMain){
+                vp.setOutputFrameBuffer(fb);
+            }
+            vp.getCamera().resize(width, height, true);
+            
+            // NOTE: Hack alert. This is done ONLY for custom framebuffers.
+            // Main framebuffer should use RenderManager.notifyReshape().
+            for (SceneProcessor sp : vp.getProcessors()){
+                sp.reshape(vp, width, height);
+            }
+        }
+    }
+
+    public boolean isInitialized() {
+        return fb != null;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+    }
+    
+    @Override
+    public void invalidate(){
+        // For "PaintMode.OnDemand" only.
+        repaintRequest.set(true);
+    }
+
+    public void postFrame(FrameBuffer out) {
+        if (!attachAsMain && out != fb){
+            throw new IllegalStateException("Why did you change the output framebuffer?");
+        }
+        
+        if (reshapeNeeded.getAndSet(false)){
+            reshapeInThread(newWidth, newHeight);
+        }else{
+            if (!checkVisibilityState()){
+                return;
+            }
+            
+            switch (paintMode){
+                case Accelerated:
+                    drawFrameInThread();
+                    break;
+                case Repaint:
+                    repaintInThread();
+                    break;
+                case OnRequest:
+                    if (repaintRequest.getAndSet(false)){
+                        repaintInThread();
+                    }
+                    break;
+            }
+        }
+    }
+    
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    public void cleanup() {
+    }
+}
diff --git a/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java b/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java
new file mode 100644
index 0000000..c980c40
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java
@@ -0,0 +1,202 @@
+package com.jme3.system.awt;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.awt.AwtKeyInput;
+import com.jme3.input.awt.AwtMouseInput;
+import com.jme3.renderer.Renderer;
+import com.jme3.system.*;
+import java.util.ArrayList;
+
+public class AwtPanelsContext implements JmeContext {
+
+    protected JmeContext actualContext;
+    protected AppSettings settings = new AppSettings(true);
+    protected SystemListener listener;
+    protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>();
+    protected AwtPanel inputSource;
+    
+    protected AwtMouseInput mouseInput = new AwtMouseInput();
+    protected AwtKeyInput keyInput = new AwtKeyInput();
+    
+    protected boolean lastThrottleState = false;
+    
+    private class AwtPanelsListener implements SystemListener {
+
+        public void initialize() {
+            initInThread();
+        }
+
+        public void reshape(int width, int height) {
+            throw new IllegalStateException();
+        }
+
+        public void update() {
+            updateInThread();
+        }
+
+        public void requestClose(boolean esc) {
+            // shouldn't happen
+            throw new IllegalStateException();
+        }
+
+        public void gainFocus() {
+            // shouldn't happen
+            throw new IllegalStateException();
+        }
+
+        public void loseFocus() {
+            // shouldn't happen
+            throw new IllegalStateException();
+        }
+
+        public void handleError(String errorMsg, Throwable t) {
+            listener.handleError(errorMsg, t);
+        }
+
+        public void destroy() {
+            destroyInThread();
+        }
+    }
+    
+    public void setInputSource(AwtPanel panel){
+        if (!panels.contains(panel))
+            throw new IllegalArgumentException();
+        
+        inputSource = panel;
+        mouseInput.setInputSource(panel);
+        keyInput.setInputSource(panel);
+    }
+    
+    public Type getType() {
+        return Type.OffscreenSurface;
+    }
+    
+    public void setSystemListener(SystemListener listener) {
+        this.listener = listener;
+    }
+
+    public AppSettings getSettings() {
+        return settings;
+    }
+
+    public Renderer getRenderer() {
+        return actualContext.getRenderer();
+    }
+
+    public MouseInput getMouseInput() {
+        return mouseInput;
+    }
+
+    public KeyInput getKeyInput() {
+        return keyInput;
+    }
+
+    public JoyInput getJoyInput() {
+        return null;
+    }
+
+    public TouchInput getTouchInput() {
+        return null;
+    }
+
+    public Timer getTimer() {
+        return actualContext.getTimer();
+    }
+
+    public boolean isCreated() {
+        return actualContext != null && actualContext.isCreated();
+    }
+
+    public boolean isRenderable() {
+        return actualContext != null && actualContext.isRenderable();
+    }
+    
+    public AwtPanelsContext(){
+    }
+    
+    public AwtPanel createPanel(PaintMode paintMode){
+        AwtPanel panel = new AwtPanel(paintMode);
+        panels.add(panel);
+        return panel;
+    }
+    
+    private void initInThread(){
+        listener.initialize();
+    }
+    
+    private void updateInThread(){
+        // Check if throttle required
+        boolean needThrottle = true;
+        
+        for (AwtPanel panel : panels){
+            if (panel.isActiveDrawing()){
+                needThrottle = false;
+                break;
+            }
+        }
+        
+        if (lastThrottleState != needThrottle){
+            lastThrottleState = needThrottle;
+            if (lastThrottleState){
+                System.out.println("OGL: Throttling update loop.");
+            }else{
+                System.out.println("OGL: Ceased throttling update loop.");
+            }
+        }
+        
+        if (needThrottle){
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+            }
+        }
+        
+        listener.update();
+    }
+    
+    private void destroyInThread(){
+        listener.destroy();
+    }
+    
+    public void setSettings(AppSettings settings) {
+        this.settings.copyFrom(settings);
+        this.settings.setRenderer(AppSettings.LWJGL_OPENGL2);
+        if (actualContext != null){
+            actualContext.setSettings(settings);
+        }
+    }
+
+    public void create(boolean waitFor) {
+        if (actualContext != null){
+            throw new IllegalStateException("Already created");
+        }
+        
+        actualContext = JmeSystem.newContext(settings, Type.OffscreenSurface);
+        actualContext.setSystemListener(new AwtPanelsListener());
+        actualContext.create(waitFor);
+    }
+
+    public void destroy(boolean waitFor) {
+        if (actualContext == null)
+            throw new IllegalStateException("Not created");
+        
+        // destroy parent context
+        actualContext.destroy(waitFor);
+    }
+    
+    public void setTitle(String title) {
+        // not relevant, ignore
+    }
+    
+    public void setAutoFlushFrames(boolean enabled) {
+        // not relevant, ignore
+    }
+
+    public void restart() {
+        // only relevant if changing pixel format.
+    }
+    
+}
diff --git a/engine/src/desktop/com/jme3/system/awt/PaintMode.java b/engine/src/desktop/com/jme3/system/awt/PaintMode.java
new file mode 100644
index 0000000..66938dd
--- /dev/null
+++ b/engine/src/desktop/com/jme3/system/awt/PaintMode.java
@@ -0,0 +1,7 @@
+package com.jme3.system.awt;
+
+public enum PaintMode {
+    Accelerated,
+    Repaint,
+    OnRequest;
+}
diff --git a/engine/src/desktop/com/jme3/texture/plugins/AWTLoader.java b/engine/src/desktop/com/jme3/texture/plugins/AWTLoader.java
new file mode 100644
index 0000000..3f471f7
--- /dev/null
+++ b/engine/src/desktop/com/jme3/texture/plugins/AWTLoader.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2009-2010 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.texture.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import javax.imageio.ImageIO;
+
+public class AWTLoader implements AssetLoader {
+
+    public static final ColorModel AWT_RGBA4444 = new DirectColorModel(16,
+                                                                       0xf000,
+                                                                       0x0f00,
+                                                                       0x00f0,
+                                                                       0x000f);
+
+    public static final ColorModel AWT_RGBA5551
+            = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 
+                                      new int[]{5, 5, 5, 1},
+                                      true,
+                                      false,
+                                      Transparency.BITMASK,
+                                      DataBuffer.TYPE_BYTE);
+
+    private Object extractImageData(BufferedImage img){
+        DataBuffer buf = img.getRaster().getDataBuffer();
+        switch (buf.getDataType()){
+            case DataBuffer.TYPE_BYTE:
+                DataBufferByte byteBuf = (DataBufferByte) buf;
+                return byteBuf.getData();
+            case DataBuffer.TYPE_USHORT:
+                DataBufferUShort shortBuf = (DataBufferUShort) buf;
+                return shortBuf.getData();
+        }
+        return null;
+    }
+
+    private void flipImage(byte[] img, int width, int height, int bpp){
+        int scSz = (width * bpp) / 8;
+        byte[] sln = new byte[scSz];
+        int y2 = 0;
+        for (int y1 = 0; y1 < height / 2; y1++){
+            y2 = height - y1 - 1;
+            System.arraycopy(img, y1 * scSz, sln, 0,         scSz);
+            System.arraycopy(img, y2 * scSz, img, y1 * scSz, scSz);
+            System.arraycopy(sln, 0,         img, y2 * scSz, scSz);
+        }
+    }
+    
+    private void flipImage(short[] img, int width, int height, int bpp){
+        int scSz = (width * bpp) / 8;
+        scSz /= 2; // Because shorts are 2 bytes
+        short[] sln = new short[scSz];
+        int y2 = 0;
+        for (int y1 = 0; y1 < height / 2; y1++){
+            y2 = height - y1 - 1;
+            System.arraycopy(img, y1 * scSz, sln, 0,         scSz);
+            System.arraycopy(img, y2 * scSz, img, y1 * scSz, scSz);
+            System.arraycopy(sln, 0,         img, y2 * scSz, scSz);
+        }
+    }
+
+    public Image load(BufferedImage img, boolean flipY){
+        int width = img.getWidth();
+        int height = img.getHeight();
+
+        switch (img.getType()){
+            case BufferedImage.TYPE_4BYTE_ABGR: // most common in PNG images w/ alpha
+               byte[] dataBuf1 = (byte[]) extractImageData(img);
+               if (flipY)
+                   flipImage(dataBuf1, width, height, 32);
+                
+               ByteBuffer data1 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*4);
+               data1.put(dataBuf1);
+               return new Image(Format.ABGR8, width, height, data1);
+            case BufferedImage.TYPE_3BYTE_BGR: // most common in JPEG images
+               byte[] dataBuf2 = (byte[]) extractImageData(img);
+               if (flipY)
+                   flipImage(dataBuf2, width, height, 24);
+               
+               ByteBuffer data2 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3);
+               data2.put(dataBuf2);
+               return new Image(Format.BGR8, width, height, data2);
+            case BufferedImage.TYPE_BYTE_GRAY: // grayscale fonts
+                byte[] dataBuf3 = (byte[]) extractImageData(img);
+                if (flipY)
+                    flipImage(dataBuf3, width, height, 8);
+                ByteBuffer data3 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight());
+                data3.put(dataBuf3);
+                return new Image(Format.Luminance8, width, height, data3);
+            case BufferedImage.TYPE_USHORT_GRAY: // grayscale heightmap
+                short[] dataBuf4 = (short[]) extractImageData(img);
+                if (flipY)
+                    flipImage(dataBuf4, width, height, 16);
+                
+                ByteBuffer data4 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*2);
+                data4.asShortBuffer().put(dataBuf4);
+                return new Image(Format.Luminance16, width, height, data4);
+            default:
+                break;
+        }
+
+        if (img.getTransparency() == Transparency.OPAQUE){
+            ByteBuffer data = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3);
+            // no alpha
+            for (int y = 0; y < height; y++){
+                for (int x = 0; x < width; x++){
+                    int ny = y;
+                    if (flipY){
+                        ny = height - y - 1;
+                    }
+
+                    int rgb = img.getRGB(x,ny);
+                    byte r = (byte) ((rgb & 0x00FF0000) >> 16);
+                    byte g = (byte) ((rgb & 0x0000FF00) >> 8);
+                    byte b = (byte) ((rgb & 0x000000FF));
+                    data.put(r).put(g).put(b);
+                }
+            }
+            data.flip();
+            return new Image(Format.RGB8, width, height, data);
+        }else{
+            ByteBuffer data = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*4);
+            // no alpha
+            for (int y = 0; y < height; y++){
+                for (int x = 0; x < width; x++){
+                    int ny = y;
+                    if (flipY){
+                        ny = height - y - 1;
+                    }
+
+                    int rgb = img.getRGB(x,ny);
+                    byte a = (byte) ((rgb & 0xFF000000) >> 24);
+                    byte r = (byte) ((rgb & 0x00FF0000) >> 16);
+                    byte g = (byte) ((rgb & 0x0000FF00) >> 8);
+                    byte b = (byte) ((rgb & 0x000000FF));
+                    data.put(r).put(g).put(b).put(a);
+                }
+            }
+            data.flip();
+            return new Image(Format.RGBA8, width, height, data);
+        }
+    }
+
+    public Image load(InputStream in, boolean flipY) throws IOException{
+        ImageIO.setUseCache(false);
+        BufferedImage img = ImageIO.read(in);
+        if (img == null)
+            return null;
+
+        return load(img, flipY);
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        if (ImageIO.getImageWritersBySuffix(info.getKey().getExtension()) != null){
+            
+            boolean flip = ((TextureKey) info.getKey()).isFlipY();
+            InputStream in = null;
+            try {
+                in = info.openStream();
+                Image img = load(in, flip);
+                return img;
+            } finally {
+                if (in != null){
+                    in.close();
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/engine/src/desktop/com/jme3/util/Screenshots.java b/engine/src/desktop/com/jme3/util/Screenshots.java
new file mode 100644
index 0000000..bbb6966
--- /dev/null
+++ b/engine/src/desktop/com/jme3/util/Screenshots.java
@@ -0,0 +1,78 @@
+package com.jme3.util;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.WritableRaster;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+public final class Screenshots {
+    
+    public static void convertScreenShot2(IntBuffer bgraBuf, BufferedImage out){
+        WritableRaster wr = out.getRaster();
+        DataBufferInt db = (DataBufferInt) wr.getDataBuffer();
+        
+        int[] cpuArray = db.getData();
+        
+        bgraBuf.clear();
+        bgraBuf.get(cpuArray);
+        
+//        int width  = wr.getWidth();
+//        int height = wr.getHeight();
+//
+//        // flip the components the way AWT likes them
+//        for (int y = 0; y < height / 2; y++){
+//            for (int x = 0; x < width; x++){
+//                int inPtr  = (y * width + x);
+//                int outPtr = ((height-y-1) * width + x);
+//                int pixel = cpuArray[inPtr];
+//                cpuArray[inPtr] = cpuArray[outPtr];
+//                cpuArray[outPtr] = pixel;
+//            }
+//        }
+    }
+    
+    public static void convertScreenShot(ByteBuffer bgraBuf, BufferedImage out){
+        WritableRaster wr = out.getRaster();
+        DataBufferByte db = (DataBufferByte) wr.getDataBuffer();
+
+        byte[] cpuArray = db.getData();
+
+        // copy native memory to java memory
+        bgraBuf.clear();
+        bgraBuf.get(cpuArray);
+        bgraBuf.clear();
+
+        int width  = wr.getWidth();
+        int height = wr.getHeight();
+
+        // flip the components the way AWT likes them
+        for (int y = 0; y < height / 2; y++){
+            for (int x = 0; x < width; x++){
+                int inPtr  = (y * width + x) * 4;
+                int outPtr = ((height-y-1) * width + x) * 4;
+
+                byte b1 = cpuArray[inPtr+0];
+                byte g1 = cpuArray[inPtr+1];
+                byte r1 = cpuArray[inPtr+2];
+                byte a1 = cpuArray[inPtr+3];
+
+                byte b2 = cpuArray[outPtr+0];
+                byte g2 = cpuArray[outPtr+1];
+                byte r2 = cpuArray[outPtr+2];
+                byte a2 = cpuArray[outPtr+3];
+
+                cpuArray[outPtr+0] = a1;
+                cpuArray[outPtr+1] = b1;
+                cpuArray[outPtr+2] = g1;
+                cpuArray[outPtr+3] = r1;
+
+                cpuArray[inPtr+0] = a2;
+                cpuArray[inPtr+1] = b2;
+                cpuArray[inPtr+2] = g2;
+                cpuArray[inPtr+3] = r2;
+            }
+        }
+    }
+}
diff --git a/engine/src/desktop/jme3tools/converters/ImageToAwt.java b/engine/src/desktop/jme3tools/converters/ImageToAwt.java
new file mode 100644
index 0000000..836cc7a
--- /dev/null
+++ b/engine/src/desktop/jme3tools/converters/ImageToAwt.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters;
+
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.plugins.AWTLoader;
+import com.jme3.util.BufferUtils;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.EnumMap;
+
+public class ImageToAwt {
+
+    private static final EnumMap<Format, DecodeParams> params
+            = new EnumMap<Format, DecodeParams>(Format.class);
+
+    private static class DecodeParams {
+
+        final int bpp, am, rm, gm, bm, as, rs, gs, bs, im, is;
+
+        public DecodeParams(int bpp, int am, int rm, int gm, int bm, int as, int rs, int gs, int bs, int im, int is) {
+            this.bpp = bpp;
+            this.am = am;
+            this.rm = rm;
+            this.gm = gm;
+            this.bm = bm;
+            this.as = as;
+            this.rs = rs;
+            this.gs = gs;
+            this.bs = bs;
+            this.im = im;
+            this.is = is;
+        }
+
+        public DecodeParams(int bpp, int rm, int rs, int im, int is, boolean alpha){
+            this.bpp = bpp;
+            if (alpha){
+                this.am = rm;
+                this.as = rs;
+                this.rm = 0;
+                this.rs = 0;
+            }else{
+                this.rm = rm;
+                this.rs = rs;
+                this.am = 0;
+                this.as = 0;
+            }
+            
+            this.gm = 0;
+            this.bm = 0;
+            this.gs = 0;
+            this.bs = 0;
+            this.im = im;
+            this.is = is;
+        }
+
+        public DecodeParams(int bpp, int rm, int rs, int im, int is){
+            this(bpp, rm, rs, im, is, false);
+        }
+    }
+
+    static {
+        final int mx___ = 0xff000000;
+        final int m_x__ = 0x00ff0000;
+        final int m__x_ = 0x0000ff00;
+        final int m___x = 0x000000ff;
+        final int sx___ = 24;
+        final int s_x__ = 16;
+        final int s__x_ = 8;
+        final int s___x = 0;
+        final int mxxxx = 0xffffffff;
+        final int sxxxx = 0;
+
+        final int m4x___ = 0xf000;
+        final int m4_x__ = 0x0f00;
+        final int m4__x_ = 0x00f0;
+        final int m4___x = 0x000f;
+        final int s4x___ = 12;
+        final int s4_x__ = 8;
+        final int s4__x_ = 4;
+        final int s4___x = 0;
+
+        final int m5___  = 0xf800;
+        final int m_5__  = 0x07c0;
+        final int m__5_  = 0x003e;
+        final int m___1  = 0x0001;
+
+        final int s5___  = 11;
+        final int s_5__  = 6;
+        final int s__5_  = 1;
+        final int s___1  = 0;
+
+        final int m5__   = 0xf800;
+        final int m_6_   = 0x07e0;
+        final int m__5   = 0x001f;
+
+        final int s5__   = 11;
+        final int s_6_   = 5;
+        final int s__5   = 0;
+
+        final int mxx__  = 0xffff0000;
+        final int sxx__  = 32;
+        final int m__xx  = 0x0000ffff;
+        final int s__xx  = 0;
+
+        // note: compressed, depth, or floating point formats not included here..
+        
+        params.put(Format.ABGR8,    new DecodeParams(4, mx___, m___x, m__x_, m_x__,
+                                                        sx___, s___x, s__x_, s_x__,
+                                                        mxxxx, sxxxx));
+        params.put(Format.ARGB4444, new DecodeParams(2, m4x___, m4_x__, m4__x_, m4___x,
+                                                        s4x___, s4_x__, s4__x_, s4___x,
+                                                        mxxxx, sxxxx));
+        params.put(Format.Alpha16,  new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, true));
+        params.put(Format.Alpha8,   new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, true));
+        params.put(Format.BGR8,     new DecodeParams(3, 0,     m___x, m__x_, m_x__,
+                                                        0,     s___x, s__x_, s_x__,
+                                                        mxxxx, sxxxx));
+        params.put(Format.Luminance16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
+        params.put(Format.Luminance8,  new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
+        params.put(Format.Luminance16Alpha16, new DecodeParams(4, m__xx, mxx__, 0, 0,
+                                                                  s__xx, sxx__, 0, 0,
+                                                                  mxxxx, sxxxx));
+        params.put(Format.Luminance16F, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false));
+        params.put(Format.Luminance16FAlpha16F, new DecodeParams(4, m__xx, mxx__, 0, 0,
+                                                                    s__xx, sxx__, 0, 0,
+                                                                    mxxxx, sxxxx));
+        params.put(Format.Luminance32F, new DecodeParams(4, mxxxx, sxxxx, mxxxx, sxxxx, false));
+        params.put(Format.Luminance8,   new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false));
+        params.put(Format.RGB5A1,       new DecodeParams(2, m___1, m5___, m_5__, m__5_,
+                                                            s___1, s5___, s_5__, s__5_,
+                                                            mxxxx, sxxxx));
+        params.put(Format.RGB565,       new DecodeParams(2, 0,     m5__ , m_6_ , m__5,
+                                                            0,     s5__ , s_6_ , s__5,
+                                                            mxxxx, sxxxx));
+        params.put(Format.RGB8,         new DecodeParams(3, 0,     m_x__, m__x_, m___x,
+                                                            0,     s_x__, s__x_, s___x,
+                                                            mxxxx, sxxxx));
+        params.put(Format.RGBA8,        new DecodeParams(4, m___x, mx___, m_x__, m__x_,
+                                                            s___x, sx___, s_x__, s__x_,
+                                                            mxxxx, sxxxx));
+    }
+
+    private static int Ix(int x, int y, int w){
+        return y * w + x;
+    }
+
+    private static int readPixel(ByteBuffer buf, int idx, int bpp){
+        buf.position(idx);
+        int original = buf.get() & 0xff;
+        while ((--bpp) > 0){
+            original = (original << 8) | (buf.get() & 0xff);
+        }
+        return original;
+    }
+
+    private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp){
+        buf.position(idx);
+        while ((--bpp) >= 0){
+//            pixel = pixel >> 8;
+            byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff);
+//            buf.put( (byte) (pixel & 0xff) );
+            buf.put(bt);
+        }
+    }
+
+
+    /**
+     * Convert an AWT image to jME image.
+     */
+    public static void convert(BufferedImage image, Format format, ByteBuffer buf){
+        DecodeParams p = params.get(format);
+        if (p == null)
+            throw new UnsupportedOperationException("Image format " + format + " is not supported");
+
+        int width = image.getWidth();
+        int height = image.getHeight();
+
+        boolean alpha = true;
+        boolean luminance = false;
+
+        int reductionA = 8 - Integer.bitCount(p.am);
+        int reductionR = 8 - Integer.bitCount(p.rm);
+        int reductionG = 8 - Integer.bitCount(p.gm);
+        int reductionB = 8 - Integer.bitCount(p.bm);
+
+        int initialPos = buf.position();
+        for (int y = 0; y < height; y++){
+            for (int x = 0; x < width; x++){
+                // Get ARGB
+                int argb = image.getRGB(x, y);
+
+                // Extract color components
+                int a = (argb & 0xff000000) >> 24;
+                int r = (argb & 0x00ff0000) >> 16;
+                int g = (argb & 0x0000ff00) >> 8;
+                int b = (argb & 0x000000ff);
+
+                // Remove anything after 8 bits
+                a = a & 0xff;
+                r = r & 0xff;
+                g = g & 0xff;
+                b = b & 0xff;
+
+                // Set full alpha if target image has no alpha
+                if (!alpha)
+                    a = 0xff;
+
+                // Convert color to luminance if target
+                // image is in luminance format
+                if (luminance){
+                    // convert RGB to luminance
+                }
+
+                // Do bit reduction, assumes proper rounding has already been
+                // done.
+                a = a >> reductionA;
+                r = r >> reductionR;
+                g = g >> reductionG;
+                b = b >> reductionB;
+                
+                // Put components into appropriate positions
+                a = (a << p.as) & p.am;
+                r = (r << p.rs) & p.rm;
+                g = (g << p.gs) & p.gm;
+                b = (b << p.bs) & p.bm;
+
+                int outputPixel = ((a | r | g | b) << p.is) & p.im;
+                int i = initialPos + (Ix(x,y,width) * p.bpp);
+                writePixel(buf, i, outputPixel, p.bpp);
+            }
+        }
+    }
+
+    private static final double LOG2 = Math.log(2);
+
+    public static void createData(Image image, boolean mipmaps){
+        int bpp = image.getFormat().getBitsPerPixel();
+        int w = image.getWidth();
+        int h = image.getHeight();
+        if (!mipmaps){
+            image.setData(BufferUtils.createByteBuffer(w*h*bpp/8));
+            return;
+        }
+        int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(h, w)) / LOG2);
+        int[] mipMapSizes = new int[expectedMipmaps];
+        int total = 0;
+        for (int i = 0; i < mipMapSizes.length; i++){
+            int size = (w * h * bpp) / 8;
+            total += size;
+            mipMapSizes[i] = size;
+            w /= 2;
+            h /= 2;
+        }
+        image.setMipMapSizes(mipMapSizes);
+        image.setData(BufferUtils.createByteBuffer(total));
+    }
+
+    /**
+     * Convert the image from the given format to the output format.
+     * It is assumed that both images have buffers with the appropriate
+     * number of elements and that both have the same dimensions.
+     *
+     * @param input
+     * @param output
+     */
+    public static void convert(Image input, Image output){
+        DecodeParams inParams  = params.get(input.getFormat());
+        DecodeParams outParams = params.get(output.getFormat());
+
+        if (inParams == null || outParams == null)
+            throw new UnsupportedOperationException();
+
+        int width  = input.getWidth();
+        int height = input.getHeight();
+
+        if (width != output.getWidth() || height != output.getHeight())
+            throw new IllegalArgumentException();
+
+        ByteBuffer inData = input.getData(0);
+
+        boolean inAlpha = false;
+        boolean inLum = false;
+        boolean inRGB = false;
+        if (inParams.am != 0) {
+            inAlpha = true;
+        }
+
+        if (inParams.rm != 0 && inParams.gm == 0 && inParams.bm == 0) {
+            inLum = true;
+        } else if (inParams.rm != 0 && inParams.gm != 0 && inParams.bm != 0) {
+            inRGB = true;
+        }
+
+        int expansionA = 8 - Integer.bitCount(inParams.am);
+        int expansionR = 8 - Integer.bitCount(inParams.rm);
+        int expansionG = 8 - Integer.bitCount(inParams.gm);
+        int expansionB = 8 - Integer.bitCount(inParams.bm);
+
+        int inputPixel;
+        for (int y = 0; y < height; y++){
+            for (int x = 0; x < width; x++){
+                int i = Ix(x, y, width) * inParams.bpp;
+                inputPixel = (readPixel(inData, i, inParams.bpp) & inParams.im) >> inParams.is;
+                
+                int a = (inputPixel & inParams.am) >> inParams.as;
+                int r = (inputPixel & inParams.rm) >> inParams.rs;
+                int g = (inputPixel & inParams.gm) >> inParams.gs;
+                int b = (inputPixel & inParams.bm) >> inParams.bs;
+
+                r = r & 0xff;
+                g = g & 0xff;
+                b = b & 0xff;
+                a = a & 0xff;
+
+                a = a << expansionA;
+                r = r << expansionR;
+                g = g << expansionG;
+                b = b << expansionB;
+
+                if (inLum)
+                    b = g = r;
+
+                if (!inAlpha)
+                    a = 0xff;
+
+//                int argb = (a << 24) | (r << 16) | (g << 8) | b;
+//                out.setRGB(x, y, argb);
+            }
+        }
+    }
+
+    public static BufferedImage convert(Image image, boolean do16bit, boolean fullalpha, int mipLevel){
+        Format format = image.getFormat();
+        DecodeParams p = params.get(image.getFormat());
+        if (p == null)
+            throw new UnsupportedOperationException();
+
+        int width = image.getWidth();
+        int height = image.getHeight();
+
+        int level = mipLevel;
+        while (--level >= 0){
+            width  /= 2;
+            height /= 2;
+        }
+
+        ByteBuffer buf = image.getData(0);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        BufferedImage out;
+
+        boolean alpha = false;
+        boolean luminance = false;
+        boolean rgb = false;
+        if (p.am != 0)
+            alpha = true;
+
+        if (p.rm != 0 && p.gm == 0 && p.bm == 0)
+            luminance = true;
+        else if (p.rm != 0 && p.gm != 0 && p.bm != 0)
+            rgb = true;
+
+        // alpha OR luminance but not both
+        if ( (alpha && !rgb && !luminance) || (luminance && !alpha && !rgb) ){
+            out = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+        }else if ( (rgb && alpha) || (luminance && alpha) ){
+            if (do16bit){
+                if (fullalpha){
+                    ColorModel model = AWTLoader.AWT_RGBA4444;
+                    WritableRaster raster = model.createCompatibleWritableRaster(width, width);
+                    out = new BufferedImage(model, raster, false, null);
+                }else{
+                    // RGB5_A1
+                    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+                    int[] nBits = {5, 5, 5, 1};
+                    int[] bOffs = {0, 1, 2, 3};
+                    ColorModel colorModel = new ComponentColorModel(cs, nBits, true, false,
+                                                                    Transparency.BITMASK,
+                                                                    DataBuffer.TYPE_BYTE);
+                    WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
+                                                                           width, height,
+                                                                           width*2, 2,
+                                                                           bOffs, null);
+                    out = new BufferedImage(colorModel, raster, false, null);
+                }
+            }else{
+                out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            }
+        }else{
+            if (do16bit){
+                out = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);
+            }else{
+                out = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+            }
+        }
+
+        int expansionA = 8 - Integer.bitCount(p.am);
+        int expansionR = 8 - Integer.bitCount(p.rm);
+        int expansionG = 8 - Integer.bitCount(p.gm);
+        int expansionB = 8 - Integer.bitCount(p.bm);
+        
+        if (expansionR < 0){
+            expansionR = 0;
+        }
+        
+        int mipPos = 0;
+        for (int i = 0; i < mipLevel; i++){
+            mipPos += image.getMipMapSizes()[i];
+        }
+        int inputPixel;
+        for (int y = 0; y < height; y++){
+            for (int x = 0; x < width; x++){
+                int i = mipPos + (Ix(x,y,width) * p.bpp);
+                inputPixel = (readPixel(buf,i,p.bpp) & p.im) >> p.is;
+                int a = (inputPixel & p.am) >> p.as;
+                int r = (inputPixel & p.rm) >> p.rs;
+                int g = (inputPixel & p.gm) >> p.gs;
+                int b = (inputPixel & p.bm) >> p.bs;
+
+                r = r & 0xff;
+                g = g & 0xff;
+                b = b & 0xff;
+                a = a & 0xff;
+
+                a = a << expansionA;
+                r = r << expansionR;
+                g = g << expansionG;
+                b = b << expansionB;
+                
+                if (luminance)
+                    b = g = r;
+
+                if (!alpha)
+                    a = 0xff;
+
+                int argb = (a << 24) | (r << 16) | (g << 8) | b;
+                out.setRGB(x, y, argb);
+            }
+        }
+
+        return out;
+    }
+
+}
diff --git a/engine/src/desktop/jme3tools/converters/MipMapGenerator.java b/engine/src/desktop/jme3tools/converters/MipMapGenerator.java
new file mode 100644
index 0000000..a4541e6
--- /dev/null
+++ b/engine/src/desktop/jme3tools/converters/MipMapGenerator.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters;
+
+import com.jme3.math.FastMath;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.plugins.AWTLoader;
+import com.jme3.util.BufferUtils;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+public class MipMapGenerator {
+
+    private static BufferedImage scaleDown(BufferedImage sourceImage, int targetWidth, int targetHeight) {
+        int sourceWidth  = sourceImage.getWidth();
+        int sourceHeight = sourceImage.getHeight();
+
+        BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, sourceImage.getType());
+
+        Graphics2D g = targetImage.createGraphics();
+        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        g.drawImage(sourceImage, 0, 0, targetWidth, targetHeight, 0, 0, sourceWidth, sourceHeight, null);
+        g.dispose();
+
+        return targetImage;
+    }
+
+    public static void resizeToPowerOf2(Image image){
+        BufferedImage original = ImageToAwt.convert(image, false, true, 0);
+        int potWidth = FastMath.nearestPowerOfTwo(image.getWidth());
+        int potHeight = FastMath.nearestPowerOfTwo(image.getHeight());
+        int potSize = Math.max(potWidth, potHeight);
+
+        BufferedImage scaled = scaleDown(original, potSize, potSize);
+
+        AWTLoader loader = new AWTLoader();
+        Image output = loader.load(scaled, false);
+
+        image.setWidth(potSize);
+        image.setHeight(potSize);
+        image.setDepth(0);
+        image.setData(output.getData(0));
+        image.setFormat(output.getFormat());
+        image.setMipMapSizes(null);
+    }
+
+    public static void generateMipMaps(Image image){
+        BufferedImage original = ImageToAwt.convert(image, false, true, 0);
+        int width = original.getWidth();
+        int height = original.getHeight();
+        int level = 0;
+
+        BufferedImage current = original;
+        AWTLoader loader = new AWTLoader();
+        ArrayList<ByteBuffer> output = new ArrayList<ByteBuffer>();
+        int totalSize = 0;
+        Format format = null;
+        
+        while (height >= 1 || width >= 1){
+            Image converted = loader.load(current, false);
+            format = converted.getFormat();
+            output.add(converted.getData(0));
+            totalSize += converted.getData(0).capacity();
+
+            if(height == 1 || width == 1) {
+              break;
+            }
+
+            level++;
+
+            height /= 2;
+            width /= 2;
+
+            current = scaleDown(current, width, height);
+        }
+
+        ByteBuffer combinedData = BufferUtils.createByteBuffer(totalSize);
+        int[] mipSizes = new int[output.size()];
+        for (int i = 0; i < output.size(); i++){
+            ByteBuffer data = output.get(i);
+            data.clear();
+            combinedData.put(data);
+            mipSizes[i] = data.capacity();
+        }
+        combinedData.flip();
+
+        // insert mip data into image
+        image.setData(0, combinedData);
+        image.setMipMapSizes(mipSizes);
+        image.setFormat(format);
+    }
+
+}
diff --git a/engine/src/desktop/jme3tools/navigation/Coordinate.java b/engine/src/desktop/jme3tools/navigation/Coordinate.java
new file mode 100644
index 0000000..6e2c2d2
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/Coordinate.java
@@ -0,0 +1,247 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+import java.text.DecimalFormat;
+
+/**
+ * Coordinate class. Used to store a coordinate in [DD]D MM.M format.
+ *
+ * @author Benjamin Jakobus (based on JMarine by Benjamin Jakobus and Cormac Gebruers)
+ * @version 1.0
+ * @since 1.0
+ */
+public class Coordinate {
+
+    /* the degree part of the position (+ N/E, -W/S) */
+    private int deg;
+
+    /* the decimals of a minute */
+    private double minsDecMins;
+
+    /* the coordinate as a decimal*/
+    private double decCoordinate;
+
+    /* whether this coordinate is a latitude or a longitude: : LAT==0, LONG==1  */
+    private int coOrdinate;
+
+    /* The minutes trailing decimal precision to use for positions */
+    public static final int MINPRECISION = 4;
+    /* The degrees trailing decimal precision to use for positions */
+    public static final int DEGPRECISION = 7;
+
+    /* typeDefs for coOrdinates */
+    public static final int LAT = 0;
+    public static final int LNG = 1;
+
+    /* typeDefs for quadrant */
+    public static final int E = 0;
+    public static final int S = 1;
+    public static final int W = 2;
+    public static final int N = 3;
+
+    /**
+     * Constructor
+     * 
+     * @param deg
+     * @param minsDecMins
+     * @param coOrdinate
+     * @param quad
+     * @throws InvalidPositionException
+     * @since 1.0
+     */
+    public Coordinate(int deg, float minsDecMins, int coOrdinate,
+            int quad) throws InvalidPositionException {
+        buildCoOrdinate(deg, minsDecMins, coOrdinate, quad);
+        if (verify()) {
+        } else {
+            throw new InvalidPositionException();
+        }
+    }
+
+    /**
+     * Constructor
+     * @param decCoordinate
+     * @param coOrdinate
+     * @throws InvalidPositionException
+     * @since 1.0
+     */
+    public Coordinate(double decCoordinate, int coOrdinate) throws InvalidPositionException {
+        DecimalFormat form = new DecimalFormat("#.#######");
+
+        this.decCoordinate = decCoordinate;
+        this.coOrdinate = coOrdinate;
+        if (verify()) {
+            deg = new Float(decCoordinate).intValue();
+            if (deg < 0) {
+                minsDecMins = Double.parseDouble(form.format((Math.abs(decCoordinate) - Math.abs(deg)) * 60));
+            } else {
+                minsDecMins = Double.parseDouble(form.format((decCoordinate - deg) * 60));
+            }
+        } else {
+            throw new InvalidPositionException();
+        }
+    }
+
+    /**
+     * This constructor takes a coordinate in the ALRS formats i.e
+     * 38∞31.64'N for lat, and 28∞19.12'W for long
+     * Note: ALRS positions are occasionally written with the decimal minutes
+     * apostrophe in the 'wrong' place and with an non CP1252 compliant decimal character.
+     * This issue has to be corrected in the source database
+     * @param coOrdinate
+     * @throws InvalidPositionException
+     * @since 1.0
+     */
+    public Coordinate(String coOrdinate) throws InvalidPositionException {
+        //firstly split it into its component parts and dispose of the unneeded characters
+        String[] items = coOrdinate.split("°");
+        int deg = Integer.valueOf(items[0]);
+
+        items = items[1].split("'");
+        float minsDecMins = Float.valueOf(items[0]);
+        char quad = items[1].charAt(0);
+
+        switch (quad) {
+            case 'N':
+                buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.N);
+                break;
+            case 'S':
+                buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.S);
+                break;
+            case 'E':
+                buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.E);
+                break;
+            case 'W':
+                buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.W);
+        }
+        if (verify()) {
+        } else {
+            throw new InvalidPositionException();
+        }
+    }
+
+    /**
+     * Prints out a coordinate as a string
+     * @return the coordinate in decimal format
+     * @since 1.0
+     */
+    public String toStringDegMin() {
+        String str = "";
+        String quad = "";
+        StringUtil su = new StringUtil();
+        switch (coOrdinate) {
+            case LAT:
+                if (decCoordinate >= 0) {
+                    quad = "N";
+                } else {
+                    quad = "S";
+                }
+                str = su.padNumZero(Math.abs(deg), 2);
+                str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad;
+                break;
+            case LNG:
+                if (decCoordinate >= 0) {
+                    quad = "E";
+                } else {
+                    quad = "W";
+                }
+                str = su.padNumZero(Math.abs(deg), 3);
+                str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad;
+                break;
+        }
+        return str;
+    }
+
+    /**
+     * Prints out a coordinate as a string
+     * @return the coordinate in decimal format
+     * @since 1.0
+     */
+    public String toStringDec() {
+        StringUtil u = new StringUtil();
+        switch (coOrdinate) {
+            case LAT:
+                return u.padNumZero(decCoordinate, 2, DEGPRECISION);
+            case LNG:
+                return u.padNumZero(decCoordinate, 3, DEGPRECISION);
+        }
+        return "error";
+    }
+
+    /**
+     * Returns the coordinate's decimal value
+     * @return float the decimal value of the coordinate
+     * @since 1.0
+     */
+    public double decVal() {
+        return decCoordinate;
+    }
+
+    /**
+     * Determines whether a decimal position is valid
+     * @return result of validity test
+     * @since 1.0
+     */
+    private boolean verify() {
+        switch (coOrdinate) {
+            case LAT:
+                if (Math.abs(decCoordinate) > 90.0) {
+                    return false;
+                }
+                break;
+
+            case LNG:
+                if (Math.abs(decCoordinate) > 180) {
+                    return false;
+                }
+        }
+        return true;
+    }
+
+    /**
+     * Populate this object by parsing the arguments to the function
+     * Placed here to allow multiple constructors to use it
+     * @since 1.0
+     */
+    private void buildCoOrdinate(int deg, float minsDecMins, int coOrdinate,
+            int quad) {
+        NumUtil nu = new NumUtil();
+
+        switch (coOrdinate) {
+            case LAT:
+                switch (quad) {
+                    case N:
+                        this.deg = deg;
+                        this.minsDecMins = minsDecMins;
+                        this.coOrdinate = coOrdinate;
+                        decCoordinate = nu.Round(this.deg + (float) this.minsDecMins / 60, Coordinate.MINPRECISION);
+                        break;
+
+                    case S:
+                        this.deg = -deg;
+                        this.minsDecMins = minsDecMins;
+                        this.coOrdinate = coOrdinate;
+                        decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION);
+                }
+
+            case LNG:
+                switch (quad) {
+                    case E:
+                        this.deg = deg;
+                        this.minsDecMins = minsDecMins;
+                        this.coOrdinate = coOrdinate;
+                        decCoordinate = nu.Round(this.deg + ((float) this.minsDecMins / 60), Coordinate.MINPRECISION);
+                        break;
+
+                    case W:
+                        this.deg = -deg;
+                        this.minsDecMins = minsDecMins;
+                        this.coOrdinate = coOrdinate;
+                        decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION);
+                }
+        }
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/GCSailing.java b/engine/src/desktop/jme3tools/navigation/GCSailing.java
new file mode 100644
index 0000000..f5699b5
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/GCSailing.java
@@ -0,0 +1,33 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+/**
+ * A utility class to package up a great circle sailing.
+ * 
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ *
+ * @version 1.0
+ * @since 1.0
+ */
+public class GCSailing {
+
+    private int[] courses;
+    private float[] distancesNM;
+
+    public GCSailing(int[] pCourses, float[] pDistancesNM) {
+        courses = pCourses;
+        distancesNM = pDistancesNM;
+    }
+
+    public int[] getCourses() {
+        return courses;
+    }
+
+    public float[] getDistancesNM() {
+        return distancesNM;
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/InvalidPositionException.java b/engine/src/desktop/jme3tools/navigation/InvalidPositionException.java
new file mode 100644
index 0000000..32b3c9b
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/InvalidPositionException.java
@@ -0,0 +1,14 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package jme3tools.navigation;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class InvalidPositionException extends Exception{
+
+}
diff --git a/engine/src/desktop/jme3tools/navigation/MapModel2D.java b/engine/src/desktop/jme3tools/navigation/MapModel2D.java
new file mode 100644
index 0000000..a1a08c1
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/MapModel2D.java
@@ -0,0 +1,368 @@
+package jme3tools.navigation;
+import java.awt.Point;
+import java.text.DecimalFormat;
+
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+
+/**
+ * A representation of the actual map in terms of lat/long and x,y co-ordinates.
+ * The Map class contains various helper methods such as methods for determining
+ * the pixel positions for lat/long co-ordinates and vice versa.
+ *
+ * @author Cormac Gebruers
+ * @author Benjamin Jakobus
+ * @version 1.0
+ * @since 1.0
+ */
+public class MapModel2D {
+
+    /* The number of radians per degree */
+    private final static double RADIANS_PER_DEGREE = 57.2957;
+
+    /* The number of degrees per radian */
+    private final static double DEGREES_PER_RADIAN = 0.0174532925;
+
+    /* The map's width in longitude */
+    public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;
+
+    /* The top right hand corner of the map */
+    private Position centre;
+
+    /* The x and y co-ordinates for the viewport's centre */
+    private int xCentre;
+    private int yCentre;
+
+    /* The width (in pixels) of the viewport holding the map */
+    private int viewportWidth;
+
+    /* The viewport height in pixels */
+    private int viewportHeight;
+
+    /* The number of minutes that one pixel represents */
+    private double minutesPerPixel;
+
+    /**
+     * Constructor
+     * @param viewportWidth the pixel width of the viewport (component) in which
+     *        the map is displayed
+     * @since 1.0
+     */
+    public MapModel2D(int viewportWidth) {
+        try {
+            this.centre = new Position(0, 0);
+        } catch (InvalidPositionException e) {
+            e.printStackTrace();
+        }
+
+        this.viewportWidth = viewportWidth;
+
+        // Calculate the number of minutes that one pixel represents along the longitude
+        calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE);
+
+        // Calculate the viewport height based on its width and the number of degrees (85)
+        // in our map
+        viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2;
+//        viewportHeight = viewportWidth; // REMOVE!!!
+        // Determine the map's x,y centre
+        xCentre = viewportWidth / 2;
+        yCentre = viewportHeight / 2;
+    }
+
+    /**
+     * Returns the height of the viewport in pixels
+     * @return the height of the viewport in pixels
+     * @since 0.1
+     */
+    public int getViewportPixelHeight() {
+        return viewportHeight;
+    }
+
+    /**
+     * Calculates the number of minutes per pixels using a given
+     * map width in longitude
+     * @param mapWidthInLongitude
+     * @since 1.0
+     */
+    public void calculateMinutesPerPixel(double mapWidthInLongitude) {
+        minutesPerPixel = (mapWidthInLongitude * 60) / (double) viewportWidth;
+    }
+
+    /**
+     * Returns the width of the viewport in pixels
+     * @return the width of the viewport in pixels
+     * @since 0.1
+     */
+    public int getViewportPixelWidth() {
+        return viewportWidth;
+    }
+
+    public void setViewportWidth(int viewportWidth) {
+        this.viewportWidth = viewportWidth;
+    }
+
+    public void setViewportHeight(int viewportHeight) {
+        this.viewportHeight = viewportHeight;
+    }
+
+    public void setCentre(Position centre) {
+        this.centre = centre;
+    }
+
+    /**
+     * Returns the number of minutes there are per pixel
+     * @return the number of minutes per pixel
+     * @since 1.0
+     */
+    public double getMinutesPerPixel() {
+        return minutesPerPixel;
+    }
+
+    public double getMetersPerPixel() {
+        return 1853 * minutesPerPixel;
+    }
+
+    public void setMinutesPerPixel(double minutesPerPixel) {
+        this.minutesPerPixel = minutesPerPixel;
+    }
+
+    /**
+     * Converts a latitude/longitude position into a pixel co-ordinate
+     * @param position the position to convert
+     * @return {@code Point} a pixel co-ordinate
+     * @since 1.0
+     */
+    public Point toPixel(Position position) {
+        // Get the distance between position and the centre for calculating
+        // the position's longitude translation
+        double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
+                position.getLongitude());
+
+        // Use the distance from the centre to calculate the pixel x co-ordinate
+        double distanceInPixels = (distance / minutesPerPixel);
+
+        // Use the difference in meridional parts to calculate the pixel y co-ordinate
+        double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
+                position.getLatitude());
+
+        int x = 0;
+        int y = 0;
+
+        if (centre.getLatitude() == position.getLatitude()) {
+            y = yCentre;
+        }
+        if (centre.getLongitude() == position.getLongitude()) {
+            x = xCentre;
+        }
+
+        // Distinguish between northern and southern hemisphere for latitude calculations
+        if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is north. Position is north of centre
+            y = yCentre + (int) ((dmp) / minutesPerPixel);
+        } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is north. Position is south of centre
+            y = yCentre - (int) ((dmp) / minutesPerPixel);
+        } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is south. Position is north of centre
+            y = yCentre + (int) ((dmp) / minutesPerPixel);
+        } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is south. Position is south of centre
+            y = yCentre - (int) ((dmp) / minutesPerPixel);
+        } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is at the equator. Position is north of the equator
+            y = yCentre + (int) ((dmp) / minutesPerPixel);
+        } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is at the equator. Position is south of the equator
+            y = yCentre - (int) ((dmp) / minutesPerPixel);
+        }
+
+        // Distinguish between western and eastern hemisphere for longitude calculations
+        if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is west. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is west. Position is south of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is east. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is east. Position is east of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is at the equator. Position is east of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is at the equator. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        }
+
+        // Distinguish between northern and souterhn hemisphere for longitude calculations
+        return new Point(x, y);
+    }
+
+    /**
+     * Converts a pixel position into a mercator position
+     * @param p {@link Point} object that you wish to convert into
+     *        longitude / latiude
+     * @return the converted {@code Position} object
+     * @since 1.0
+     */
+    public Position toPosition(Point p) {
+        double lat, lon;
+        Position pos = null;
+        try {
+            Point pixelCentre = toPixel(new Position(0, 0));
+
+            // Get the distance between position and the centre
+            double xDistance = distance(xCentre, p.getX());
+            double yDistance = distance(pixelCentre.getY(), p.getY());
+            double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60;
+            double mp = (yDistance * minutesPerPixel);
+            // If we are zoomed in past a certain point, then use linear search.
+            // Otherwise use binary search
+            if (getMinutesPerPixel() < 0.05) {
+                lat = findLat(mp, getCentre().getLatitude());
+                if (lat == -1000) {
+                    System.out.println("lat: " + lat);
+                }
+            } else {
+                lat = findLat(mp, 0.0, 85.0);
+            }
+            lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
+                    : centre.getLongitude() + lonDistanceInDegrees);
+
+            if (p.getY() > pixelCentre.getY()) {
+                lat = -1 * lat;
+            }
+            if (lat == -1000 || lon == -1000) {
+                return pos;
+            }
+            pos = new Position(lat, lon);
+        } catch (InvalidPositionException ipe) {
+            ipe.printStackTrace();
+        }
+        return pos;
+    }
+
+    /**
+     * Calculates distance between two points on the map in pixels
+     * @param a
+     * @param b
+     * @return distance the distance between a and b in pixels
+     * @since 1.0
+     */
+    private double distance(double a, double b) {
+        return Math.abs(a - b);
+    }
+
+    /**
+     * Defines the centre of the map in pixels
+     * @param p <code>Point</code> object denoting the map's new centre
+     * @since 1.0
+     */
+    public void setCentre(Point p) {
+        try {
+            Position newCentre = toPosition(p);
+            if (newCentre != null) {
+                centre = newCentre;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Sets the map's xCentre
+     * @param xCentre
+     * @since 1.0
+     */
+    public void setXCentre(int xCentre) {
+        this.xCentre = xCentre;
+    }
+
+    /**
+     * Sets the map's yCentre
+     * @param yCentre
+     * @since 1.0
+     */
+    public void setYCentre(int yCentre) {
+        this.yCentre = yCentre;
+    }
+
+    /**
+     * Returns the pixel (x,y) centre of the map
+     * @return {@link Point) object marking the map's (x,y) centre
+     * @since 1.0
+     */
+    public Point getPixelCentre() {
+        return new Point(xCentre, yCentre);
+    }
+
+    /**
+     * Returns the {@code Position} centre of the map
+     * @return {@code Position} object marking the map's (lat, long) centre
+     * @since 1.0
+     */
+    public Position getCentre() {
+        return centre;
+    }
+
+    /**
+     * Uses binary search to find the latitude of a given MP.
+     *
+     * @param mp maridian part
+     * @param low
+     * @param high
+     * @return the latitude of the MP value
+     * @since 1.0
+     */
+    private double findLat(double mp, double low, double high) {
+        DecimalFormat form = new DecimalFormat("#.####");
+        mp = Math.round(mp);
+        double midLat = (low + high) / 2.0;
+        // ctr is used to make sure that with some
+        // numbers which can't be represented exactly don't inifitely repeat
+        double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
+
+        while (low <= high) {
+            if (guessMP == mp) {
+                return midLat;
+            } else {
+                if (guessMP > mp) {
+                    high = midLat - 0.0001;
+                } else {
+                    low = midLat + 0.0001;
+                }
+            }
+
+            midLat = Double.valueOf(form.format(((low + high) / 2.0)));
+            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
+            guessMP = Math.round(guessMP);
+        }
+        return -1000;
+    }
+
+    /**
+     * Uses linear search to find the latitude of a given MP
+     * @param mp the meridian part for which to find the latitude
+     * @param previousLat the previous latitude. Used as a upper / lower bound
+     * @return the latitude of the MP value
+     */
+    private double findLat(double mp, double previousLat) {
+        DecimalFormat form = new DecimalFormat("#.#####");
+        mp = Double.parseDouble(form.format(mp));
+        double guessMP;
+        for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
+            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
+            guessMP = Double.parseDouble(form.format(guessMP));
+            if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) {
+                return lat;
+            }
+        }
+        return -1000;
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/MapModel3D.java b/engine/src/desktop/jme3tools/navigation/MapModel3D.java
new file mode 100644
index 0000000..f3d296c
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/MapModel3D.java
@@ -0,0 +1,389 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+import com.jme3.math.Vector3f;
+import java.text.DecimalFormat;
+
+
+/**
+ * A representation of the actual map in terms of lat/long and x,y,z co-ordinates.
+ * The Map class contains various helper methods such as methods for determining
+ * the world unit positions for lat/long coordinates and vice versa. This map projection
+ * does not handle screen/pixel coordinates.
+ *
+ * @author Benjamin Jakobus (thanks to Cormac Gebruers)
+ * @version 1.0
+ * @since 1.0
+ */
+public class MapModel3D {
+
+    /* The number of radians per degree */
+    private final static double RADIANS_PER_DEGREE = 57.2957;
+
+    /* The number of degrees per radian */
+    private final static double DEGREES_PER_RADIAN = 0.0174532925;
+
+    /* The map's width in longitude */
+    public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;
+
+    /* The top right hand corner of the map */
+    private Position centre;
+
+    /* The x and y co-ordinates for the viewport's centre */
+    private int xCentre;
+    private int zCentre;
+
+    /* The width (in world units (wu)) of the viewport holding the map */
+    private int worldWidth;
+
+    /* The viewport height in pixels */
+    private int worldHeight;
+
+    /* The number of minutes that one pixel represents */
+    private double minutesPerWorldUnit;
+
+    /**
+     * Constructor.
+     * 
+     * @param worldWidth         The world unit width the map's area
+     * @since 1.0
+     */
+    public MapModel3D(int worldWidth) {
+        try {
+            this.centre = new Position(0, 0);
+        } catch (InvalidPositionException e) {
+            e.printStackTrace();
+        }
+
+        this.worldWidth = worldWidth;
+
+        // Calculate the number of minutes that one pixel represents along the longitude
+        calculateMinutesPerWorldUnit(DEFAULT_MAP_WIDTH_LONGITUDE);
+
+        // Calculate the viewport height based on its width and the number of degrees (85)
+        // in our map
+        worldHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerWorldUnit) * 2;
+
+        // Determine the map's x,y centre
+        xCentre = 0;
+        zCentre = 0;
+//        xCentre = worldWidth / 2;
+//        zCentre = worldHeight / 2;
+    }
+
+    /**
+     * Returns the height of the viewport in pixels.
+     *
+     * @return          The height of the viewport in pixels.
+     * @since 1.0
+     */
+    public int getWorldHeight() {
+        return worldHeight;
+    }
+
+    /**
+     * Calculates the number of minutes per pixels using a given
+     * map width in longitude.
+     *
+     * @param mapWidthInLongitude               The map's with in degrees of longitude.
+     * @since 1.0
+     */
+    public void calculateMinutesPerWorldUnit(double mapWidthInLongitude) {
+        // Multiply mapWidthInLongitude by 60 to convert it to minutes.
+        minutesPerWorldUnit = (mapWidthInLongitude * 60) / (double) worldWidth;
+    }
+
+    /**
+     * Returns the width of the viewport in pixels.
+     *
+     * @return              The width of the viewport in pixels.
+     * @since 1.0
+     */
+    public int getWorldWidth() {
+        return worldWidth;
+    }
+
+    /**
+     * Sets the world's desired width.
+     *
+     * @param viewportWidth     The world's desired width in WU.
+     * @since 1.0
+     */
+    public void setWorldWidth(int viewportWidth) {
+        this.worldWidth = viewportWidth;
+    }
+
+     /**
+     * Sets the world's desired height.
+     *
+     * @param viewportHeight     The world's desired height in WU.
+     * @since 1.0
+     */
+    public void setWorldHeight(int viewportHeight) {
+        this.worldHeight = viewportHeight;
+    }
+
+    /**
+     * Sets the map's centre.
+     *
+     * @param centre            The <code>Position</code> denoting the map's
+     *                          desired centre.
+     * @since 1.0
+     */
+    public void setCentre(Position centre) {
+        this.centre = centre;
+    }
+
+    /**
+     * Returns the number of minutes there are per WU.
+     *
+     * @return                  The number of minutes per WU.
+     * @since 1.0
+     */
+    public double getMinutesPerWu() {
+        return minutesPerWorldUnit;
+    }
+
+    /**
+     * Returns the meters per WU.
+     *
+     * @return                  The meters per WU.
+     * @since 1.0
+     */
+    public double getMetersPerWu() {
+        return 1853 * minutesPerWorldUnit;
+    }
+
+    /**
+     * Converts a latitude/longitude position into a WU coordinate.
+     *
+     * @param position          The <code>Position</code> to convert.
+     * @return                  The <code>Point</code> a pixel coordinate.
+     * @since 1.0
+     */
+    public Vector3f toWorldUnit(Position position) {
+        // Get the difference between position and the centre for calculating
+        // the position's longitude translation
+        double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
+                position.getLongitude());
+
+        // Use the difference from the centre to calculate the pixel x co-ordinate
+        double distanceInPixels = (distance / minutesPerWorldUnit);
+
+        // Use the difference in meridional parts to calculate the pixel y co-ordinate
+        double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
+                position.getLatitude());
+
+        int x = 0;
+        int z = 0;
+
+        if (centre.getLatitude() == position.getLatitude()) {
+            z = zCentre;
+        }
+        if (centre.getLongitude() == position.getLongitude()) {
+            x = xCentre;
+        }
+
+        // Distinguish between northern and southern hemisphere for latitude calculations
+        if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is north. Position is north of centre
+            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
+        } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is north. Position is south of centre
+            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
+        } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is south. Position is north of centre
+            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
+        } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is south. Position is south of centre
+            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
+        } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
+            // Centre is at the equator. Position is north of the equator
+            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
+        } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
+            // Centre is at the equator. Position is south of the equator
+            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
+        }
+
+        // Distinguish between western and eastern hemisphere for longitude calculations
+        if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is west. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is west. Position is south of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is east. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is east. Position is east of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
+            // Centre is at the equator. Position is east of centre
+            x = xCentre + (int) distanceInPixels;
+        } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
+            // Centre is at the equator. Position is west of centre
+            x = xCentre - (int) distanceInPixels;
+        }
+
+        // Distinguish between northern and southern hemisphere for longitude calculations
+        return new Vector3f(x, 0, z);
+    }
+
+    /**
+     * Converts a world position into a Mercator position.
+     *
+     * @param posVec                     <code>Vector</code> containing the world unit 
+     *                              coordinates that are to be converted into
+     *                              longitude / latitude coordinates.
+     * @return                      The resulting <code>Position</code> in degrees of
+     *                              latitude and longitude.
+     * @since 1.0
+     */
+    public Position toPosition(Vector3f posVec) {
+        double lat, lon;
+        Position pos = null;
+        try {
+            Vector3f worldCentre = toWorldUnit(new Position(0, 0));
+
+            // Get the difference between position and the centre
+            double xDistance = difference(xCentre, posVec.getX());
+            double yDistance = difference(worldCentre.getZ(), posVec.getZ());
+            double lonDistanceInDegrees = (xDistance * minutesPerWorldUnit) / 60;
+            double mp = (yDistance * minutesPerWorldUnit);
+            // If we are zoomed in past a certain point, then use linear search.
+            // Otherwise use binary search
+            if (getMinutesPerWu() < 0.05) {
+                lat = findLat(mp, getCentre().getLatitude());
+                if (lat == -1000) {
+                    System.out.println("lat: " + lat);
+                }
+            } else {
+                lat = findLat(mp, 0.0, 85.0);
+            }
+            lon = (posVec.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
+                    : centre.getLongitude() + lonDistanceInDegrees);
+
+            if (posVec.getZ() > worldCentre.getZ()) {
+                lat = -1 * lat;
+            }
+            if (lat == -1000 || lon == -1000) {
+                return pos;
+            }
+            pos = new Position(lat, lon);
+        } catch (InvalidPositionException ipe) {
+            ipe.printStackTrace();
+        }
+        return pos;
+    }
+
+    /**
+     * Calculates difference between two points on the map in WU.
+     *
+     * @param a                     
+     * @param b
+     * @return difference           The difference between a and b in WU.
+     * @since 1.0
+     */
+    private double difference(double a, double b) {
+        return Math.abs(a - b);
+    }
+
+    /**
+     * Defines the centre of the map in pixels.
+     *
+     * @param posVec             <code>Vector3f</code> object denoting the map's new centre.
+     * @since 1.0
+     */
+    public void setCentre(Vector3f posVec) {
+        try {
+            Position newCentre = toPosition(posVec);
+            if (newCentre != null) {
+                centre = newCentre;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Returns the WU (x,y,z) centre of the map.
+     * 
+     * @return              <code>Vector3f</code> object marking the map's (x,y) centre.
+     * @since 1.0
+     */
+    public Vector3f getCentreWu() {
+        return new Vector3f(xCentre, 0, zCentre);
+    }
+
+    /**
+     * Returns the <code>Position</code> centre of the map.
+     *
+     * @return              <code>Position</code> object marking the map's (lat, long)
+     *                      centre.
+     * @since 1.0
+     */
+    public Position getCentre() {
+        return centre;
+    }
+
+    /**
+     * Uses binary search to find the latitude of a given MP.
+     *
+     * @param mp                Maridian part whose latitude to determine.
+     * @param low               Minimum latitude bounds.
+     * @param high              Maximum latitude bounds.
+     * @return                  The latitude of the MP value
+     * @since 1.0
+     */
+    private double findLat(double mp, double low, double high) {
+        DecimalFormat form = new DecimalFormat("#.####");
+        mp = Math.round(mp);
+        double midLat = (low + high) / 2.0;
+        // ctr is used to make sure that with some
+        // numbers which can't be represented exactly don't inifitely repeat
+        double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
+
+        while (low <= high) {
+            if (guessMP == mp) {
+                return midLat;
+            } else {
+                if (guessMP > mp) {
+                    high = midLat - 0.0001;
+                } else {
+                    low = midLat + 0.0001;
+                }
+            }
+
+            midLat = Double.valueOf(form.format(((low + high) / 2.0)));
+            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
+            guessMP = Math.round(guessMP);
+        }
+        return -1000;
+    }
+
+    /**
+     * Uses linear search to find the latitude of a given MP.
+     *
+     * @param mp                The meridian part for which to find the latitude.
+     * @param previousLat       The previous latitude. Used as a upper / lower bound.
+     * @return                  The latitude of the MP value.
+     * @since 1.0
+     */
+    private double findLat(double mp, double previousLat) {
+        DecimalFormat form = new DecimalFormat("#.#####");
+        mp = Double.parseDouble(form.format(mp));
+        double guessMP;
+        for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
+            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
+            guessMP = Double.parseDouble(form.format(guessMP));
+            if (guessMP == mp || Math.abs(guessMP - mp) < 0.05) {
+                return lat;
+            }
+        }
+        return -1000;
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/NavCalculator.java b/engine/src/desktop/jme3tools/navigation/NavCalculator.java
new file mode 100644
index 0000000..7145636
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/NavCalculator.java
@@ -0,0 +1,591 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+
+
+/**
+ * A utlity class for performing position calculations
+ *
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+public class NavCalculator {
+
+    private double distance;
+    private double trueCourse;
+
+    /* The earth's radius in meters */
+    public static final int WGS84_EARTH_RADIUS = 6378137;
+    private String strCourse;
+
+    /* The sailing calculation type */
+    public static final int MERCATOR = 0;
+    public static final int GC = 1;
+
+    /* The degree precision to use for courses */
+    public static final int RL_CRS_PRECISION = 1;
+
+    /* The distance precision to use for distances */
+    public static final int RL_DIST_PRECISION = 1;
+    public static final int METERS_PER_MINUTE = 1852;
+
+    /**
+     * Constructor
+     * @param P1
+     * @param P2
+     * @param calcType
+     * @since 1.0
+     */
+    public NavCalculator(Position P1, Position P2, int calcType) {
+        switch (calcType) {
+            case MERCATOR:
+                mercatorSailing(P1, P2);
+                break;
+            case GC:
+                greatCircleSailing(P1, P2);
+                break;
+        }
+    }
+
+    /**
+     * Constructor
+     * @since 1.0
+     */
+    public NavCalculator() {
+    }
+
+    /**
+     * Determines a great circle track between two positions
+     * @param p1 origin position
+     * @param p2 destination position
+     */
+    public GCSailing greatCircleSailing(Position p1, Position p2) {
+        return new GCSailing(new int[0], new float[0]);
+    }
+
+    /**
+     * Determines a Rhumb Line course and distance between two points
+     * @param p1 origin position
+     * @param p2 destination position
+     */
+    public RLSailing rhumbLineSailing(Position p1, Position p2) {
+        RLSailing rl = mercatorSailing(p1, p2);
+        return rl;
+    }
+
+    /**
+     * Determines the rhumb line course  and distance between two positions
+     * @param p1 origin position
+     * @param p2 destination position
+     */
+    public RLSailing mercatorSailing(Position p1, Position p2) {
+
+        double dLat = computeDLat(p1.getLatitude(), p2.getLatitude());
+        //plane sailing...
+        if (dLat == 0) {
+            RLSailing rl = planeSailing(p1, p2);
+            return rl;
+        }
+
+        double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
+        double dmp = (float) computeDMPClarkeSpheroid(p1.getLatitude(), p2.getLatitude());
+
+        trueCourse = (float) Math.toDegrees(Math.atan(dLong / dmp));
+        double degCrs = convertCourse((float) trueCourse, p1, p2);
+        distance = (float) Math.abs(dLat / Math.cos(Math.toRadians(trueCourse)));
+
+        RLSailing rl = new RLSailing(degCrs, (float) distance);
+        trueCourse = rl.getCourse();
+        strCourse = (dLat < 0 ? "S" : "N");
+        strCourse += " " + trueCourse;
+        strCourse += " " + (dLong < 0 ? "W" : "E");
+        return rl;
+
+    }
+
+    /**
+     * Calculate a plane sailing situation - i.e. where Lats are the same 
+     * @param p1
+     * @param p2
+     * @return
+     * @since 1.0
+     */
+    public RLSailing planeSailing(Position p1, Position p2) {
+        double dLong = computeDLong(p1.getLongitude(), p2.getLongitude());
+
+        double sgnDLong = 0 - (dLong / Math.abs(dLong));
+        if (Math.abs(dLong) > 180 * 60) {
+            dLong = (360 * 60 - Math.abs(dLong)) * sgnDLong;
+        }
+
+        double redist = 0;
+        double recourse = 0;
+        if (p1.getLatitude() == 0) {
+            redist = Math.abs(dLong);
+        } else {
+            redist = Math.abs(dLong * (float) Math.cos(p1.getLatitude() * 2 * Math.PI / 360));
+        }
+        recourse = (float) Math.asin(0 - sgnDLong);
+        recourse = recourse * 360 / 2 / (float) Math.PI;
+
+        if (recourse < 0) {
+            recourse = recourse + 360;
+        }
+        return new RLSailing(recourse, redist);
+    }
+
+    /**
+     * Converts a course from cardinal XddY to ddd notation
+     * @param tc
+     * @param p1 position one
+     * @param p2 position two
+     * @return
+     * @since 1.0
+     */
+    public static double convertCourse(float tc, Position p1, Position p2) {
+
+        double dLat = p1.getLatitude() - p2.getLatitude();
+        double dLong = p1.getLongitude() - p2.getLongitude();
+        //NE
+        if (dLong >= 0 & dLat >= 0) {
+            return Math.abs(tc);
+        }
+
+        //SE
+        if (dLong >= 0 & dLat < 0) {
+            return 180 - Math.abs(tc);
+        }
+
+        //SW
+        if (dLong < 0 & dLat < 0) {
+            return 180 + Math.abs(tc);
+        }
+
+        //NW
+        if (dLong < 0 & dLat >= 0) {
+            return 360 - Math.abs(tc);
+        }
+        return -1;
+    }
+
+    /**
+     * Getter method for the distance between two points
+     * @return distance
+     * @since 1.0
+     */
+    public double getDistance() {
+        return distance;
+    }
+
+    /**
+     * Getter method for the true course
+     * @return true course
+     * @since 1.0
+     */
+    public double getTrueCourse() {
+        return trueCourse;
+    }
+
+    /**
+     * Getter method for the true course
+     * @return true course
+     * @since 1.0
+     */
+    public String getStrCourse() {
+        return strCourse;
+    }
+
+    /**
+     * Computes the difference in meridional parts for two latitudes in minutes
+     * (based on Clark 1880 spheroid)
+     * @param lat1
+     * @param lat2
+     * @return difference in minutes
+     * @since 1.0
+     */
+    public static double computeDMPClarkeSpheroid(double lat1, double lat2) {
+        double absLat1 = Math.abs(lat1);
+        double absLat2 = Math.abs(lat2);
+
+        double m1 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
+                + (absLat1 / 2)))) / Math.log(10))
+                - 23.268932 * Math.sin(Math.toRadians(absLat1))
+                - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat1)), 3)
+                - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat1)), 5));
+
+        double m2 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45
+                + (absLat2 / 2)))) / Math.log(10))
+                - 23.268932 * Math.sin(Math.toRadians(absLat2))
+                - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat2)), 3)
+                - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat2)), 5));
+        if ((lat1 <= 0 && lat2 <= 0) || (lat1 > 0 && lat2 > 0)) {
+            return Math.abs(m1 - m2);
+        } else {
+            return m1 + m2;
+        }
+    }
+
+    /**
+     * Computes the difference in meridional parts for a perfect sphere between
+     * two degrees of latitude
+     * @param lat1
+     * @param lat2
+     * @return difference in meridional parts between lat1 and lat2 in minutes
+     * @since 1.0
+     */
+    public static float computeDMPWGS84Spheroid(float lat1, float lat2) {
+        float absLat1 = Math.abs(lat1);
+        float absLat2 = Math.abs(lat2);
+
+        float m1 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat1 / 2))))
+                - 23.01358 * Math.sin(absLat1 - 0.05135) * Math.pow(Math.sin(absLat1), 3));
+
+        float m2 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat2 / 2))))
+                - 23.01358 * Math.sin(absLat2 - 0.05135) * Math.pow(Math.sin(absLat2), 3));
+
+        if (lat1 <= 0 & lat2 <= 0 || lat1 > 0 & lat2 > 0) {
+            return Math.abs(m1 - m2);
+        } else {
+            return m1 + m2;
+        }
+    }
+
+    /**
+     * Predicts the position of a target for a given time in the future
+     * @param time the number of seconds from now for which to predict the future
+     *        position
+     * @param speed the miles per minute that the target is traveling
+     * @param currentLat the target's current latitude
+     * @param currentLong the target's current longitude
+     * @param course the target's current course in degrees
+     * @return the predicted future position
+     * @since 1.0
+     */
+    public static Position predictPosition(int time, double speed,
+            double currentLat, double currentLong, double course) {
+        Position futurePosition = null;
+        course = Math.toRadians(course);
+        double futureLong = currentLong + speed * time * Math.sin(course);
+        double futureLat = currentLat + speed * time * Math.cos(course);
+        try {
+            futurePosition = new Position(futureLat, futureLong);
+        } catch (InvalidPositionException ipe) {
+            ipe.printStackTrace();
+        }
+        return futurePosition;
+
+    }
+
+    /**
+     * Computes the coordinate of position B relative to an offset given
+     * a distance and an angle.
+     *
+     * @param offset        The offset position.
+     * @param bearing       The bearing between the offset and the coordinate
+     *                      that you want to calculate.
+     * @param distance      The distance, in meters, between the offset
+     *                      and point B.
+     * @return              The position of point B that is located from
+     *                      given offset at given distance and angle.
+     * @since 1.0
+     */
+    public static Position computePosition(Position initialPos, double heading,
+            double distance) {
+        if (initialPos == null) {
+            return null;
+        }
+        double angle;
+        if (heading < 90) {
+            angle = heading;
+        } else if (heading > 90 && heading < 180) {
+            angle = 180 - heading;
+        } else if (heading > 180 && heading < 270) {
+            angle = heading - 180;
+        } else {
+            angle = 360 - heading;
+        }
+
+        Position newPosition = null;
+
+        // Convert meters into nautical miles
+        distance = distance * 0.000539956803;
+        angle = Math.toRadians(angle);
+        double initialLat = initialPos.getLatitude();
+        double initialLong = initialPos.getLongitude();
+        double dlat = distance * Math.cos(angle);
+        dlat = dlat / 60;
+        dlat = Math.abs(dlat);
+        double newLat = 0;
+        if ((heading > 270 && heading < 360) || (heading > 0 && heading < 90)) {
+            newLat = initialLat + dlat;
+        } else if (heading < 270 && heading > 90) {
+            newLat = initialLat - dlat;
+        }
+        double meanLat = (Math.abs(dlat) / 2.0) + newLat;
+        double dep = (Math.abs(dlat * 60)) * Math.tan(angle);
+        double dlong = dep * (1.0 / Math.cos(Math.toRadians(meanLat)));
+        dlong = dlong / 60;
+        dlong = Math.abs(dlong);
+        double newLong;
+        if (heading > 180 && heading < 360) {
+            newLong = initialLong - dlong;
+        } else {
+            newLong = initialLong + dlong;
+        }
+
+        if (newLong < -180) {
+            double diff = Math.abs(newLong + 180);
+            newLong = 180 - diff;
+        }
+
+        if (newLong > 180) {
+            double diff = Math.abs(newLong + 180);
+            newLong = (180 - diff) * -1;
+        }
+
+        if (heading == 0 || heading == 360 || heading == 180) {
+            newLong = initialLong;
+            newLat = initialLat + dlat;
+        } else if (heading == 90 || heading == 270) {
+            newLat = initialLat;
+//            newLong = initialLong + dlong; THIS WAS THE ORIGINAL (IT WORKED)
+            newLong = initialLong - dlong;
+        }
+        try {
+            newPosition = new Position(newLat,
+                    newLong);
+        } catch (InvalidPositionException ipe) {
+            ipe.printStackTrace();
+            System.out.println(newLat + "," + newLong);
+        }
+        return newPosition;
+    }
+
+    /**
+     * Computes the difference in Longitude between two positions and assigns the
+     * correct sign -westwards travel, + eastwards travel
+     * @param lng1
+     * @param lng2
+     * @return difference in longitude
+     * @since 1.0
+     */
+    public static double computeDLong(double lng1, double lng2) {
+        if (lng1 - lng2 == 0) {
+            return 0;
+        }
+
+        // both easterly
+        if (lng1 >= 0 & lng2 >= 0) {
+            return -(lng1 - lng2) * 60;
+        }
+        //both westerly
+        if (lng1 < 0 & lng2 < 0) {
+            return -(lng1 - lng2) * 60;
+        }
+
+        //opposite sides of Date line meridian
+
+        //sum less than 180
+        if (Math.abs(lng1) + Math.abs(lng2) < 180) {
+            if (lng1 < 0 & lng2 > 0) {
+                return -(Math.abs(lng1) + Math.abs(lng2)) * 60;
+            } else {
+                return Math.abs(lng1) + Math.abs(lng2) * 60;
+            }
+        } else {
+            //sum greater than 180
+            if (lng1 < 0 & lng2 > 0) {
+                return -(360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
+            } else {
+                return (360 - (Math.abs(lng1) + Math.abs(lng2))) * 60;
+            }
+        }
+    }
+
+    /**
+     * Computes the difference in Longitude between two positions and assigns the
+     * correct sign -westwards travel, + eastwards travel
+     * @param lng1
+     * @param lng2
+     * @return difference in longitude
+     * @since 1.0
+     */
+    public static double computeLongDiff(double lng1, double lng2) {
+        if (lng1 - lng2 == 0) {
+            return 0;
+        }
+
+        // both easterly
+        if (lng1 >= 0 & lng2 >= 0) {
+            return Math.abs(-(lng1 - lng2) * 60);
+        }
+        //both westerly
+        if (lng1 < 0 & lng2 < 0) {
+            return Math.abs(-(lng1 - lng2) * 60);
+        }
+
+        if (lng1 == 0) {
+            return Math.abs(lng2 * 60);
+        }
+
+        if (lng2 == 0) {
+            return Math.abs(lng1 * 60);
+        }
+
+        return (Math.abs(lng1) + Math.abs(lng2)) * 60;
+    }
+
+    /**
+     * Compute the difference in latitude between two positions
+     * @param lat1
+     * @param lat2
+     * @return difference in latitude
+     * @since 1.0
+     */
+    public static double computeDLat(double lat1, double lat2) {
+        //same side of equator
+
+        //plane sailing
+        if (lat1 - lat2 == 0) {
+            return 0;
+        }
+
+        //both northerly
+        if (lat1 >= 0 & lat2 >= 0) {
+            return -(lat1 - lat2) * 60;
+        }
+        //both southerly
+        if (lat1 < 0 & lat2 < 0) {
+            return -(lat1 - lat2) * 60;
+        }
+
+        //opposite sides of equator
+        if (lat1 >= 0) {
+            //heading south
+            return -(Math.abs(lat1) + Math.abs(lat2));
+        } else {
+            //heading north
+            return (Math.abs(lat1) + Math.abs(lat2));
+        }
+    }
+
+    public static class Quadrant {
+
+        private static final Quadrant FIRST = new Quadrant(1, 1);
+        private static final Quadrant SECOND = new Quadrant(-1, 1);
+        private static final Quadrant THIRD = new Quadrant(-1, -1);
+        private static final Quadrant FOURTH = new Quadrant(1, -1);
+        private final int lonMultiplier;
+        private final int latMultiplier;
+
+        public Quadrant(final int xMultiplier, final int yMultiplier) {
+            this.lonMultiplier = xMultiplier;
+            this.latMultiplier = yMultiplier;
+        }
+
+        static Quadrant getQuadrant(double degrees, boolean invert) {
+            if (invert) {
+                if (degrees >= 0 && degrees <= 90) {
+                    return FOURTH;
+                } else if (degrees > 90 && degrees <= 180) {
+                    return THIRD;
+                } else if (degrees > 180 && degrees <= 270) {
+                    return SECOND;
+                }
+                return FIRST;
+            } else {
+                if (degrees >= 0 && degrees <= 90) {
+                    return FIRST;
+                } else if (degrees > 90 && degrees <= 180) {
+                    return SECOND;
+                } else if (degrees > 180 && degrees <= 270) {
+                    return THIRD;
+                }
+                return FOURTH;
+            }
+        }
+    }
+
+    /**
+     * Converts meters to degrees.
+     *
+     * @param meters            The meters that you want to convert into degrees.
+     * @return                  The degree equivalent of the given meters.
+     * @since 1.0
+     */
+    public static double toDegrees(double meters) {
+        return (meters / METERS_PER_MINUTE) / 60;
+    }
+
+    /**
+     * Computes the bearing between two points.
+     * 
+     * @param p1
+     * @param p2
+     * @return
+     * @since 1.0
+     */
+    public static int computeBearing(Position p1, Position p2) {
+        int bearing;
+        double dLon = computeDLong(p1.getLongitude(), p2.getLongitude());
+        double y = Math.sin(dLon) * Math.cos(p2.getLatitude());
+        double x = Math.cos(p1.getLatitude()) * Math.sin(p2.getLatitude())
+                - Math.sin(p1.getLatitude()) * Math.cos(p2.getLatitude()) * Math.cos(dLon);
+        bearing = (int) Math.toDegrees(Math.atan2(y, x));
+        return bearing;
+    }
+
+    /**
+     * Computes the angle between two points.
+     *
+     * @param p1
+     * @param p2
+     * @return
+     */
+    public static int computeAngle(Position p1, Position p2) {
+        // cos (adj / hyp)
+        double adj = Math.abs(p1.getLongitude() - p2.getLongitude());
+        double opp = Math.abs(p1.getLatitude() - p2.getLatitude());
+        return (int) Math.toDegrees(Math.atan(opp / adj));
+
+//        int angle = (int)Math.atan2(p2.getLatitude() - p1.getLatitude(),
+//                p2.getLongitude() - p1.getLongitude());
+        //Actually it's ATan2(dy , dx) where dy = y2 - y1 and dx = x2 - x1, or ATan(dy / dx)
+    }
+
+    public static int computeHeading(Position p1, Position p2) {
+        int angle = computeAngle(p1, p2);
+        // NE
+        if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() >= p1.getLatitude()) {
+            return angle;
+        } else if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) {
+            // SE
+            return 90 + angle;
+        } else if (p2.getLongitude() <= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) {
+            // SW
+            return 270 - angle;
+        } else {
+            // NW
+            return 270 + angle;
+        }
+    }
+
+    public static void main(String[] args) {
+        try {
+            int pos = NavCalculator.computeHeading(new Position(0, 0), new Position(10, -10));
+//            System.out.println(pos.getLatitude() + "," + pos.getLongitude());
+            System.out.println(pos);
+        } catch (Exception e) {
+        }
+
+
+
+
+
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/NumUtil.java b/engine/src/desktop/jme3tools/navigation/NumUtil.java
new file mode 100644
index 0000000..086fff2
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/NumUtil.java
@@ -0,0 +1,30 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+/**
+ * Provides various helper methods for number conversions (such as degree to radian
+ * conversion, decimal degree to radians etc)
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+public class NumUtil {
+
+    /**
+     * Rounds a number
+     * @param Rval number to be rounded
+     * @param Rpl number of decimal places
+     * @return rounded number
+     * @since 0.1
+     */
+    public float Round(float Rval, int Rpl) {
+        float p = (float) Math.pow(10, Rpl);
+        Rval = Rval * p;
+        float tmp = Math.round(Rval);
+        return (float) tmp / p;
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/Position.java b/engine/src/desktop/jme3tools/navigation/Position.java
new file mode 100644
index 0000000..b55e852
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/Position.java
@@ -0,0 +1,228 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+/**
+ * This class represents the position of an entity in the world.
+ * 
+ * @author Benjamin Jakobus (based on JMarine by Cormac Gebruers and Benjamin Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+public class Position {
+
+    /* the latitude (+ N/E) */
+    private Coordinate lat;
+
+    /* the longitude  (-W/S) */
+    private Coordinate lng;
+
+    /* An optional time to associate with this position - for historical tracking */
+    private String utcTimeStamp;
+
+    /* Degree position */
+    private double degree;
+
+    /**
+     * A new position expressed in decimal format
+     * @param dblLat
+     * @param dblLng
+     * @since 1.0
+     */
+    public Position(double dblLat, double dblLng) throws InvalidPositionException {
+        lat = new Coordinate(dblLat, Coordinate.LAT);
+        lng = new Coordinate(dblLng, Coordinate.LNG);
+    }
+
+    /**
+     * A new position expressed in decimal format and degrees
+     * @param dblLat
+     * @param dblLng
+     * @param degree
+     * @since 1.0
+     */
+//    public Position(double dblLat, double dblLng, double degree) throws InvalidPositionException {
+//        lat = new Coordinate(dblLat, Coordinate.LAT);
+//        lng = new Coordinate(dblLng, Coordinate.LNG);
+//        this.degree = degree;
+//    }
+    /**
+     * A new position expressed in DegMin format
+     * @param latDeg
+     * @param latMin
+     * @param lngDeg
+     * @param lngMin
+     * @since 1.0
+     */
+    public Position(int latDeg, float latMin, int latQuad, int lngDeg,
+            float lngMin, int lngQuad) throws InvalidPositionException {
+        lat = new Coordinate(latDeg, latMin, Coordinate.LAT, latQuad);
+        lng = new Coordinate(lngDeg, lngMin, Coordinate.LNG, lngQuad);
+    }
+
+    /**
+     * A new position expressed in ALRS format
+     * @param lat
+     * @param lng
+     * @since 1.0
+     */
+    public Position(String lat, String lng) throws InvalidPositionException {
+        this.lat = new Coordinate(lat);
+        this.lng = new Coordinate(lng);
+    }
+
+    /**
+     * A new position expressed in NMEA GPS message format:
+     * 4807.038,N,01131.000,E
+     * @param
+     * @param
+     * @param
+     * @param
+     * @since  12.0
+     */
+    public Position(String latNMEAGPS, String latQuad, String lngNMEAGPS, String lngQuad, String utcTimeStamp) {
+        int quad;
+
+        //LAT
+        if (latQuad.compareTo("N") == 0) {
+            quad = Coordinate.N;
+        } else {
+            quad = Coordinate.S;
+        }
+        try {
+            this.lat = new Coordinate(Integer.valueOf(latNMEAGPS.substring(0, 2)), Float.valueOf(latNMEAGPS.substring(2)), Coordinate.LAT, quad);
+        } catch (InvalidPositionException e) {
+            e.printStackTrace();
+        }
+
+        //LNG
+        if (lngQuad.compareTo("E") == 0) {
+            quad = Coordinate.E;
+        } else {
+            quad = Coordinate.W;
+        }
+        try {
+            this.lng = new Coordinate(Integer.valueOf(lngNMEAGPS.substring(0, 3)), Float.valueOf(lngNMEAGPS.substring(3)), Coordinate.LNG, quad);
+        } catch (InvalidPositionException e) {
+            e.printStackTrace();
+        }
+
+        //TIMESTAMP
+        this.associateUTCTime(utcTimeStamp);
+    }
+
+    /**
+     * Add a reference time for this position - useful for historical tracking
+     * @param data
+     * @since 1.0
+     */
+    public void associateUTCTime(String data) {
+        utcTimeStamp = data;
+    }
+
+    /**
+     * Returns the UTC time stamp
+     * @return str the UTC timestamp
+     * @since 1.0
+     */
+    public String utcTimeStamp() {
+        return utcTimeStamp;
+    }
+
+    /**
+     * Prints out position using decimal format
+     * @return the position in decimal format
+     */
+    public String toStringDec() {
+        return lat.toStringDec() + " " + lng.toStringDec();
+    }
+
+    /**
+     * Return the position latitude in decimal format
+     * @return the latitude in decimal format
+     * @since 1.0
+     */
+    public double getLatitude() {
+        return lat.decVal();
+    }
+
+    /**
+     * Returns the degree of the entity
+     * @return degree
+     * @since 1.0
+     */
+//    public double getDegree() {
+//        return degree;
+//    }
+    /**
+     * Return the position longitude in decimal format
+     * @return the longitude in decimal format
+     * @since 1.0
+     */
+    public double getLongitude() {
+        return lng.decVal();
+    }
+
+    /**
+     * Prints out position using DegMin format
+     * @return the position in DegMin Format
+     * @since 1.0
+     */
+    public String toStringDegMin() {
+        String output = "";
+        output += lat.toStringDegMin();
+        output += "   " + lng.toStringDegMin();
+        return output;
+    }
+
+    /**
+     * Prints out the position latitude
+     * @return the latitude as a string for display purposes
+     * @since 1.0
+     */
+    public String toStringDegMinLat() {
+        return lat.toStringDegMin();
+    }
+
+    /**
+     * Prints out the position longitude
+     * @return the longitude as a string for display purposes
+     * @since 1.0
+     */
+    public String toStringDegMinLng() {
+        return lng.toStringDegMin();
+    }
+
+    /**
+     * Prints out the position latitude
+     * @return the latitude as a string for display purposes
+     * @since 1.0
+     */
+    public String toStringDecLat() {
+        return lat.toStringDec();
+    }
+
+    /**
+     * Prints out the position longitude
+     * @return the longitude as a string for display purposes
+     * @since 1.0
+     */
+    public String toStringDecLng() {
+        return lng.toStringDec();
+    }
+
+    //TEST HARNESS - DO NOT DELETE!
+    public static void main(String[] argsc) {
+
+        //NMEA GPS Position format:
+        Position p = new Position("4807.038", "N", "01131.000", "W", "123519");
+        System.out.println(p.toStringDegMinLat());
+        System.out.println(p.getLatitude());
+        System.out.println(p.getLongitude());
+        System.out.println(p.toStringDegMinLng());
+        System.out.println(p.utcTimeStamp());
+
+    }//main
+}
diff --git a/engine/src/desktop/jme3tools/navigation/RLSailing.java b/engine/src/desktop/jme3tools/navigation/RLSailing.java
new file mode 100644
index 0000000..1a9a91c
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/RLSailing.java
@@ -0,0 +1,32 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.navigation;
+
+/**
+ * A utility class to package up a rhumb line sailing
+ * 
+ * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin
+ *          Jakobus)
+ * @version 1.0
+ * @since 1.0
+ */
+public class RLSailing {
+
+    private double course;
+    private double distNM;
+
+    public RLSailing(double pCourse, double pDistNM) {
+        course = pCourse;
+        distNM = pDistNM;
+    }
+
+    public double getCourse() {
+        return course;
+    }
+
+    public double getDistNM() {
+        return distNM;
+    }
+}
diff --git a/engine/src/desktop/jme3tools/navigation/StringUtil.java b/engine/src/desktop/jme3tools/navigation/StringUtil.java
new file mode 100644
index 0000000..b59f68f
--- /dev/null
+++ b/engine/src/desktop/jme3tools/navigation/StringUtil.java
@@ -0,0 +1,260 @@
+/*

+ * To change this template, choose Tools | Templates

+ * and open the template in the editor.

+ */

+package jme3tools.navigation;

+

+import java.util.regex.Pattern;

+

+/**

+ * A collection of String utilities.

+ *

+ * @author Benjamin Jakobus

+ * @version 1.0

+ */

+public class StringUtil {

+

+    /**

+     * Splits a newline (\n) delimited string into an array of strings

+     *

+     * @param str the string to split up

+     * @param delimiter the delimiter to use in splitting

+     * @returns an array of String objects equivalent to str

+     */

+    public String[] splitDelimitedStr(String str, String delimiter) {

+        Pattern pttn = Pattern.compile(delimiter);

+        return pttn.split(str);

+    }

+

+    /**

+     * Right aligns a long number with spaces for printing

+     *

+     * @param num the number to be aligned

+     * @param totalLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNum(long num, int totalLen) {

+        String numStr = Long.toString(num);

+        int len = totalLen - numStr.length();

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += " ";

+        }

+        return pads + numStr;

+    }

+

+    /**

+     * Right aligns a long number with zeros for printing

+     *

+     * @param num the number to be aligned

+     * @param totalLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNumZero(long num, int totalLen) {

+        String numStr = Long.toString(num);

+        int len = totalLen - numStr.length();

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += "0";

+        }

+        return pads + numStr;

+    }

+

+    /**

+     * Right aligns an integer number with spaces for printing

+     *

+     * @param num the number to be aligned

+     * @param totalLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNum(int num, int totalLen) {

+        String numStr = Integer.toString(num);

+        int len = totalLen - numStr.length();

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += " ";

+        }

+        return pads + numStr;

+    }

+

+    /**

+     * Right aligns an integer number with zeros for printing

+     *

+     * @param num the number to be aligned

+     * @param totalLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNumZero(int num, int totalLen) {

+        String numStr = Integer.toString(num);

+        int len = totalLen - numStr.length();

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += "0";

+        }

+        return pads + numStr;

+    }

+

+    /**

+     * Right aligns a double number with spaces for printing

+     *

+     * @param num the number to be aligned

+     * @param wholeLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNum(double num, int wholeLen, int decimalPlaces) {

+        String numStr = Double.toString(num);

+        int dpLoc = numStr.indexOf(".");

+

+        int len = wholeLen - dpLoc;

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += " ";

+        }

+

+        numStr = pads + numStr;

+

+        dpLoc = numStr.indexOf(".");

+

+        if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) {

+            return numStr;

+        }

+        return numStr.substring(0, dpLoc + 1 + decimalPlaces);

+    }

+

+    /**

+     * Right aligns a double number with zeros for printing

+     *

+     * @param num the number to be aligned

+     * @param wholeLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNumZero(double num, int wholeLen, int decimalPlaces) {

+        String numStr = Double.toString(num);

+        int dpLoc = numStr.indexOf(".");

+

+        int len = wholeLen - dpLoc;

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += "0";

+        }

+

+        numStr = pads + numStr;

+

+        dpLoc = numStr.indexOf(".");

+

+        if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) {

+            return numStr;

+        }

+        return numStr.substring(0, dpLoc + 1 + decimalPlaces);

+    }

+

+    /**

+     * Right aligns a float number with spaces for printing

+     *

+     * @param num the number to be aligned

+     * @param wholeLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNum(float num, int wholeLen, int decimalPlaces) {

+        String numStr = Float.toString(num);

+        int dpLoc = numStr.indexOf(".");

+

+        int len = wholeLen - dpLoc;

+        String pads = "";

+        for (int i = 0; i < len; i++) {

+            pads += " ";

+        }

+

+        numStr = pads + numStr;

+

+        dpLoc = numStr.indexOf(".");

+

+        if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) {

+            return numStr;

+        }

+        return numStr.substring(0, dpLoc + 1 + decimalPlaces);

+    }

+

+    /**

+     * Right aligns a float number with zeros for printing

+     *

+     * @param num the number to be aligned

+     * @param wholeLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padNumZero(float num, int wholeLen, int decimalPlaces) {

+        String numStr = Float.toString(num);

+        int dpLoc = numStr.indexOf(".");

+

+        int len = wholeLen - dpLoc;

+        String pads = "";

+

+        if (numStr.charAt(0) == '-') {

+            len += 1;

+            for (int i = 0; i < len; i++) {

+                pads += "0";

+            }

+            pads = "-" + pads;

+            numStr = pads + numStr.substring(1);

+        } else {

+            for (int i = 0; i < len; i++) {

+                pads += "0";

+            }

+            numStr = pads + numStr;

+        }

+

+        dpLoc = numStr.indexOf(".");

+        int length = numStr.substring(dpLoc).length();

+        while (length < decimalPlaces) {

+            numStr += "0";

+        }

+        return numStr;

+

+    }

+

+    /**

+     * Right aligns a float number with zeros for printing

+     *

+     * @param num the number to be aligned

+     * @param wholeLen the total length of the padded string

+     * @return the padded number

+     */

+    public String padStringRight(String input, int wholeLen) {

+        for (int i = input.length(); i < wholeLen; i++) {

+            input += " ";

+        }

+        return input;

+    }

+

+    /**

+     * @param arr a boolean array to be represented as a string

+     * @return the array as a string

+     */

+    public String boolArrToStr(boolean[] arr) {

+        String output = "";

+        for (int i = 0; i < arr.length; i++) {

+            if (arr[i]) {

+                output += "1";

+            } else {

+                output += "0";

+            }

+        }

+        return output;

+    }

+

+    /**

+     * Formats a double nicely for printing: THIS DOES NOT ROUND!!!!

+     * @param num the double to be turned into a pretty string

+     * @return the pretty string

+     */

+    public String prettyNum(double num) {

+        String numStr = (new Double(num)).toString();

+

+        while (numStr.length() < 4) {

+            numStr += "0";

+        }

+

+        numStr = numStr.substring(0, numStr.indexOf(".") + 3);

+        return numStr;

+    }

+}

diff --git a/engine/src/jbullet/com/jme3/bullet/PhysicsSpace.java b/engine/src/jbullet/com/jme3/bullet/PhysicsSpace.java
new file mode 100644
index 0000000..e4dba9c
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/PhysicsSpace.java
@@ -0,0 +1,853 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.bulletphysics.BulletGlobals;
+import com.bulletphysics.ContactAddedCallback;
+import com.bulletphysics.ContactDestroyedCallback;
+import com.bulletphysics.ContactProcessedCallback;
+import com.bulletphysics.collision.broadphase.*;
+import com.bulletphysics.collision.dispatch.CollisionWorld.LocalConvexResult;
+import com.bulletphysics.collision.dispatch.CollisionWorld.LocalRayResult;
+import com.bulletphysics.collision.dispatch.*;
+import com.bulletphysics.collision.narrowphase.ManifoldPoint;
+import com.bulletphysics.collision.shapes.ConvexShape;
+import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
+import com.bulletphysics.dynamics.DynamicsWorld;
+import com.bulletphysics.dynamics.InternalTickCallback;
+import com.bulletphysics.dynamics.RigidBody;
+import com.bulletphysics.dynamics.constraintsolver.ConstraintSolver;
+import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;
+import com.bulletphysics.extras.gimpact.GImpactCollisionAlgorithm;
+import com.jme3.app.AppTask;
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.collision.*;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.PhysicsControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.bullet.util.Converter;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsSpace - The central jbullet-jme physics space</p>
+ * @author normenhansen
+ */
+public class PhysicsSpace {
+
+    public static final int AXIS_X = 0;
+    public static final int AXIS_Y = 1;
+    public static final int AXIS_Z = 2;
+    private static ThreadLocal<ConcurrentLinkedQueue<AppTask<?>>> pQueueTL =
+            new ThreadLocal<ConcurrentLinkedQueue<AppTask<?>>>() {
+
+                @Override
+                protected ConcurrentLinkedQueue<AppTask<?>> initialValue() {
+                    return new ConcurrentLinkedQueue<AppTask<?>>();
+                }
+            };
+    private ConcurrentLinkedQueue<AppTask<?>> pQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+    private static ThreadLocal<PhysicsSpace> physicsSpaceTL = new ThreadLocal<PhysicsSpace>();
+    private DiscreteDynamicsWorld dynamicsWorld = null;
+    private BroadphaseInterface broadphase;
+    private BroadphaseType broadphaseType = BroadphaseType.DBVT;
+    private CollisionDispatcher dispatcher;
+    private ConstraintSolver solver;
+    private DefaultCollisionConfiguration collisionConfiguration;
+//    private Map<GhostObject, PhysicsGhostObject> physicsGhostNodes = new ConcurrentHashMap<GhostObject, PhysicsGhostObject>();
+    private Map<RigidBody, PhysicsRigidBody> physicsNodes = new ConcurrentHashMap<RigidBody, PhysicsRigidBody>();
+    private List<PhysicsJoint> physicsJoints = new LinkedList<PhysicsJoint>();
+    private List<PhysicsCollisionListener> collisionListeners = new LinkedList<PhysicsCollisionListener>();
+    private List<PhysicsCollisionEvent> collisionEvents = new LinkedList<PhysicsCollisionEvent>();
+    private Map<Integer, PhysicsCollisionGroupListener> collisionGroupListeners = new ConcurrentHashMap<Integer, PhysicsCollisionGroupListener>();
+    private ConcurrentLinkedQueue<PhysicsTickListener> tickListeners = new ConcurrentLinkedQueue<PhysicsTickListener>();
+    private PhysicsCollisionEventFactory eventFactory = new PhysicsCollisionEventFactory();
+    private Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f);
+    private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f);
+    private float accuracy = 1f / 60f;
+    private int maxSubSteps = 4;
+    private javax.vecmath.Vector3f rayVec1 = new javax.vecmath.Vector3f();
+    private javax.vecmath.Vector3f rayVec2 = new javax.vecmath.Vector3f();
+    private com.bulletphysics.linearmath.Transform sweepTrans1 = new com.bulletphysics.linearmath.Transform(new javax.vecmath.Matrix3f());
+    private com.bulletphysics.linearmath.Transform sweepTrans2 = new com.bulletphysics.linearmath.Transform(new javax.vecmath.Matrix3f());
+    private AssetManager debugManager;
+
+    /**
+     * Get the current PhysicsSpace <b>running on this thread</b><br/>
+     * For parallel physics, this can also be called from the OpenGL thread to receive the PhysicsSpace
+     * @return the PhysicsSpace running on this thread
+     */
+    public static PhysicsSpace getPhysicsSpace() {
+        return physicsSpaceTL.get();
+    }
+
+    /**
+     * Used internally
+     * @param space
+     */
+    public static void setLocalThreadPhysicsSpace(PhysicsSpace space) {
+        physicsSpaceTL.set(space);
+    }
+
+    public PhysicsSpace() {
+        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), BroadphaseType.DBVT);
+    }
+
+    public PhysicsSpace(BroadphaseType broadphaseType) {
+        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType);
+    }
+
+    public PhysicsSpace(Vector3f worldMin, Vector3f worldMax) {
+        this(worldMin, worldMax, BroadphaseType.AXIS_SWEEP_3);
+    }
+
+    public PhysicsSpace(Vector3f worldMin, Vector3f worldMax, BroadphaseType broadphaseType) {
+        this.worldMin.set(worldMin);
+        this.worldMax.set(worldMax);
+        this.broadphaseType = broadphaseType;
+        create();
+    }
+
+    /**
+     * Has to be called from the (designated) physics thread
+     */
+    public void create() {
+        pQueueTL.set(pQueue);
+
+        collisionConfiguration = new DefaultCollisionConfiguration();
+        dispatcher = new CollisionDispatcher(collisionConfiguration);
+        switch (broadphaseType) {
+            case SIMPLE:
+                broadphase = new SimpleBroadphase();
+                break;
+            case AXIS_SWEEP_3:
+                broadphase = new AxisSweep3(Converter.convert(worldMin), Converter.convert(worldMax));
+                break;
+            case AXIS_SWEEP_3_32:
+                broadphase = new AxisSweep3_32(Converter.convert(worldMin), Converter.convert(worldMax));
+                break;
+            case DBVT:
+                broadphase = new DbvtBroadphase();
+                break;
+        }
+
+        solver = new SequentialImpulseConstraintSolver();
+
+        dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
+        dynamicsWorld.setGravity(new javax.vecmath.Vector3f(0, -9.81f, 0));
+
+        broadphase.getOverlappingPairCache().setInternalGhostPairCallback(new GhostPairCallback());
+        GImpactCollisionAlgorithm.registerAlgorithm(dispatcher);
+
+        physicsSpaceTL.set(this);
+        //register filter callback for tick / collision
+        setTickCallback();
+        setContactCallbacks();
+        //register filter callback for collision groups
+        setOverlapFilterCallback();
+    }
+
+    private void setOverlapFilterCallback() {
+        OverlapFilterCallback callback = new OverlapFilterCallback() {
+
+            public boolean needBroadphaseCollision(BroadphaseProxy bp, BroadphaseProxy bp1) {
+                boolean collides = (bp.collisionFilterGroup & bp1.collisionFilterMask) != 0;
+                if (collides) {
+                    collides = (bp1.collisionFilterGroup & bp.collisionFilterMask) != 0;
+                }
+                if (collides) {
+                    assert (bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject && bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject);
+                    com.bulletphysics.collision.dispatch.CollisionObject colOb = (com.bulletphysics.collision.dispatch.CollisionObject) bp.clientObject;
+                    com.bulletphysics.collision.dispatch.CollisionObject colOb1 = (com.bulletphysics.collision.dispatch.CollisionObject) bp1.clientObject;
+                    assert (colOb.getUserPointer() != null && colOb1.getUserPointer() != null);
+                    PhysicsCollisionObject collisionObject = (PhysicsCollisionObject) colOb.getUserPointer();
+                    PhysicsCollisionObject collisionObject1 = (PhysicsCollisionObject) colOb1.getUserPointer();
+                    if ((collisionObject.getCollideWithGroups() & collisionObject1.getCollisionGroup()) > 0
+                            || (collisionObject1.getCollideWithGroups() & collisionObject.getCollisionGroup()) > 0) {
+                        PhysicsCollisionGroupListener listener = collisionGroupListeners.get(collisionObject.getCollisionGroup());
+                        PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(collisionObject1.getCollisionGroup());
+                        if (listener != null) {
+                            return listener.collide(collisionObject, collisionObject1);
+                        } else if (listener1 != null) {
+                            return listener1.collide(collisionObject, collisionObject1);
+                        }
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }
+                return collides;
+            }
+        };
+        dynamicsWorld.getPairCache().setOverlapFilterCallback(callback);
+    }
+
+    private void setTickCallback() {
+        final PhysicsSpace space = this;
+        InternalTickCallback callback2 = new InternalTickCallback() {
+
+            @Override
+            public void internalTick(DynamicsWorld dw, float f) {
+                //execute task list
+                AppTask task = pQueue.poll();
+                task = pQueue.poll();
+                while (task != null) {
+                    while (task.isCancelled()) {
+                        task = pQueue.poll();
+                    }
+                    try {
+                        task.invoke();
+                    } catch (Exception ex) {
+                        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.SEVERE, null, ex);
+                    }
+                    task = pQueue.poll();
+                }
+                for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+                    PhysicsTickListener physicsTickCallback = it.next();
+                    physicsTickCallback.prePhysicsTick(space, f);
+                }
+            }
+        };
+        dynamicsWorld.setPreTickCallback(callback2);
+        InternalTickCallback callback = new InternalTickCallback() {
+
+            @Override
+            public void internalTick(DynamicsWorld dw, float f) {
+                for (Iterator<PhysicsTickListener> it = tickListeners.iterator(); it.hasNext();) {
+                    PhysicsTickListener physicsTickCallback = it.next();
+                    physicsTickCallback.physicsTick(space, f);
+                }
+            }
+        };
+        dynamicsWorld.setInternalTickCallback(callback, this);
+    }
+
+    private void setContactCallbacks() {
+        BulletGlobals.setContactAddedCallback(new ContactAddedCallback() {
+
+            public boolean contactAdded(ManifoldPoint cp, com.bulletphysics.collision.dispatch.CollisionObject colObj0,
+                    int partId0, int index0, com.bulletphysics.collision.dispatch.CollisionObject colObj1, int partId1,
+                    int index1) {
+                System.out.println("contact added");
+                return true;
+            }
+        });
+
+        BulletGlobals.setContactProcessedCallback(new ContactProcessedCallback() {
+
+            public boolean contactProcessed(ManifoldPoint cp, Object body0, Object body1) {
+                if (body0 instanceof CollisionObject && body1 instanceof CollisionObject) {
+                    PhysicsCollisionObject node = null, node1 = null;
+                    CollisionObject rBody0 = (CollisionObject) body0;
+                    CollisionObject rBody1 = (CollisionObject) body1;
+                    node = (PhysicsCollisionObject) rBody0.getUserPointer();
+                    node1 = (PhysicsCollisionObject) rBody1.getUserPointer();
+                    collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, cp));
+                }
+                return true;
+            }
+        });
+
+        BulletGlobals.setContactDestroyedCallback(new ContactDestroyedCallback() {
+
+            public boolean contactDestroyed(Object userPersistentData) {
+                System.out.println("contact destroyed");
+                return true;
+            }
+        });
+    }
+
+    /**
+     * updates the physics space
+     * @param time the current time value
+     */
+    public void update(float time) {
+        update(time, maxSubSteps);
+    }
+
+    /**
+     * updates the physics space, uses maxSteps<br>
+     * @param time the current time value
+     * @param maxSteps
+     */
+    public void update(float time, int maxSteps) {
+        if (getDynamicsWorld() == null) {
+            return;
+        }
+        //step simulation
+        dynamicsWorld.stepSimulation(time, maxSteps, accuracy);
+    }
+
+    public void distributeEvents() {
+        //add collision callbacks
+        synchronized (collisionEvents) {
+            for (Iterator<PhysicsCollisionEvent> it = collisionEvents.iterator(); it.hasNext();) {
+                PhysicsCollisionEvent physicsCollisionEvent = it.next();
+                for (PhysicsCollisionListener listener : collisionListeners) {
+                    listener.collision(physicsCollisionEvent);
+                }
+                //recycle events
+                eventFactory.recycle(physicsCollisionEvent);
+                it.remove();
+            }
+        }
+    }
+
+    public static <V> Future<V> enqueueOnThisThread(Callable<V> callable) {
+        AppTask<V> task = new AppTask<V>(callable);
+        System.out.println("created apptask");
+        pQueueTL.get().add(task);
+        return task;
+    }
+
+    /**
+     * calls the callable on the next physics tick (ensuring e.g. force applying)
+     * @param <V>
+     * @param callable
+     * @return
+     */
+    public <V> Future<V> enqueue(Callable<V> callable) {
+        AppTask<V> task = new AppTask<V>(callable);
+        pQueue.add(task);
+        return task;
+    }
+
+    /**
+     * adds an object to the physics space
+     * @param obj the PhysicsControl or Spatial with PhysicsControl to add
+     */
+    public void add(Object obj) {
+        if (obj instanceof PhysicsControl) {
+            ((PhysicsControl) obj).setPhysicsSpace(this);
+        } else if (obj instanceof Spatial) {
+            Spatial node = (Spatial) obj;
+            PhysicsControl control = node.getControl(PhysicsControl.class);
+            control.setPhysicsSpace(this);
+        } else if (obj instanceof PhysicsCollisionObject) {
+            addCollisionObject((PhysicsCollisionObject) obj);
+        } else if (obj instanceof PhysicsJoint) {
+            addJoint((PhysicsJoint) obj);
+        } else {
+            throw (new UnsupportedOperationException("Cannot add this kind of object to the physics space."));
+        }
+    }
+
+    public void addCollisionObject(PhysicsCollisionObject obj) {
+        if (obj instanceof PhysicsGhostObject) {
+            addGhostObject((PhysicsGhostObject) obj);
+        } else if (obj instanceof PhysicsRigidBody) {
+            addRigidBody((PhysicsRigidBody) obj);
+        } else if (obj instanceof PhysicsVehicle) {
+            addRigidBody((PhysicsVehicle) obj);
+        } else if (obj instanceof PhysicsCharacter) {
+            addCharacter((PhysicsCharacter) obj);
+        }
+    }
+
+    /**
+     * removes an object from the physics space
+     * @param obj the PhysicsControl or Spatial with PhysicsControl to remove
+     */
+    public void remove(Object obj) {
+        if (obj instanceof PhysicsControl) {
+            ((PhysicsControl) obj).setPhysicsSpace(null);
+        } else if (obj instanceof Spatial) {
+            Spatial node = (Spatial) obj;
+            PhysicsControl control = node.getControl(PhysicsControl.class);
+            control.setPhysicsSpace(null);
+        } else if (obj instanceof PhysicsCollisionObject) {
+            removeCollisionObject((PhysicsCollisionObject) obj);
+        } else if (obj instanceof PhysicsJoint) {
+            removeJoint((PhysicsJoint) obj);
+        } else {
+            throw (new UnsupportedOperationException("Cannot remove this kind of object from the physics space."));
+        }
+    }
+
+    public void removeCollisionObject(PhysicsCollisionObject obj) {
+        if (obj instanceof PhysicsGhostObject) {
+            removeGhostObject((PhysicsGhostObject) obj);
+        } else if (obj instanceof PhysicsRigidBody) {
+            removeRigidBody((PhysicsRigidBody) obj);
+        } else if (obj instanceof PhysicsCharacter) {
+            removeCharacter((PhysicsCharacter) obj);
+        }
+    }
+
+    /**
+     * adds all physics controls and joints in the given spatial node to the physics space
+     * (e.g. after loading from disk) - recursive if node
+     * @param spatial the rootnode containing the physics objects
+     */
+    public void addAll(Spatial spatial) {
+        if (spatial.getControl(RigidBodyControl.class) != null) {
+            RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class);
+            if (!physicsNodes.containsValue(physicsNode)) {
+                physicsNode.setPhysicsSpace(this);
+            }
+            //add joints
+            List<PhysicsJoint> joints = physicsNode.getJoints();
+            for (Iterator<PhysicsJoint> it1 = joints.iterator(); it1.hasNext();) {
+                PhysicsJoint physicsJoint = it1.next();
+                //add connected physicsnodes if they are not already added
+                if (!physicsNodes.containsValue(physicsJoint.getBodyA())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        add(physicsJoint.getBodyA());
+                    } else {
+                        addRigidBody(physicsJoint.getBodyA());
+                    }
+                }
+                if (!physicsNodes.containsValue(physicsJoint.getBodyB())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        add(physicsJoint.getBodyB());
+                    } else {
+                        addRigidBody(physicsJoint.getBodyB());
+                    }
+                }
+                if (!physicsJoints.contains(physicsJoint)) {
+                    addJoint(physicsJoint);
+                }
+            }
+        } else if (spatial.getControl(PhysicsControl.class) != null) {
+            spatial.getControl(PhysicsControl.class).setPhysicsSpace(this);
+        }
+        //recursion
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it = children.iterator(); it.hasNext();) {
+                Spatial spat = it.next();
+                addAll(spat);
+            }
+        }
+    }
+
+    /**
+     * Removes all physics controls and joints in the given spatial from the physics space
+     * (e.g. before saving to disk) - recursive if node
+     * @param spatial the rootnode containing the physics objects
+     */
+    public void removeAll(Spatial spatial) {
+        if (spatial.getControl(RigidBodyControl.class) != null) {
+            RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class);
+            if (physicsNodes.containsValue(physicsNode)) {
+                physicsNode.setPhysicsSpace(null);
+            }
+            //remove joints
+            List<PhysicsJoint> joints = physicsNode.getJoints();
+            for (Iterator<PhysicsJoint> it1 = joints.iterator(); it1.hasNext();) {
+                PhysicsJoint physicsJoint = it1.next();
+                //add connected physicsnodes if they are not already added
+                if (physicsNodes.containsValue(physicsJoint.getBodyA())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        remove(physicsJoint.getBodyA());
+                    } else {
+                        removeRigidBody(physicsJoint.getBodyA());
+                    }
+                }
+                if (physicsNodes.containsValue(physicsJoint.getBodyB())) {
+                    if (physicsJoint.getBodyA() instanceof PhysicsControl) {
+                        remove(physicsJoint.getBodyB());
+                    } else {
+                        removeRigidBody(physicsJoint.getBodyB());
+                    }
+                }
+                if (physicsJoints.contains(physicsJoint)) {
+                    removeJoint(physicsJoint);
+                }
+            }
+        } else if (spatial.getControl(PhysicsControl.class) != null) {
+            spatial.getControl(PhysicsControl.class).setPhysicsSpace(null);
+        }
+        //recursion
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it = children.iterator(); it.hasNext();) {
+                Spatial spat = it.next();
+                removeAll(spat);
+            }
+        }
+    }
+
+    private void addGhostObject(PhysicsGhostObject node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding ghost object {0} to physics space.", node.getObjectId());
+        dynamicsWorld.addCollisionObject(node.getObjectId());
+    }
+
+    private void removeGhostObject(PhysicsGhostObject node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing ghost object {0} from physics space.", node.getObjectId());
+        dynamicsWorld.removeCollisionObject(node.getObjectId());
+    }
+
+    private void addCharacter(PhysicsCharacter node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding character {0} to physics space.", node.getObjectId());
+//        dynamicsWorld.addCollisionObject(node.getObjectId());
+        dynamicsWorld.addCollisionObject(node.getObjectId(), CollisionFilterGroups.CHARACTER_FILTER, (short) (CollisionFilterGroups.STATIC_FILTER | CollisionFilterGroups.DEFAULT_FILTER));
+        dynamicsWorld.addAction(node.getControllerId());
+    }
+
+    private void removeCharacter(PhysicsCharacter node) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing character {0} from physics space.", node.getObjectId());
+        dynamicsWorld.removeAction(node.getControllerId());
+        dynamicsWorld.removeCollisionObject(node.getObjectId());
+    }
+
+    private void addRigidBody(PhysicsRigidBody node) {
+        physicsNodes.put(node.getObjectId(), node);
+
+        //Workaround
+        //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non kinematic again afterward.
+        //so we add it non kinematic, then set it kinematic again.
+        boolean kinematic = false;
+        if (node.isKinematic()) {
+            kinematic = true;
+            node.setKinematic(false);
+        }
+        dynamicsWorld.addRigidBody(node.getObjectId());
+        if (kinematic) {
+            node.setKinematic(true);
+        }
+
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding RigidBody {0} to physics space.", node.getObjectId());
+        if (node instanceof PhysicsVehicle) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding vehicle constraint {0} to physics space.", ((PhysicsVehicle) node).getVehicleId());
+            ((PhysicsVehicle) node).createVehicle(this);
+            dynamicsWorld.addVehicle(((PhysicsVehicle) node).getVehicleId());
+        }
+    }
+
+    private void removeRigidBody(PhysicsRigidBody node) {
+        if (node instanceof PhysicsVehicle) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing vehicle constraint {0} from physics space.", ((PhysicsVehicle) node).getVehicleId());
+            dynamicsWorld.removeVehicle(((PhysicsVehicle) node).getVehicleId());
+        }
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing RigidBody {0} from physics space.", node.getObjectId());
+        physicsNodes.remove(node.getObjectId());
+        dynamicsWorld.removeRigidBody(node.getObjectId());
+    }
+
+    private void addJoint(PhysicsJoint joint) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Adding Joint {0} to physics space.", joint.getObjectId());
+        physicsJoints.add(joint);
+        dynamicsWorld.addConstraint(joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys());
+    }
+
+    private void removeJoint(PhysicsJoint joint) {
+        Logger.getLogger(PhysicsSpace.class.getName()).log(Level.INFO, "Removing Joint {0} from physics space.", joint.getObjectId());
+        physicsJoints.remove(joint);
+        dynamicsWorld.removeConstraint(joint.getObjectId());
+    }
+
+    /**
+     * Sets the gravity of the PhysicsSpace, set before adding physics objects!
+     * @param gravity
+     */
+    public void setGravity(Vector3f gravity) {
+        dynamicsWorld.setGravity(Converter.convert(gravity));
+    }
+
+    /**
+     * applies gravity value to all objects
+     */
+    public void applyGravity() {
+        dynamicsWorld.applyGravity();
+    }
+
+    /**
+     * clears forces of all objects
+     */
+    public void clearForces() {
+        dynamicsWorld.clearForces();
+    }
+
+    /**
+     * Adds the specified listener to the physics tick listeners.
+     * The listeners are called on each physics step, which is not necessarily
+     * each frame but is determined by the accuracy of the physics space.
+     * @param listener
+     */
+    public void addTickListener(PhysicsTickListener listener) {
+        tickListeners.add(listener);
+    }
+
+    public void removeTickListener(PhysicsTickListener listener) {
+        tickListeners.remove(listener);
+    }
+
+    /**
+     * Adds a CollisionListener that will be informed about collision events
+     * @param listener the CollisionListener to add
+     */
+    public void addCollisionListener(PhysicsCollisionListener listener) {
+        collisionListeners.add(listener);
+    }
+
+    /**
+     * Removes a CollisionListener from the list
+     * @param listener the CollisionListener to remove
+     */
+    public void removeCollisionListener(PhysicsCollisionListener listener) {
+        collisionListeners.remove(listener);
+    }
+
+    /**
+     * Adds a listener for a specific collision group, such a listener can disable collisions when they happen.<br>
+     * There can be only one listener per collision group.
+     * @param listener
+     * @param collisionGroup
+     */
+    public void addCollisionGroupListener(PhysicsCollisionGroupListener listener, int collisionGroup) {
+        collisionGroupListeners.put(collisionGroup, listener);
+    }
+
+    public void removeCollisionGroupListener(int collisionGroup) {
+        collisionGroupListeners.remove(collisionGroup);
+    }
+
+    /**
+     * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults
+     */
+    public List<PhysicsRayTestResult> rayTest(Vector3f from, Vector3f to) {
+        List<PhysicsRayTestResult> results = new LinkedList<PhysicsRayTestResult>();
+        dynamicsWorld.rayTest(Converter.convert(from, rayVec1), Converter.convert(to, rayVec2), new InternalRayListener(results));
+        return results;
+    }
+
+    /**
+     * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults
+     */
+    public List<PhysicsRayTestResult> rayTest(Vector3f from, Vector3f to, List<PhysicsRayTestResult> results) {
+        results.clear();
+        dynamicsWorld.rayTest(Converter.convert(from, rayVec1), Converter.convert(to, rayVec2), new InternalRayListener(results));
+        return results;
+    }
+
+    private class InternalRayListener extends CollisionWorld.RayResultCallback {
+
+        private List<PhysicsRayTestResult> results;
+
+        public InternalRayListener(List<PhysicsRayTestResult> results) {
+            this.results = results;
+        }
+
+        @Override
+        public float addSingleResult(LocalRayResult lrr, boolean bln) {
+            PhysicsCollisionObject obj = (PhysicsCollisionObject) lrr.collisionObject.getUserPointer();
+            results.add(new PhysicsRayTestResult(obj, Converter.convert(lrr.hitNormalLocal), lrr.hitFraction, bln));
+            return lrr.hitFraction;
+        }
+    }
+
+    /**
+     * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults<br/>
+     * You have to use different Transforms for start and end (at least distance > 0.4f).
+     * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center.
+     */
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end) {
+        List<PhysicsSweepTestResult> results = new LinkedList<PhysicsSweepTestResult>();
+        if (!(shape.getCShape() instanceof ConvexShape)) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
+            return results;
+        }
+        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
+        return results;
+
+    }
+
+    /**
+     * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults<br/>
+     * You have to use different Transforms for start and end (at least distance > 0.4f).
+     * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center.
+     */
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end, List<PhysicsSweepTestResult> results) {
+        results.clear();
+        if (!(shape.getCShape() instanceof ConvexShape)) {
+            Logger.getLogger(PhysicsSpace.class.getName()).log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
+            return results;
+        }
+        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
+        return results;
+    }
+
+    private class InternalSweepListener extends CollisionWorld.ConvexResultCallback {
+
+        private List<PhysicsSweepTestResult> results;
+
+        public InternalSweepListener(List<PhysicsSweepTestResult> results) {
+            this.results = results;
+        }
+
+        @Override
+        public float addSingleResult(LocalConvexResult lcr, boolean bln) {
+            PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer();
+            results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln));
+            return lcr.hitFraction;
+        }
+    }
+
+    /**
+     * destroys the current PhysicsSpace so that a new one can be created
+     */
+    public void destroy() {
+        physicsNodes.clear();
+        physicsJoints.clear();
+
+        dynamicsWorld.destroy();
+        dynamicsWorld = null;
+    }
+
+    /**
+     * used internally
+     * @return the dynamicsWorld
+     */
+    public DynamicsWorld getDynamicsWorld() {
+        return dynamicsWorld;
+    }
+
+    public BroadphaseType getBroadphaseType() {
+        return broadphaseType;
+    }
+
+    public void setBroadphaseType(BroadphaseType broadphaseType) {
+        this.broadphaseType = broadphaseType;
+    }
+
+    /**
+     * Sets the maximum amount of extra steps that will be used to step the physics
+     * when the fps is below the physics fps. Doing this maintains determinism in physics.
+     * For example a maximum number of 2 can compensate for framerates as low as 30fps
+     * when the physics has the default accuracy of 60 fps. Note that setting this
+     * value too high can make the physics drive down its own fps in case its overloaded.
+     * @param steps The maximum number of extra steps, default is 4.
+     */
+    public void setMaxSubSteps(int steps) {
+        maxSubSteps = steps;
+    }
+
+    /**
+     * get the current accuracy of the physics computation
+     * @return the current accuracy
+     */
+    public float getAccuracy() {
+        return accuracy;
+    }
+
+    /**
+     * sets the accuracy of the physics computation, default=1/60s<br>
+     * @param accuracy
+     */
+    public void setAccuracy(float accuracy) {
+        this.accuracy = accuracy;
+    }
+
+    public Vector3f getWorldMin() {
+        return worldMin;
+    }
+
+    /**
+     * only applies for AXIS_SWEEP broadphase
+     * @param worldMin
+     */
+    public void setWorldMin(Vector3f worldMin) {
+        this.worldMin.set(worldMin);
+    }
+
+    public Vector3f getWorldMax() {
+        return worldMax;
+    }
+
+    /**
+     * only applies for AXIS_SWEEP broadphase
+     * @param worldMax
+     */
+    public void setWorldMax(Vector3f worldMax) {
+        this.worldMax.set(worldMax);
+    }
+
+    /**
+     * Enable debug display for physics
+     * @param manager AssetManager to use to create debug materials
+     */
+    public void enableDebug(AssetManager manager) {
+        debugManager = manager;
+    }
+
+    /**
+     * Disable debug display
+     */
+    public void disableDebug() {
+        debugManager = null;
+    }
+
+    public AssetManager getDebugManager() {
+        return debugManager;
+    }
+
+    /**
+     * interface with Broadphase types
+     */
+    public enum BroadphaseType {
+
+        /**
+         * basic Broadphase
+         */
+        SIMPLE,
+        /**
+         * better Broadphase, needs worldBounds , max Object number = 16384
+         */
+        AXIS_SWEEP_3,
+        /**
+         * better Broadphase, needs worldBounds , max Object number = 65536
+         */
+        AXIS_SWEEP_3_32,
+        /**
+         * Broadphase allowing quicker adding/removing of physics objects
+         */
+        DBVT;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java
new file mode 100644
index 0000000..4dc768a
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEvent.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.bulletphysics.collision.narrowphase.ManifoldPoint;
+import com.jme3.bullet.util.Converter;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.util.EventObject;
+
+/**
+ * A CollisionEvent stores all information about a collision in the PhysicsWorld.
+ * Do not store this Object, as it will be reused after the collision() method has been called.
+ * Get/reference all data you need in the collide method.
+ * @author normenhansen
+ */
+public class PhysicsCollisionEvent extends EventObject {
+
+    public static final int TYPE_ADDED = 0;
+    public static final int TYPE_PROCESSED = 1;
+    public static final int TYPE_DESTROYED = 2;
+    private int type;
+    private PhysicsCollisionObject nodeA;
+    private PhysicsCollisionObject nodeB;
+    private ManifoldPoint cp;
+
+    public PhysicsCollisionEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) {
+        super(source);
+        this.type = type;
+        this.nodeA = source;
+        this.nodeB = nodeB;
+        this.cp = cp;
+    }
+
+    /**
+     * used by event factory, called when event is destroyed
+     */
+    public void clean() {
+        source = null;
+        type = 0;
+        nodeA = null;
+        nodeB = null;
+        cp = null;
+    }
+
+    /**
+     * used by event factory, called when event reused
+     */
+    public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) {
+        this.source = source;
+        this.type = type;
+        this.nodeA = source;
+        this.nodeB = nodeB;
+        this.cp = cp;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial
+     */
+    public Spatial getNodeA() {
+        if (nodeA.getUserObject() instanceof Spatial) {
+            return (Spatial) nodeA.getUserObject();
+        }
+        return null;
+    }
+
+    /**
+     * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial
+     */
+    public Spatial getNodeB() {
+        if (nodeB.getUserObject() instanceof Spatial) {
+            return (Spatial) nodeB.getUserObject();
+        }
+        return null;
+    }
+
+    public PhysicsCollisionObject getObjectA() {
+        return nodeA;
+    }
+
+    public PhysicsCollisionObject getObjectB() {
+        return nodeB;
+    }
+
+    public float getAppliedImpulse() {
+        return cp.appliedImpulse;
+    }
+
+    public float getAppliedImpulseLateral1() {
+        return cp.appliedImpulseLateral1;
+    }
+
+    public float getAppliedImpulseLateral2() {
+        return cp.appliedImpulseLateral2;
+    }
+
+    public float getCombinedFriction() {
+        return cp.combinedFriction;
+    }
+
+    public float getCombinedRestitution() {
+        return cp.combinedRestitution;
+    }
+
+    public float getDistance1() {
+        return cp.distance1;
+    }
+
+    public int getIndex0() {
+        return cp.index0;
+    }
+
+    public int getIndex1() {
+        return cp.index1;
+    }
+
+    public Vector3f getLateralFrictionDir1() {
+        return Converter.convert(cp.lateralFrictionDir1);
+    }
+
+    public Vector3f getLateralFrictionDir2() {
+        return Converter.convert(cp.lateralFrictionDir2);
+    }
+
+    public boolean isLateralFrictionInitialized() {
+        return cp.lateralFrictionInitialized;
+    }
+
+    public int getLifeTime() {
+        return cp.lifeTime;
+    }
+
+    public Vector3f getLocalPointA() {
+        return Converter.convert(cp.localPointA);
+    }
+
+    public Vector3f getLocalPointB() {
+        return Converter.convert(cp.localPointB);
+    }
+
+    public Vector3f getNormalWorldOnB() {
+        return Converter.convert(cp.normalWorldOnB);
+    }
+
+    public int getPartId0() {
+        return cp.partId0;
+    }
+
+    public int getPartId1() {
+        return cp.partId1;
+    }
+
+    public Vector3f getPositionWorldOnA() {
+        return Converter.convert(cp.positionWorldOnA);
+    }
+
+    public Vector3f getPositionWorldOnB() {
+        return Converter.convert(cp.positionWorldOnB);
+    }
+
+    public Object getUserPersistentData() {
+        return cp.userPersistentData;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java
new file mode 100644
index 0000000..cf8d8ae
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.bulletphysics.collision.narrowphase.ManifoldPoint;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class PhysicsCollisionEventFactory {
+
+    private ConcurrentLinkedQueue<PhysicsCollisionEvent> eventBuffer = new ConcurrentLinkedQueue<PhysicsCollisionEvent>();
+
+    public PhysicsCollisionEvent getEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) {
+        PhysicsCollisionEvent event = eventBuffer.poll();
+        if (event == null) {
+            event = new PhysicsCollisionEvent(type, source, nodeB, cp);
+        }else{
+            event.refactor(type, source, nodeB, cp);
+        }
+        return event;
+    }
+
+    public void recycle(PhysicsCollisionEvent event) {
+        event.clean();
+        eventBuffer.add(event);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java
new file mode 100644
index 0000000..0054cf0
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsCollisionObject.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bullet.collision;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.util.DebugShapeFactory;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Base class for collision objects (PhysicsRigidBody, PhysicsGhostObject)
+ * @author normenhansen
+ */
+public abstract class PhysicsCollisionObject implements Savable {
+
+    protected Spatial debugShape;
+    protected Arrow debugArrow;
+    protected Geometry debugArrowGeom;
+    protected Material debugMaterialBlue;
+    protected Material debugMaterialRed;
+    protected Material debugMaterialGreen;
+    protected Material debugMaterialYellow;
+    protected CollisionShape collisionShape;
+    public static final int COLLISION_GROUP_NONE = 0x00000000;
+    public static final int COLLISION_GROUP_01 = 0x00000001;
+    public static final int COLLISION_GROUP_02 = 0x00000002;
+    public static final int COLLISION_GROUP_03 = 0x00000004;
+    public static final int COLLISION_GROUP_04 = 0x00000008;
+    public static final int COLLISION_GROUP_05 = 0x00000010;
+    public static final int COLLISION_GROUP_06 = 0x00000020;
+    public static final int COLLISION_GROUP_07 = 0x00000040;
+    public static final int COLLISION_GROUP_08 = 0x00000080;
+    public static final int COLLISION_GROUP_09 = 0x00000100;
+    public static final int COLLISION_GROUP_10 = 0x00000200;
+    public static final int COLLISION_GROUP_11 = 0x00000400;
+    public static final int COLLISION_GROUP_12 = 0x00000800;
+    public static final int COLLISION_GROUP_13 = 0x00001000;
+    public static final int COLLISION_GROUP_14 = 0x00002000;
+    public static final int COLLISION_GROUP_15 = 0x00004000;
+    public static final int COLLISION_GROUP_16 = 0x00008000;
+    protected int collisionGroup = 0x00000001;
+    protected int collisionGroupsMask = 0x00000001;
+    private Object userObject;
+
+    /**
+     * Sets a CollisionShape to this physics object, note that the object should
+     * not be in the physics space when adding a new collision shape as it is rebuilt
+     * on the physics side.
+     * @param collisionShape the CollisionShape to set
+     */
+    public void setCollisionShape(CollisionShape collisionShape) {
+        this.collisionShape = collisionShape;
+        updateDebugShape();
+    }
+
+    /**
+     * @return the CollisionShape of this PhysicsNode, to be able to reuse it with
+     * other physics nodes (increases performance)
+     */
+    public CollisionShape getCollisionShape() {
+        return collisionShape;
+    }
+
+    /**
+     * Returns the collision group for this collision shape
+     * @return
+     */
+    public int getCollisionGroup() {
+        return collisionGroup;
+    }
+
+    /**
+     * Sets the collision group number for this physics object. <br>
+     * The groups are integer bit masks and some pre-made variables are available in CollisionObject.
+     * All physics objects are by default in COLLISION_GROUP_01.<br>
+     * Two object will collide when <b>one</b> of the partys has the
+     * collisionGroup of the other in its collideWithGroups set.
+     * @param collisionGroup the collisionGroup to set
+     */
+    public void setCollisionGroup(int collisionGroup) {
+        this.collisionGroup = collisionGroup;
+    }
+
+    /**
+     * Add a group that this object will collide with.<br>
+     * Two object will collide when <b>one</b> of the partys has the
+     * collisionGroup of the other in its collideWithGroups set.<br>
+     * @param collisionGroup
+     */
+    public void addCollideWithGroup(int collisionGroup) {
+        this.collisionGroupsMask = this.collisionGroupsMask | collisionGroup;
+    }
+
+    /**
+     * Remove a group from the list this object collides with.
+     * @param collisionGroup
+     */
+    public void removeCollideWithGroup(int collisionGroup) {
+        this.collisionGroupsMask = this.collisionGroupsMask & ~collisionGroup;
+    }
+
+    /**
+     * Directly set the bitmask for collision groups that this object collides with.
+     * @param collisionGroups
+     */
+    public void setCollideWithGroups(int collisionGroups) {
+        this.collisionGroupsMask = collisionGroups;
+    }
+
+    /**
+     * Gets the bitmask of collision groups that this object collides with.
+     * @return
+     */
+    public int getCollideWithGroups() {
+        return collisionGroupsMask;
+    }
+
+    /**
+     * Creates a visual debug shape of the current collision shape of this physics object<br/>
+     * <b>Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging</b>
+     * @param manager AssetManager to load the default wireframe material for the debug shape
+     */
+    protected Spatial attachDebugShape(AssetManager manager) {
+        debugMaterialBlue = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialBlue.getAdditionalRenderState().setWireframe(true);
+        debugMaterialBlue.setColor("Color", ColorRGBA.Blue);
+        debugMaterialGreen = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialGreen.getAdditionalRenderState().setWireframe(true);
+        debugMaterialGreen.setColor("Color", ColorRGBA.Green);
+        debugMaterialRed = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialRed.getAdditionalRenderState().setWireframe(true);
+        debugMaterialRed.setColor("Color", ColorRGBA.Red);
+        debugMaterialYellow = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        debugMaterialYellow.getAdditionalRenderState().setWireframe(true);
+        debugMaterialYellow.setColor("Color", ColorRGBA.Yellow);
+        debugArrow = new Arrow(Vector3f.UNIT_XYZ);
+        debugArrowGeom = new Geometry("DebugArrow", debugArrow);
+        debugArrowGeom.setMaterial(debugMaterialGreen);
+        return attachDebugShape();
+    }
+    
+    /**
+     * creates a debug shape for this CollisionObject
+     * @param manager
+     * @return 
+     */
+    public Spatial createDebugShape(AssetManager manager){
+        return attachDebugShape(manager);
+    }
+
+    protected Spatial attachDebugShape(Material material) {
+        debugMaterialBlue = material;
+        debugMaterialGreen = material;
+        debugMaterialRed = material;
+        debugMaterialYellow = material;
+        debugArrow = new Arrow(Vector3f.UNIT_XYZ);
+        debugArrowGeom = new Geometry("DebugArrow", debugArrow);
+        debugArrowGeom.setMaterial(debugMaterialGreen);
+        return attachDebugShape();
+    }
+
+    public Spatial debugShape() {
+        return debugShape;
+    }
+
+    /**
+     * Creates a visual debug shape of the current collision shape of this physics object<br/>
+     * <b>Does not work with detached physics, please switch to PARALLEL or SEQUENTIAL for debugging</b>
+     * @param material Material to use for the debug shape
+     */
+    protected Spatial attachDebugShape() {
+        if (debugShape != null) {
+            detachDebugShape();
+        }
+        Spatial spatial = getDebugShape();
+        this.debugShape = spatial;
+        return debugShape;
+    }
+
+    protected void updateDebugShape() {
+        if (debugShape != null) {
+            detachDebugShape();
+            attachDebugShape();
+        }
+    }
+
+    protected Spatial getDebugShape() {
+        Spatial spatial = DebugShapeFactory.getDebugShape(collisionShape);
+        if (spatial == null) {
+            return new Node("nullnode");
+        }
+        if (spatial instanceof Node) {
+            List<Spatial> children = ((Node) spatial).getChildren();
+            for (Iterator<Spatial> it1 = children.iterator(); it1.hasNext();) {
+                Spatial spatial1 = it1.next();
+                Geometry geom = ((Geometry) spatial1);
+                geom.setMaterial(debugMaterialBlue);
+                geom.setCullHint(Spatial.CullHint.Never);
+            }
+        } else {
+            Geometry geom = ((Geometry) spatial);
+            geom.setMaterial(debugMaterialBlue);
+            geom.setCullHint(Spatial.CullHint.Never);
+        }
+        spatial.setCullHint(Spatial.CullHint.Never);
+        return spatial;
+    }
+
+    /**
+     * Removes the debug shape
+     */
+    public void detachDebugShape() {
+        debugShape = null;
+    }
+
+    /**
+     * @return the userObject
+     */
+    public Object getUserObject() {
+        return userObject;
+    }
+
+    /**
+     * @param userObject the userObject to set
+     */
+    public void setUserObject(Object userObject) {
+        this.userObject = userObject;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(collisionGroup, "collisionGroup", 0x00000001);
+        capsule.write(collisionGroupsMask, "collisionGroupsMask", 0x00000001);
+        capsule.write(debugShape, "debugShape", null);
+        capsule.write(collisionShape, "collisionShape", null);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        collisionGroup = capsule.readInt("collisionGroup", 0x00000001);
+        collisionGroupsMask = capsule.readInt("collisionGroupsMask", 0x00000001);
+        debugShape = (Spatial) capsule.readSavable("debugShape", null);
+        CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null);
+        collisionShape = shape;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/PhysicsRayTestResult.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsRayTestResult.java
new file mode 100644
index 0000000..1941344
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsRayTestResult.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.math.Vector3f;
+
+/**
+ * Contains the results of a PhysicsSpace rayTest
+ * @author normenhansen
+ */
+public class PhysicsRayTestResult {
+
+    private PhysicsCollisionObject collisionObject;
+    private Vector3f hitNormalLocal;
+    private float hitFraction;
+    private boolean normalInWorldSpace;
+
+    public PhysicsRayTestResult() {
+    }
+
+    public PhysicsRayTestResult(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+
+    /**
+     * @return the collisionObject
+     */
+    public PhysicsCollisionObject getCollisionObject() {
+        return collisionObject;
+    }
+
+    /**
+     * @return the hitNormalLocal
+     */
+    public Vector3f getHitNormalLocal() {
+        return hitNormalLocal;
+    }
+
+    /**
+     * @return the hitFraction
+     */
+    public float getHitFraction() {
+        return hitFraction;
+    }
+
+    /**
+     * @return the normalInWorldSpace
+     */
+    public boolean isNormalInWorldSpace() {
+        return normalInWorldSpace;
+    }
+
+    public void fill(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java
new file mode 100644
index 0000000..d513204
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/PhysicsSweepTestResult.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision;
+
+import com.jme3.math.Vector3f;
+
+/**
+ * Contains the results of a PhysicsSpace rayTest
+ * @author normenhansen
+ */
+public class PhysicsSweepTestResult {
+
+    private PhysicsCollisionObject collisionObject;
+    private Vector3f hitNormalLocal;
+    private float hitFraction;
+    private boolean normalInWorldSpace;
+
+    public PhysicsSweepTestResult() {
+    }
+
+    public PhysicsSweepTestResult(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+
+    /**
+     * @return the collisionObject
+     */
+    public PhysicsCollisionObject getCollisionObject() {
+        return collisionObject;
+    }
+
+    /**
+     * @return the hitNormalLocal
+     */
+    public Vector3f getHitNormalLocal() {
+        return hitNormalLocal;
+    }
+
+    /**
+     * @return the hitFraction
+     */
+    public float getHitFraction() {
+        return hitFraction;
+    }
+
+    /**
+     * @return the normalInWorldSpace
+     */
+    public boolean isNormalInWorldSpace() {
+        return normalInWorldSpace;
+    }
+
+    public void fill(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) {
+        this.collisionObject = collisionObject;
+        this.hitNormalLocal = hitNormalLocal;
+        this.hitFraction = hitFraction;
+        this.normalInWorldSpace = normalInWorldSpace;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java
new file mode 100644
index 0000000..ea3f605
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/BoxCollisionShape.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.BoxShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * Basic box collision shape
+ * @author normenhansen
+ */
+public class BoxCollisionShape extends CollisionShape {
+
+    private Vector3f halfExtents;
+
+    public BoxCollisionShape() {
+    }
+
+    /**
+     * creates a collision box from the given halfExtents
+     * @param halfExtents the halfExtents of the CollisionBox
+     */
+    public BoxCollisionShape(Vector3f halfExtents) {
+        this.halfExtents = halfExtents;
+        createShape();
+    }
+
+    public final Vector3f getHalfExtents() {
+        return halfExtents;
+    }
+    
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1));
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        Vector3f halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(1, 1, 1));
+        this.halfExtents = halfExtents;
+        createShape();
+    }
+
+    protected void createShape() {
+        cShape = new BoxShape(Converter.convert(halfExtents));
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java
new file mode 100644
index 0000000..61024eb
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.CapsuleShape;
+import com.bulletphysics.collision.shapes.CapsuleShapeX;
+import com.bulletphysics.collision.shapes.CapsuleShapeZ;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * Basic capsule collision shape
+ * @author normenhansen
+ */
+public class CapsuleCollisionShape extends CollisionShape{
+    protected float radius,height;
+    protected int axis;
+
+    public CapsuleCollisionShape() {
+    }
+
+    /**
+     * creates a new CapsuleCollisionShape with the given radius and height
+     * @param radius the radius of the capsule
+     * @param height the height of the capsule
+     */
+    public CapsuleCollisionShape(float radius, float height) {
+        this.radius=radius;
+        this.height=height;
+        this.axis=1;
+        CapsuleShape capShape=new CapsuleShape(radius,height);
+        cShape=capShape;
+    }
+
+    /**
+     * creates a capsule shape around the given axis (0=X,1=Y,2=Z)
+     * @param radius
+     * @param height
+     * @param axis
+     */
+    public CapsuleCollisionShape(float radius, float height, int axis) {
+        this.radius=radius;
+        this.height=height;
+        this.axis=axis;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public float getHeight() {
+        return height;
+    }
+
+    public int getAxis() {
+        return axis;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+        capsule.write(height, "height", 1);
+        capsule.write(axis, "axis", 1);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        height = capsule.readFloat("height", 0.5f);
+        axis = capsule.readInt("axis", 1);
+        createShape();
+    }
+
+    protected void createShape(){
+        switch(axis){
+            case 0:
+                cShape=new CapsuleShapeX(radius,height);
+            break;
+            case 1:
+                cShape=new CapsuleShape(radius,height);
+            break;
+            case 2:
+                cShape=new CapsuleShapeZ(radius,height);
+            break;
+        }
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/CollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CollisionShape.java
new file mode 100644
index 0000000..3a36a75
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CollisionShape.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * This Object holds information about a jbullet CollisionShape to be able to reuse
+ * CollisionShapes (as suggested in bullet manuals)
+ * TODO: add static methods to create shapes from nodes (like jbullet-jme constructor)
+ * @author normenhansen
+ */
+public abstract class CollisionShape implements Savable {
+
+    protected com.bulletphysics.collision.shapes.CollisionShape cShape;
+    protected Vector3f scale = new Vector3f(1, 1, 1);
+    protected float margin = 0.0f;
+
+    public CollisionShape() {
+    }
+
+    /**
+     * used internally, not safe
+     */
+    public void calculateLocalInertia(float mass, javax.vecmath.Vector3f vector) {
+        if (cShape == null) {
+            return;
+        }
+        if (this instanceof MeshCollisionShape) {
+            vector.set(0, 0, 0);
+        } else {
+            cShape.calculateLocalInertia(mass, vector);
+        }
+    }
+
+    /**
+     * used internally
+     */
+    public com.bulletphysics.collision.shapes.CollisionShape getCShape() {
+        return cShape;
+    }
+
+    /**
+     * used internally
+     */
+    public void setCShape(com.bulletphysics.collision.shapes.CollisionShape cShape) {
+        this.cShape = cShape;
+    }
+
+    public void setScale(Vector3f scale) {
+        this.scale.set(scale);
+        cShape.setLocalScaling(Converter.convert(scale));
+    }
+
+    public float getMargin() {
+        return cShape.getMargin();
+    }
+
+    public void setMargin(float margin) {
+        cShape.setMargin(margin);
+        this.margin = margin;
+    }
+
+    public Vector3f getScale() {
+        return scale;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(scale, "scale", new Vector3f(1, 1, 1));
+        capsule.write(getMargin(), "margin", 0.0f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1));
+        this.margin = capsule.readFloat("margin", 0.0f);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java
new file mode 100644
index 0000000..1beb066
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.CompoundShape;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A CompoundCollisionShape allows combining multiple base shapes
+ * to generate a more sophisticated shape.
+ * @author normenhansen
+ */
+public class CompoundCollisionShape extends CollisionShape {
+
+    protected ArrayList<ChildCollisionShape> children = new ArrayList<ChildCollisionShape>();
+
+    public CompoundCollisionShape() {
+        cShape = new CompoundShape();
+    }
+
+    /**
+     * adds a child shape at the given local translation
+     * @param shape the child shape to add
+     * @param location the local location of the child shape
+     */
+    public void addChildShape(CollisionShape shape, Vector3f location) {
+        Transform transA = new Transform(Converter.convert(new Matrix3f()));
+        Converter.convert(location, transA.origin);
+        children.add(new ChildCollisionShape(location.clone(), new Matrix3f(), shape));
+        ((CompoundShape) cShape).addChildShape(transA, shape.getCShape());
+    }
+
+    /**
+     * adds a child shape at the given local translation
+     * @param shape the child shape to add
+     * @param location the local location of the child shape
+     */
+    public void addChildShape(CollisionShape shape, Vector3f location, Matrix3f rotation) {
+        if(shape instanceof CompoundCollisionShape){
+            throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!");
+        }
+        Transform transA = new Transform(Converter.convert(rotation));
+        Converter.convert(location, transA.origin);
+        Converter.convert(rotation, transA.basis);
+        children.add(new ChildCollisionShape(location.clone(), rotation.clone(), shape));
+        ((CompoundShape) cShape).addChildShape(transA, shape.getCShape());
+    }
+
+    private void addChildShapeDirect(CollisionShape shape, Vector3f location, Matrix3f rotation) {
+        if(shape instanceof CompoundCollisionShape){
+            throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!");
+        }
+        Transform transA = new Transform(Converter.convert(rotation));
+        Converter.convert(location, transA.origin);
+        Converter.convert(rotation, transA.basis);
+        ((CompoundShape) cShape).addChildShape(transA, shape.getCShape());
+    }
+
+    /**
+     * removes a child shape
+     * @param shape the child shape to remove
+     */
+    public void removeChildShape(CollisionShape shape) {
+        ((CompoundShape) cShape).removeChildShape(shape.getCShape());
+        for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+            ChildCollisionShape childCollisionShape = it.next();
+            if (childCollisionShape.shape == shape) {
+                it.remove();
+            }
+        }
+    }
+
+    public List<ChildCollisionShape> getChildren() {
+        return children;
+    }
+
+    /**
+     * WARNING - CompoundCollisionShape scaling has no effect.
+     */
+    @Override
+    public void setScale(Vector3f scale) {
+        Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CompoundCollisionShape cannot be scaled");
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.writeSavableArrayList(children, "children", new ArrayList<ChildCollisionShape>());
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        children = capsule.readSavableArrayList("children", new ArrayList<ChildCollisionShape>());
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+        loadChildren();
+    }
+
+    private void loadChildren() {
+        for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+            ChildCollisionShape child = it.next();
+            addChildShapeDirect(child.shape, child.location, child.rotation);
+        }
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
new file mode 100644
index 0000000..7103f0d
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
@@ -0,0 +1,77 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.ConeShape;
+import com.bulletphysics.collision.shapes.ConeShapeX;
+import com.bulletphysics.collision.shapes.ConeShapeZ;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class ConeCollisionShape extends CollisionShape {
+
+    protected float radius;
+    protected float height;
+    protected int axis;
+
+    public ConeCollisionShape() {
+    }
+
+    public ConeCollisionShape(float radius, float height, int axis) {
+        this.radius = radius;
+        this.height = radius;
+        this.axis = axis;
+        createShape();
+    }
+
+    public ConeCollisionShape(float radius, float height) {
+        this.radius = radius;
+        this.height = radius;
+        this.axis = PhysicsSpace.AXIS_Y;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+        capsule.write(height, "height", 0.5f);
+        capsule.write(axis, "axis", 0.5f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        radius = capsule.readFloat("height", 0.5f);
+        radius = capsule.readFloat("axis", 0.5f);
+        createShape();
+    }
+
+    protected void createShape() {
+        if (axis == PhysicsSpace.AXIS_X) {
+            cShape = new ConeShapeX(radius, height);
+        } else if (axis == PhysicsSpace.AXIS_Y) {
+            cShape = new ConeShape(radius, height);
+        } else if (axis == PhysicsSpace.AXIS_Z) {
+            cShape = new ConeShapeZ(radius, height);
+        }
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java
new file mode 100644
index 0000000..866a443
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.CylinderShape;
+import com.bulletphysics.collision.shapes.CylinderShapeX;
+import com.bulletphysics.collision.shapes.CylinderShapeZ;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * Basic cylinder collision shape
+ * @author normenhansen
+ */
+public class CylinderCollisionShape extends CollisionShape {
+
+    protected Vector3f halfExtents;
+    protected int axis;
+
+    public CylinderCollisionShape() {
+    }
+
+    /**
+     * creates a cylinder shape from the given halfextents
+     * @param halfExtents the halfextents to use
+     */
+    public CylinderCollisionShape(Vector3f halfExtents) {
+        this.halfExtents = halfExtents;
+        this.axis = 2;
+        createShape();
+    }
+
+    /**
+     * Creates a cylinder shape around the given axis from the given halfextents
+     * @param halfExtents the halfextents to use
+     * @param axis (0=X,1=Y,2=Z)
+     */
+    public CylinderCollisionShape(Vector3f halfExtents, int axis) {
+        this.halfExtents = halfExtents;
+        this.axis = axis;
+        createShape();
+    }
+
+    public final Vector3f getHalfExtents() {
+        return halfExtents;
+    }
+
+    public int getAxis() {
+        return axis;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(halfExtents, "halfExtents", new Vector3f(0.5f, 0.5f, 0.5f));
+        capsule.write(axis, "axis", 1);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(0.5f, 0.5f, 0.5f));
+        axis = capsule.readInt("axis", 1);
+        createShape();
+    }
+
+    protected void createShape() {
+        switch (axis) {
+            case 0:
+                cShape = new CylinderShapeX(Converter.convert(halfExtents));
+                break;
+            case 1:
+                cShape = new CylinderShape(Converter.convert(halfExtents));
+                break;
+            case 2:
+                cShape = new CylinderShapeZ(Converter.convert(halfExtents));
+                break;
+        }
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
new file mode 100644
index 0000000..6d12420
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.IndexedMesh;
+import com.bulletphysics.collision.shapes.TriangleIndexVertexArray;
+import com.bulletphysics.extras.gimpact.GImpactMeshShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Basic mesh collision shape
+ * @author normenhansen
+ */
+public class GImpactCollisionShape extends CollisionShape{
+
+    protected Vector3f worldScale;
+    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
+    protected ByteBuffer triangleIndexBase, vertexBase;
+    protected IndexedMesh bulletMesh;
+
+    public GImpactCollisionShape() {
+    }
+
+    /**
+     * creates a collision shape from the given Mesh
+     * @param mesh the Mesh to use
+     */
+    public GImpactCollisionShape(Mesh mesh) {
+        createCollisionMesh(mesh, new Vector3f(1,1,1));
+    }
+
+
+    private void createCollisionMesh(Mesh mesh, Vector3f worldScale) {
+        this.worldScale = worldScale;
+        bulletMesh = Converter.convert(mesh);
+        this.numVertices = bulletMesh.numVertices;
+        this.numTriangles = bulletMesh.numTriangles;
+        this.vertexStride = bulletMesh.vertexStride;
+        this.triangleIndexStride = bulletMesh.triangleIndexStride;
+        this.triangleIndexBase = bulletMesh.triangleIndexBase;
+        this.vertexBase = bulletMesh.vertexBase;
+        createShape();
+    }
+
+    /**
+     * creates a jme mesh from the collision shape, only needed for debugging
+     */
+    public Mesh createJmeMesh(){
+        return Converter.convert(bulletMesh);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(worldScale, "worldScale", new Vector3f(1, 1, 1));
+        capsule.write(numVertices, "numVertices", 0);
+        capsule.write(numTriangles, "numTriangles", 0);
+        capsule.write(vertexStride, "vertexStride", 0);
+        capsule.write(triangleIndexStride, "triangleIndexStride", 0);
+
+        capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);
+        capsule.write(vertexBase.array(), "vertexBase", new byte[0]);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        worldScale = (Vector3f) capsule.readSavable("worldScale", new Vector3f(1, 1, 1));
+        numVertices = capsule.readInt("numVertices", 0);
+        numTriangles = capsule.readInt("numTriangles", 0);
+        vertexStride = capsule.readInt("vertexStride", 0);
+        triangleIndexStride = capsule.readInt("triangleIndexStride", 0);
+
+        triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));
+        vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0]));
+        createShape();
+    }
+
+    protected void createShape() {
+        bulletMesh = new IndexedMesh();
+        bulletMesh.numVertices = numVertices;
+        bulletMesh.numTriangles = numTriangles;
+        bulletMesh.vertexStride = vertexStride;
+        bulletMesh.triangleIndexStride = triangleIndexStride;
+        bulletMesh.triangleIndexBase = triangleIndexBase;
+        bulletMesh.vertexBase = vertexBase;
+        bulletMesh.triangleIndexBase = triangleIndexBase;
+        TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);
+        cShape = new GImpactMeshShape(tiv);
+        cShape.setLocalScaling(Converter.convert(worldScale));
+        ((GImpactMeshShape)cShape).updateBound();
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
new file mode 100644
index 0000000..7f0611e
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
@@ -0,0 +1,133 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.bullet.collision.shapes;
+
+import com.bulletphysics.dom.HeightfieldTerrainShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+
+/**
+ * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster
+ * than using a regular mesh.
+ * There are a couple tricks though:
+ *	-No rotation or translation is supported.
+ *	-The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being
+ *	equal on either side. If not, the whole collision box is shifted vertically and things don't collide
+ *	as they should.
+ * 
+ * @author Brent Owens
+ */
+public class HeightfieldCollisionShape extends CollisionShape {
+
+	//protected HeightfieldTerrainShape heightfieldShape;
+	protected int heightStickWidth;
+	protected int heightStickLength;
+	protected float[] heightfieldData;
+	protected float heightScale;
+	protected float minHeight;
+	protected float maxHeight;
+	protected int upAxis;
+	protected boolean flipQuadEdges;
+
+	public HeightfieldCollisionShape() {
+
+	}
+
+	public HeightfieldCollisionShape(float[] heightmap) {
+		createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ);
+	}
+
+	public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) {
+		createCollisionHeightfield(heightmap, scale);
+	}
+
+	protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) {
+		this.scale = worldScale;
+		this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale
+		
+		this.heightfieldData = heightmap;
+
+		float min = heightfieldData[0];
+		float max = heightfieldData[0];
+		// calculate min and max height
+		for (int i=0; i<heightfieldData.length; i++) {
+			if (heightfieldData[i] < min)
+				min = heightfieldData[i];
+			if (heightfieldData[i] > max)
+				max = heightfieldData[i];
+		}
+		// we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the
+		// min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect.
+		if (max < 0)
+			max = -min;
+		else {
+			if (Math.abs(max) > Math.abs(min))
+				min = -max;
+			else
+				max = -min;
+		}
+		this.minHeight = min;
+		this.maxHeight = max;
+
+		this.upAxis = HeightfieldTerrainShape.YAXIS;
+		this.flipQuadEdges = false;
+
+		heightStickWidth = (int) FastMath.sqrt(heightfieldData.length);
+		heightStickLength = heightStickWidth;
+
+
+		createShape();
+	}
+	
+	protected void createShape() {
+
+		HeightfieldTerrainShape shape = new HeightfieldTerrainShape(heightStickWidth, heightStickLength, heightfieldData, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges);
+		shape.setLocalScaling(new javax.vecmath.Vector3f(scale.x, scale.y, scale.z));
+		cShape = shape;
+		cShape.setLocalScaling(Converter.convert(getScale()));
+                cShape.setMargin(margin);
+	}
+
+	public Mesh createJmeMesh(){
+        //TODO return Converter.convert(bulletMesh);
+		return null;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(heightStickWidth, "heightStickWidth", 0);
+        capsule.write(heightStickLength, "heightStickLength", 0);
+        capsule.write(heightScale, "heightScale", 0);
+        capsule.write(minHeight, "minHeight", 0);
+        capsule.write(maxHeight, "maxHeight", 0);
+        capsule.write(upAxis, "upAxis", 1);
+        capsule.write(heightfieldData, "heightfieldData", new float[0]);
+        capsule.write(flipQuadEdges, "flipQuadEdges", false);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        heightStickWidth = capsule.readInt("heightStickWidth", 0);
+        heightStickLength = capsule.readInt("heightStickLength", 0);
+        heightScale = capsule.readFloat("heightScale", 0);
+        minHeight = capsule.readFloat("minHeight", 0);
+        maxHeight = capsule.readFloat("maxHeight", 0);
+        upAxis = capsule.readInt("upAxis", 1);
+        heightfieldData = capsule.readFloatArray("heightfieldData", new float[0]);
+        flipQuadEdges = capsule.readBoolean("flipQuadEdges", false);
+        createShape();
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
new file mode 100644
index 0000000..1aa4a67
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
@@ -0,0 +1,79 @@
+package com.jme3.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.ConvexHullShape;
+import com.bulletphysics.util.ObjectArrayList;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import javax.vecmath.Vector3f;
+
+public class HullCollisionShape extends CollisionShape {
+
+    private float[] points;
+
+    public HullCollisionShape() {
+    }
+
+    public HullCollisionShape(Mesh mesh) {
+        this.points = getPoints(mesh);
+        createShape(this.points);
+    }
+
+    public HullCollisionShape(float[] points) {
+        this.points = points;
+        createShape(this.points);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(points, "points", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+
+        // for backwards compatability
+        Mesh mesh = (Mesh) capsule.readSavable("hullMesh", null);
+        if (mesh != null) {
+            this.points = getPoints(mesh);
+        } else {
+            this.points = capsule.readFloatArray("points", null);
+
+        }
+        createShape(this.points);
+    }
+
+    protected void createShape(float[] points) {
+        ObjectArrayList<Vector3f> pointList = new ObjectArrayList<Vector3f>();
+        for (int i = 0; i < points.length; i += 3) {
+            pointList.add(new Vector3f(points[i], points[i + 1], points[i + 2]));
+        }
+        cShape = new ConvexHullShape(pointList);
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+    protected float[] getPoints(Mesh mesh) {
+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
+        vertices.rewind();
+        int components = mesh.getVertexCount() * 3;
+        float[] pointsArray = new float[components];
+        for (int i = 0; i < components; i += 3) {
+            pointsArray[i] = vertices.get();
+            pointsArray[i + 1] = vertices.get();
+            pointsArray[i + 2] = vertices.get();
+        }
+        return pointsArray;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
new file mode 100644
index 0000000..661b8dc
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.BvhTriangleMeshShape;
+import com.bulletphysics.collision.shapes.IndexedMesh;
+import com.bulletphysics.collision.shapes.TriangleIndexVertexArray;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Basic mesh collision shape
+ * @author normenhansen
+ */
+public class MeshCollisionShape extends CollisionShape {
+
+    protected int numVertices, numTriangles, vertexStride, triangleIndexStride;
+    protected ByteBuffer triangleIndexBase, vertexBase;
+    protected IndexedMesh bulletMesh;
+
+    public MeshCollisionShape() {
+    }
+
+    /**
+     * creates a collision shape from the given TriMesh
+     * @param mesh the TriMesh to use
+     */
+    public MeshCollisionShape(Mesh mesh) {
+        createCollisionMesh(mesh, new Vector3f(1, 1, 1));
+    }
+
+    private void createCollisionMesh(Mesh mesh, Vector3f worldScale) {
+        this.scale = worldScale;
+        bulletMesh = Converter.convert(mesh);
+        this.numVertices = bulletMesh.numVertices;
+        this.numTriangles = bulletMesh.numTriangles;
+        this.vertexStride = bulletMesh.vertexStride;
+        this.triangleIndexStride = bulletMesh.triangleIndexStride;
+        this.triangleIndexBase = bulletMesh.triangleIndexBase;
+        this.vertexBase = bulletMesh.vertexBase;
+        createShape();
+    }
+
+    /**
+     * creates a jme mesh from the collision shape, only needed for debugging
+     */
+    public Mesh createJmeMesh(){
+        return Converter.convert(bulletMesh);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(numVertices, "numVertices", 0);
+        capsule.write(numTriangles, "numTriangles", 0);
+        capsule.write(vertexStride, "vertexStride", 0);
+        capsule.write(triangleIndexStride, "triangleIndexStride", 0);
+
+        capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]);
+        capsule.write(vertexBase.array(), "vertexBase", new byte[0]);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        numVertices = capsule.readInt("numVertices", 0);
+        numTriangles = capsule.readInt("numTriangles", 0);
+        vertexStride = capsule.readInt("vertexStride", 0);
+        triangleIndexStride = capsule.readInt("triangleIndexStride", 0);
+
+        triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0]));
+        vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0]));
+        createShape();
+    }
+
+    protected void createShape() {
+        bulletMesh = new IndexedMesh();
+        bulletMesh.numVertices = numVertices;
+        bulletMesh.numTriangles = numTriangles;
+        bulletMesh.vertexStride = vertexStride;
+        bulletMesh.triangleIndexStride = triangleIndexStride;
+        bulletMesh.triangleIndexBase = triangleIndexBase;
+        bulletMesh.vertexBase = vertexBase;
+        bulletMesh.triangleIndexBase = triangleIndexBase;
+        TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride);
+        cShape = new BvhTriangleMeshShape(tiv, true);
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java
new file mode 100644
index 0000000..ce51f18
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java
@@ -0,0 +1,59 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.StaticPlaneShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Plane;
+import java.io.IOException;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class PlaneCollisionShape extends CollisionShape{
+    private Plane plane;
+
+    public PlaneCollisionShape() {
+    }
+
+    /**
+     * Creates a plane Collision shape
+     * @param plane the plane that defines the shape
+     */
+    public PlaneCollisionShape(Plane plane) {
+        this.plane = plane;
+        createShape();
+    }
+
+    public final Plane getPlane() {
+        return plane;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(plane, "collisionPlane", new Plane());
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        plane = (Plane) capsule.readSavable("collisionPlane", new Plane());
+        createShape();
+    }
+
+    protected void createShape() {
+        cShape = new StaticPlaneShape(Converter.convert(plane.getNormal()),plane.getConstant());
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java
new file mode 100644
index 0000000..7ddefd3
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java
@@ -0,0 +1,85 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.BU_Simplex1to4;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * A simple point, line, triangle or quad collisionShape based on one to four points-
+ * @author normenhansen
+ */
+public class SimplexCollisionShape extends CollisionShape {
+
+    private Vector3f vector1, vector2, vector3, vector4;
+
+    public SimplexCollisionShape() {
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3, Vector3f point4) {
+        vector1 = point1;
+        vector2 = point2;
+        vector3 = point3;
+        vector4 = point4;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3) {
+        vector1 = point1;
+        vector2 = point2;
+        vector3 = point3;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1, Vector3f point2) {
+        vector1 = point1;
+        vector2 = point2;
+        createShape();
+    }
+
+    public SimplexCollisionShape(Vector3f point1) {
+        vector1 = point1;
+        createShape();
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(vector1, "simplexPoint1", null);
+        capsule.write(vector2, "simplexPoint2", null);
+        capsule.write(vector3, "simplexPoint3", null);
+        capsule.write(vector4, "simplexPoint4", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        vector1 = (Vector3f) capsule.readSavable("simplexPoint1", null);
+        vector2 = (Vector3f) capsule.readSavable("simplexPoint2", null);
+        vector3 = (Vector3f) capsule.readSavable("simplexPoint3", null);
+        vector4 = (Vector3f) capsule.readSavable("simplexPoint4", null);
+        createShape();
+    }
+
+    protected void createShape() {
+        if (vector4 != null) {
+            cShape = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3), Converter.convert(vector4));
+        } else if (vector3 != null) {
+            cShape = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3));
+        } else if (vector2 != null) {
+            cShape = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2));
+        } else {
+            cShape = new BU_Simplex1to4(Converter.convert(vector1));
+        }
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/engine/src/jbullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java
new file mode 100644
index 0000000..787f597
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/collision/shapes/SphereCollisionShape.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.collision.shapes;
+
+import com.bulletphysics.collision.shapes.SphereShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * Basic sphere collision shape
+ * @author normenhansen
+ */
+public class SphereCollisionShape extends CollisionShape {
+
+    protected float radius;
+
+    public SphereCollisionShape() {
+    }
+
+    /**
+     * creates a SphereCollisionShape with the given radius
+     * @param radius
+     */
+    public SphereCollisionShape(float radius) {
+        this.radius = radius;
+        createShape();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(radius, "radius", 0.5f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        radius = capsule.readFloat("radius", 0.5f);
+        createShape();
+    }
+
+    protected void createShape() {
+        cShape = new SphereShape(radius);
+        cShape.setLocalScaling(Converter.convert(getScale()));
+        cShape.setMargin(margin);
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/ConeJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/ConeJoint.java
new file mode 100644
index 0000000..2dc54f4
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/ConeJoint.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.ConeTwistConstraint;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * To create ragdolls, the conve twist constraint is very useful for limbs like the upper arm.
+ * It is a special point to point constraint that adds cone and twist axis limits.
+ * The x-axis serves as twist axis.
+ * @author normenhansen
+ */
+public class ConeJoint extends PhysicsJoint {
+
+    protected Matrix3f rotA, rotB;
+    protected float swingSpan1 = 1e30f;
+    protected float swingSpan2 = 1e30f;
+    protected float twistSpan = 1e30f;
+    protected boolean angularOnly = false;
+
+    public ConeJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = new Matrix3f();
+        this.rotB = new Matrix3f();
+        createJoint();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA = rotA;
+        this.rotB = rotB;
+        createJoint();
+    }
+
+    public void setLimit(float swingSpan1, float swingSpan2, float twistSpan) {
+        this.swingSpan1 = swingSpan1;
+        this.swingSpan2 = swingSpan2;
+        this.twistSpan = twistSpan;
+        ((ConeTwistConstraint) constraint).setLimit(swingSpan1, swingSpan2, twistSpan);
+    }
+
+    public void setAngularOnly(boolean value) {
+        angularOnly = value;
+        ((ConeTwistConstraint) constraint).setAngularOnly(value);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(rotA, "rotA", new Matrix3f());
+        capsule.write(rotB, "rotB", new Matrix3f());
+
+        capsule.write(angularOnly, "angularOnly", false);
+        capsule.write(swingSpan1, "swingSpan1", 1e30f);
+        capsule.write(swingSpan2, "swingSpan2", 1e30f);
+        capsule.write(twistSpan, "twistSpan", 1e30f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        this.rotA = (Matrix3f) capsule.readSavable("rotA", new Matrix3f());
+        this.rotB = (Matrix3f) capsule.readSavable("rotB", new Matrix3f());
+
+        this.angularOnly = capsule.readBoolean("angularOnly", false);
+        this.swingSpan1 = capsule.readFloat("swingSpan1", 1e30f);
+        this.swingSpan2 = capsule.readFloat("swingSpan2", 1e30f);
+        this.twistSpan = capsule.readFloat("twistSpan", 1e30f);
+        createJoint();
+    }
+
+    protected void createJoint() {
+        Transform transA = new Transform(Converter.convert(rotA));
+        Converter.convert(pivotA, transA.origin);
+        Converter.convert(rotA, transA.basis);
+
+        Transform transB = new Transform(Converter.convert(rotB));
+        Converter.convert(pivotB, transB.origin);
+        Converter.convert(rotB, transB.basis);
+
+        constraint = new ConeTwistConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB);
+        ((ConeTwistConstraint) constraint).setLimit(swingSpan1, swingSpan2, twistSpan);
+        ((ConeTwistConstraint) constraint).setAngularOnly(angularOnly);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/HingeJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/HingeJoint.java
new file mode 100644
index 0000000..47b0250
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/HingeJoint.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.HingeConstraint;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * Hinge constraint, or revolute joint restricts two additional angular degrees of freedom,
+ * so the body can only rotate around one axis, the hinge axis.
+ * This can be useful to represent doors or wheels rotating around one axis.
+ * The user can specify limits and motor for the hinge.
+ * @author normenhansen
+ */
+public class HingeJoint extends PhysicsJoint {
+
+    protected Vector3f axisA;
+    protected Vector3f axisB;
+    protected boolean angularOnly = false;
+    protected float biasFactor = 0.3f;
+    protected float relaxationFactor = 1.0f;
+    protected float limitSoftness = 0.9f;
+
+    public HingeJoint() {
+    }
+
+    /**
+     * Creates a new HingeJoint
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public HingeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Vector3f axisA, Vector3f axisB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.axisA = axisA;
+        this.axisB = axisB;
+        createJoint();
+    }
+
+    public void enableMotor(boolean enable, float targetVelocity, float maxMotorImpulse) {
+        ((HingeConstraint) constraint).enableAngularMotor(enable, targetVelocity, maxMotorImpulse);
+    }
+
+    public void setLimit(float low, float high) {
+        ((HingeConstraint) constraint).setLimit(low, high);
+    }
+
+    public void setLimit(float low, float high, float _softness, float _biasFactor, float _relaxationFactor) {
+        biasFactor = _biasFactor;
+        relaxationFactor = _relaxationFactor;
+        limitSoftness = _softness;
+        ((HingeConstraint) constraint).setLimit(low, high, _softness, _biasFactor, _relaxationFactor);
+    }
+
+    public float getUpperLimit(){
+        return ((HingeConstraint) constraint).getUpperLimit();
+    }
+
+    public float getLowerLimit(){
+        return ((HingeConstraint) constraint).getLowerLimit();
+    }
+
+    public void setAngularOnly(boolean angularOnly) {
+        this.angularOnly = angularOnly;
+        ((HingeConstraint) constraint).setAngularOnly(angularOnly);
+    }
+
+    public float getHingeAngle() {
+        return ((HingeConstraint) constraint).getHingeAngle();
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(axisA, "axisA", new Vector3f());
+        capsule.write(axisB, "axisB", new Vector3f());
+
+        capsule.write(angularOnly, "angularOnly", false);
+
+        capsule.write(((HingeConstraint) constraint).getLowerLimit(), "lowerLimit", 1e30f);
+        capsule.write(((HingeConstraint) constraint).getUpperLimit(), "upperLimit", -1e30f);
+
+        capsule.write(biasFactor, "biasFactor", 0.3f);
+        capsule.write(relaxationFactor, "relaxationFactor", 1f);
+        capsule.write(limitSoftness, "limitSoftness", 0.9f);
+
+        capsule.write(((HingeConstraint) constraint).getEnableAngularMotor(), "enableAngularMotor", false);
+        capsule.write(((HingeConstraint) constraint).getMotorTargetVelosity(), "targetVelocity", 0.0f);
+        capsule.write(((HingeConstraint) constraint).getMaxMotorImpulse(), "maxMotorImpulse", 0.0f);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        this.axisA = (Vector3f) capsule.readSavable("axisA", new Vector3f());
+        this.axisB = (Vector3f) capsule.readSavable("axisB", new Vector3f());
+
+        this.angularOnly = capsule.readBoolean("angularOnly", false);
+        float lowerLimit = capsule.readFloat("lowerLimit", 1e30f);
+        float upperLimit = capsule.readFloat("upperLimit", -1e30f);
+
+        this.biasFactor = capsule.readFloat("biasFactor", 0.3f);
+        this.relaxationFactor = capsule.readFloat("relaxationFactor", 1f);
+        this.limitSoftness = capsule.readFloat("limitSoftness", 0.9f);
+
+        boolean enableAngularMotor=capsule.readBoolean("enableAngularMotor", false);
+        float targetVelocity=capsule.readFloat("targetVelocity", 0.0f);
+        float maxMotorImpulse=capsule.readFloat("maxMotorImpulse", 0.0f);
+
+        createJoint();
+        enableMotor(enableAngularMotor, targetVelocity, maxMotorImpulse);
+        ((HingeConstraint) constraint).setLimit(lowerLimit, upperLimit, limitSoftness, biasFactor, relaxationFactor);
+    }
+
+    protected void createJoint() {
+        constraint = new HingeConstraint(nodeA.getObjectId(), nodeB.getObjectId(),
+                Converter.convert(pivotA), Converter.convert(pivotB),
+                Converter.convert(axisA), Converter.convert(axisB));
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/PhysicsJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/PhysicsJoint.java
new file mode 100644
index 0000000..533078a
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/PhysicsJoint.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.TypedConstraint;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * <p>PhysicsJoint - Basic Phyiscs Joint</p>
+ * @author normenhansen
+ */
+public abstract class PhysicsJoint implements Savable {
+
+    protected TypedConstraint constraint;
+    protected PhysicsRigidBody nodeA;
+    protected PhysicsRigidBody nodeB;
+    protected Vector3f pivotA;
+    protected Vector3f pivotB;
+    protected boolean collisionBetweenLinkedBodys = true;
+
+    public PhysicsJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public PhysicsJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        this.nodeA = nodeA;
+        this.nodeB = nodeB;
+        this.pivotA = pivotA;
+        this.pivotB = pivotB;
+        nodeA.addJoint(this);
+        nodeB.addJoint(this);
+    }
+
+    public float getAppliedImpulse(){
+        return constraint.getAppliedImpulse();
+    }
+
+    /**
+     * @return the constraint
+     */
+    public TypedConstraint getObjectId() {
+        return constraint;
+    }
+
+    /**
+     * @return the collisionBetweenLinkedBodys
+     */
+    public boolean isCollisionBetweenLinkedBodys() {
+        return collisionBetweenLinkedBodys;
+    }
+
+    /**
+     * toggles collisions between linked bodys<br>
+     * joint has to be removed from and added to PhyiscsSpace to apply this.
+     * @param collisionBetweenLinkedBodys set to false to have no collisions between linked bodys
+     */
+    public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodys) {
+        this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodys;
+    }
+
+    public PhysicsRigidBody getBodyA() {
+        return nodeA;
+    }
+
+    public PhysicsRigidBody getBodyB() {
+        return nodeB;
+    }
+
+    public Vector3f getPivotA() {
+        return pivotA;
+    }
+
+    public Vector3f getPivotB() {
+        return pivotB;
+    }
+
+    /**
+     * destroys this joint and removes it from its connected PhysicsRigidBodys joint lists
+     */
+    public void destroy() {
+        getBodyA().removeJoint(this);
+        getBodyB().removeJoint(this);
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(nodeA, "nodeA", null);
+        capsule.write(nodeB, "nodeB", null);
+        capsule.write(pivotA, "pivotA", null);
+        capsule.write(pivotB, "pivotB", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        this.nodeA = ((PhysicsRigidBody) capsule.readSavable("nodeA", new PhysicsRigidBody()));
+        this.nodeB = (PhysicsRigidBody) capsule.readSavable("nodeB", new PhysicsRigidBody());
+        this.pivotA = (Vector3f) capsule.readSavable("pivotA", new Vector3f());
+        this.pivotB = (Vector3f) capsule.readSavable("pivotB", new Vector3f());
+    }
+
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/Point2PointJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/Point2PointJoint.java
new file mode 100644
index 0000000..a3f768a
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/Point2PointJoint.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.Point2PointConstraint;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * Point to point constraint, also known as ball socket joint limits the translation
+ * so that the local pivot points of 2 rigidbodies match in worldspace.
+ * A chain of rigidbodies can be connected using this constraint.
+ * @author normenhansen
+ */
+public class Point2PointJoint extends PhysicsJoint {
+
+    public Point2PointJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public Point2PointJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        createJoint();
+    }
+
+    public void setDamping(float value) {
+        ((Point2PointConstraint) constraint).setting.damping = value;
+    }
+
+    public void setImpulseClamp(float value) {
+        ((Point2PointConstraint) constraint).setting.impulseClamp = value;
+    }
+
+    public void setTau(float value) {
+        ((Point2PointConstraint) constraint).setting.tau = value;
+    }
+
+    public float getDamping() {
+        return ((Point2PointConstraint) constraint).setting.damping;
+    }
+
+    public float getImpulseClamp() {
+        return ((Point2PointConstraint) constraint).setting.impulseClamp;
+    }
+
+    public float getTau() {
+        return ((Point2PointConstraint) constraint).setting.tau;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule cap = ex.getCapsule(this);
+        cap.write(getDamping(), "damping", 1.0f);
+        cap.write(getTau(), "tau", 0.3f);
+        cap.write(getImpulseClamp(), "impulseClamp", 0f);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        createJoint();
+        InputCapsule cap=im.getCapsule(this);
+        setDamping(cap.readFloat("damping", 1.0f));
+        setDamping(cap.readFloat("tau", 0.3f));
+        setDamping(cap.readFloat("impulseClamp", 0f));
+    }
+
+    protected void createJoint() {
+        constraint = new Point2PointConstraint(nodeA.getObjectId(), nodeB.getObjectId(), Converter.convert(pivotA), Converter.convert(pivotB));
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/SixDofJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/SixDofJoint.java
new file mode 100644
index 0000000..e153760
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/SixDofJoint.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.Generic6DofConstraint;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.joints.motors.RotationalLimitMotor;
+import com.jme3.bullet.joints.motors.TranslationalLimitMotor;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * This generic constraint can emulate a variety of standard constraints,
+ * by configuring each of the 6 degrees of freedom (dof).
+ * The first 3 dof axis are linear axis, which represent translation of rigidbodies,
+ * and the latter 3 dof axis represent the angular motion. Each axis can be either locked,
+ * free or limited. On construction of a new btGeneric6DofConstraint, all axis are locked.
+ * Afterwards the axis can be reconfigured. Note that several combinations that
+ * include free and/or limited angular degrees of freedom are undefined.
+ * @author normenhansen
+ */
+public class SixDofJoint extends PhysicsJoint {
+
+    private boolean useLinearReferenceFrameA = true;
+    private LinkedList<RotationalLimitMotor> rotationalMotors = new LinkedList<RotationalLimitMotor>();
+    private TranslationalLimitMotor translationalMotor;
+    private Vector3f angularUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY);
+    private Vector3f angularLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+    private Vector3f linearUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY);
+    private Vector3f linearLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+
+    public SixDofJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+
+        Transform transA = new Transform(Converter.convert(rotA));
+        Converter.convert(pivotA, transA.origin);
+        Converter.convert(rotA, transA.basis);
+
+        Transform transB = new Transform(Converter.convert(rotB));
+        Converter.convert(pivotB, transB.origin);
+        Converter.convert(rotB, transB.basis);
+
+        constraint = new Generic6DofConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA);
+        gatherMotors();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.useLinearReferenceFrameA = useLinearReferenceFrameA;
+
+        Transform transA = new Transform(Converter.convert(new Matrix3f()));
+        Converter.convert(pivotA, transA.origin);
+
+        Transform transB = new Transform(Converter.convert(new Matrix3f()));
+        Converter.convert(pivotB, transB.origin);
+
+        constraint = new Generic6DofConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA);
+        gatherMotors();
+    }
+
+    private void gatherMotors() {
+        for (int i = 0; i < 3; i++) {
+            RotationalLimitMotor rmot = new RotationalLimitMotor(((Generic6DofConstraint) constraint).getRotationalLimitMotor(i));
+            rotationalMotors.add(rmot);
+        }
+        translationalMotor = new TranslationalLimitMotor(((Generic6DofConstraint) constraint).getTranslationalLimitMotor());
+    }
+
+    /**
+     * returns the TranslationalLimitMotor of this 6DofJoint which allows
+     * manipulating the translational axis
+     * @return the TranslationalLimitMotor
+     */
+    public TranslationalLimitMotor getTranslationalLimitMotor() {
+        return translationalMotor;
+    }
+
+    /**
+     * returns one of the three RotationalLimitMotors of this 6DofJoint which
+     * allow manipulating the rotational axes
+     * @param index the index of the RotationalLimitMotor
+     * @return the RotationalLimitMotor at the given index
+     */
+    public RotationalLimitMotor getRotationalLimitMotor(int index) {
+        return rotationalMotors.get(index);
+    }
+
+    public void setLinearUpperLimit(Vector3f vector) {
+        linearUpperLimit.set(vector);
+        ((Generic6DofConstraint) constraint).setLinearUpperLimit(Converter.convert(vector));
+    }
+
+    public void setLinearLowerLimit(Vector3f vector) {
+        linearLowerLimit.set(vector);
+        ((Generic6DofConstraint) constraint).setLinearLowerLimit(Converter.convert(vector));
+    }
+
+    public void setAngularUpperLimit(Vector3f vector) {
+        angularUpperLimit.set(vector);
+        ((Generic6DofConstraint) constraint).setAngularUpperLimit(Converter.convert(vector));
+    }
+
+    public void setAngularLowerLimit(Vector3f vector) {
+        angularLowerLimit.set(vector);
+        ((Generic6DofConstraint) constraint).setAngularLowerLimit(Converter.convert(vector));
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+
+        Transform transA = new Transform(Converter.convert(new Matrix3f()));
+        Converter.convert(pivotA, transA.origin);
+
+        Transform transB = new Transform(Converter.convert(new Matrix3f()));
+        Converter.convert(pivotB, transB.origin);
+        constraint = new Generic6DofConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA);
+        gatherMotors();
+
+        setAngularUpperLimit((Vector3f) capsule.readSavable("angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)));
+        setAngularLowerLimit((Vector3f) capsule.readSavable("angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)));
+        setLinearUpperLimit((Vector3f) capsule.readSavable("linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)));
+        setLinearLowerLimit((Vector3f) capsule.readSavable("linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)));
+
+        for (int i = 0; i < 3; i++) {
+            RotationalLimitMotor rotationalLimitMotor = getRotationalLimitMotor(i);
+            rotationalLimitMotor.setBounce(capsule.readFloat("rotMotor" + i + "_Bounce", 0.0f));
+            rotationalLimitMotor.setDamping(capsule.readFloat("rotMotor" + i + "_Damping", 1.0f));
+            rotationalLimitMotor.setERP(capsule.readFloat("rotMotor" + i + "_ERP", 0.5f));
+            rotationalLimitMotor.setHiLimit(capsule.readFloat("rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY));
+            rotationalLimitMotor.setLimitSoftness(capsule.readFloat("rotMotor" + i + "_LimitSoftness", 0.5f));
+            rotationalLimitMotor.setLoLimit(capsule.readFloat("rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY));
+            rotationalLimitMotor.setMaxLimitForce(capsule.readFloat("rotMotor" + i + "_MaxLimitForce", 300.0f));
+            rotationalLimitMotor.setMaxMotorForce(capsule.readFloat("rotMotor" + i + "_MaxMotorForce", 0.1f));
+            rotationalLimitMotor.setTargetVelocity(capsule.readFloat("rotMotor" + i + "_TargetVelocity", 0));
+            rotationalLimitMotor.setEnableMotor(capsule.readBoolean("rotMotor" + i + "_EnableMotor", false));
+        }
+        getTranslationalLimitMotor().setAccumulatedImpulse((Vector3f) capsule.readSavable("transMotor_AccumulatedImpulse", Vector3f.ZERO));
+        getTranslationalLimitMotor().setDamping(capsule.readFloat("transMotor_Damping", 1.0f));
+        getTranslationalLimitMotor().setLimitSoftness(capsule.readFloat("transMotor_LimitSoftness", 0.7f));
+        getTranslationalLimitMotor().setLowerLimit((Vector3f) capsule.readSavable("transMotor_LowerLimit", Vector3f.ZERO));
+        getTranslationalLimitMotor().setRestitution(capsule.readFloat("transMotor_Restitution", 0.5f));
+        getTranslationalLimitMotor().setUpperLimit((Vector3f) capsule.readSavable("transMotor_UpperLimit", Vector3f.ZERO));
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(angularUpperLimit, "angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY));
+        capsule.write(angularLowerLimit, "angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY));
+        capsule.write(linearUpperLimit, "linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY));
+        capsule.write(linearLowerLimit, "linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY));
+        int i = 0;
+        for (Iterator<RotationalLimitMotor> it = rotationalMotors.iterator(); it.hasNext();) {
+            RotationalLimitMotor rotationalLimitMotor = it.next();
+            capsule.write(rotationalLimitMotor.getBounce(), "rotMotor" + i + "_Bounce", 0.0f);
+            capsule.write(rotationalLimitMotor.getDamping(), "rotMotor" + i + "_Damping", 1.0f);
+            capsule.write(rotationalLimitMotor.getERP(), "rotMotor" + i + "_ERP", 0.5f);
+            capsule.write(rotationalLimitMotor.getHiLimit(), "rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY);
+            capsule.write(rotationalLimitMotor.getLimitSoftness(), "rotMotor" + i + "_LimitSoftness", 0.5f);
+            capsule.write(rotationalLimitMotor.getLoLimit(), "rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY);
+            capsule.write(rotationalLimitMotor.getMaxLimitForce(), "rotMotor" + i + "_MaxLimitForce", 300.0f);
+            capsule.write(rotationalLimitMotor.getMaxMotorForce(), "rotMotor" + i + "_MaxMotorForce", 0.1f);
+            capsule.write(rotationalLimitMotor.getTargetVelocity(), "rotMotor" + i + "_TargetVelocity", 0);
+            capsule.write(rotationalLimitMotor.isEnableMotor(), "rotMotor" + i + "_EnableMotor", false);
+            i++;
+        }
+        capsule.write(getTranslationalLimitMotor().getAccumulatedImpulse(), "transMotor_AccumulatedImpulse", Vector3f.ZERO);
+        capsule.write(getTranslationalLimitMotor().getDamping(), "transMotor_Damping", 1.0f);
+        capsule.write(getTranslationalLimitMotor().getLimitSoftness(), "transMotor_LimitSoftness", 0.7f);
+        capsule.write(getTranslationalLimitMotor().getLowerLimit(), "transMotor_LowerLimit", Vector3f.ZERO);
+        capsule.write(getTranslationalLimitMotor().getRestitution(), "transMotor_Restitution", 0.5f);
+        capsule.write(getTranslationalLimitMotor().getUpperLimit(), "transMotor_UpperLimit", Vector3f.ZERO);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/SliderJoint.java b/engine/src/jbullet/com/jme3/bullet/joints/SliderJoint.java
new file mode 100644
index 0000000..4602018
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/SliderJoint.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints;
+
+import com.bulletphysics.dynamics.constraintsolver.SliderConstraint;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * <i>From bullet manual:</i><br>
+ * The slider constraint allows the body to rotate around one axis and translate along this axis.
+ * @author normenhansen
+ */
+public class SliderJoint extends PhysicsJoint {
+    protected Matrix3f rotA, rotB;
+    protected boolean useLinearReferenceFrameA;
+
+    public SliderJoint() {
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA=rotA;
+        this.rotB=rotB;
+        this.useLinearReferenceFrameA=useLinearReferenceFrameA;
+        createJoint();
+    }
+
+    /**
+     * @param pivotA local translation of the joint connection point in node A
+     * @param pivotB local translation of the joint connection point in node B
+     */
+    public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) {
+        super(nodeA, nodeB, pivotA, pivotB);
+        this.rotA=new Matrix3f();
+        this.rotB=new Matrix3f();
+        this.useLinearReferenceFrameA=useLinearReferenceFrameA;
+        createJoint();
+    }
+
+    public float getLowerLinLimit() {
+        return ((SliderConstraint) constraint).getLowerLinLimit();
+    }
+
+    public void setLowerLinLimit(float lowerLinLimit) {
+        ((SliderConstraint) constraint).setLowerLinLimit(lowerLinLimit);
+    }
+
+    public float getUpperLinLimit() {
+        return ((SliderConstraint) constraint).getUpperLinLimit();
+    }
+
+    public void setUpperLinLimit(float upperLinLimit) {
+        ((SliderConstraint) constraint).setUpperLinLimit(upperLinLimit);
+    }
+
+    public float getLowerAngLimit() {
+        return ((SliderConstraint) constraint).getLowerAngLimit();
+    }
+
+    public void setLowerAngLimit(float lowerAngLimit) {
+        ((SliderConstraint) constraint).setLowerAngLimit(lowerAngLimit);
+    }
+
+    public float getUpperAngLimit() {
+        return ((SliderConstraint) constraint).getUpperAngLimit();
+    }
+
+    public void setUpperAngLimit(float upperAngLimit) {
+        ((SliderConstraint) constraint).setUpperAngLimit(upperAngLimit);
+    }
+
+    public float getSoftnessDirLin() {
+        return ((SliderConstraint) constraint).getSoftnessDirLin();
+    }
+
+    public void setSoftnessDirLin(float softnessDirLin) {
+        ((SliderConstraint) constraint).setSoftnessDirLin(softnessDirLin);
+    }
+
+    public float getRestitutionDirLin() {
+        return ((SliderConstraint) constraint).getRestitutionDirLin();
+    }
+
+    public void setRestitutionDirLin(float restitutionDirLin) {
+        ((SliderConstraint) constraint).setRestitutionDirLin(restitutionDirLin);
+    }
+
+    public float getDampingDirLin() {
+        return ((SliderConstraint) constraint).getDampingDirLin();
+    }
+
+    public void setDampingDirLin(float dampingDirLin) {
+        ((SliderConstraint) constraint).setDampingDirLin(dampingDirLin);
+    }
+
+    public float getSoftnessDirAng() {
+        return ((SliderConstraint) constraint).getSoftnessDirAng();
+    }
+
+    public void setSoftnessDirAng(float softnessDirAng) {
+        ((SliderConstraint) constraint).setSoftnessDirAng(softnessDirAng);
+    }
+
+    public float getRestitutionDirAng() {
+        return ((SliderConstraint) constraint).getRestitutionDirAng();
+    }
+
+    public void setRestitutionDirAng(float restitutionDirAng) {
+        ((SliderConstraint) constraint).setRestitutionDirAng(restitutionDirAng);
+    }
+
+    public float getDampingDirAng() {
+        return ((SliderConstraint) constraint).getDampingDirAng();
+    }
+
+    public void setDampingDirAng(float dampingDirAng) {
+        ((SliderConstraint) constraint).setDampingDirAng(dampingDirAng);
+    }
+
+    public float getSoftnessLimLin() {
+        return ((SliderConstraint) constraint).getSoftnessLimLin();
+    }
+
+    public void setSoftnessLimLin(float softnessLimLin) {
+        ((SliderConstraint) constraint).setSoftnessLimLin(softnessLimLin);
+    }
+
+    public float getRestitutionLimLin() {
+        return ((SliderConstraint) constraint).getRestitutionLimLin();
+    }
+
+    public void setRestitutionLimLin(float restitutionLimLin) {
+        ((SliderConstraint) constraint).setRestitutionLimLin(restitutionLimLin);
+    }
+
+    public float getDampingLimLin() {
+        return ((SliderConstraint) constraint).getDampingLimLin();
+    }
+
+    public void setDampingLimLin(float dampingLimLin) {
+        ((SliderConstraint) constraint).setDampingLimLin(dampingLimLin);
+    }
+
+    public float getSoftnessLimAng() {
+        return ((SliderConstraint) constraint).getSoftnessLimAng();
+    }
+
+    public void setSoftnessLimAng(float softnessLimAng) {
+        ((SliderConstraint) constraint).setSoftnessLimAng(softnessLimAng);
+    }
+
+    public float getRestitutionLimAng() {
+        return ((SliderConstraint) constraint).getRestitutionLimAng();
+    }
+
+    public void setRestitutionLimAng(float restitutionLimAng) {
+        ((SliderConstraint) constraint).setRestitutionLimAng(restitutionLimAng);
+    }
+
+    public float getDampingLimAng() {
+        return ((SliderConstraint) constraint).getDampingLimAng();
+    }
+
+    public void setDampingLimAng(float dampingLimAng) {
+        ((SliderConstraint) constraint).setDampingLimAng(dampingLimAng);
+    }
+
+    public float getSoftnessOrthoLin() {
+        return ((SliderConstraint) constraint).getSoftnessOrthoLin();
+    }
+
+    public void setSoftnessOrthoLin(float softnessOrthoLin) {
+        ((SliderConstraint) constraint).setSoftnessOrthoLin(softnessOrthoLin);
+    }
+
+    public float getRestitutionOrthoLin() {
+        return ((SliderConstraint) constraint).getRestitutionOrthoLin();
+    }
+
+    public void setRestitutionOrthoLin(float restitutionOrthoLin) {
+        ((SliderConstraint) constraint).setRestitutionOrthoLin(restitutionOrthoLin);
+    }
+
+    public float getDampingOrthoLin() {
+        return ((SliderConstraint) constraint).getDampingOrthoLin();
+    }
+
+    public void setDampingOrthoLin(float dampingOrthoLin) {
+        ((SliderConstraint) constraint).setDampingOrthoLin(dampingOrthoLin);
+    }
+
+    public float getSoftnessOrthoAng() {
+        return ((SliderConstraint) constraint).getSoftnessOrthoAng();
+    }
+
+    public void setSoftnessOrthoAng(float softnessOrthoAng) {
+        ((SliderConstraint) constraint).setSoftnessOrthoAng(softnessOrthoAng);
+    }
+
+    public float getRestitutionOrthoAng() {
+        return ((SliderConstraint) constraint).getRestitutionOrthoAng();
+    }
+
+    public void setRestitutionOrthoAng(float restitutionOrthoAng) {
+        ((SliderConstraint) constraint).setRestitutionOrthoAng(restitutionOrthoAng);
+    }
+
+    public float getDampingOrthoAng() {
+        return ((SliderConstraint) constraint).getDampingOrthoAng();
+    }
+
+    public void setDampingOrthoAng(float dampingOrthoAng) {
+        ((SliderConstraint) constraint).setDampingOrthoAng(dampingOrthoAng);
+    }
+
+    public boolean isPoweredLinMotor() {
+        return ((SliderConstraint) constraint).getPoweredLinMotor();
+    }
+
+    public void setPoweredLinMotor(boolean poweredLinMotor) {
+        ((SliderConstraint) constraint).setPoweredLinMotor(poweredLinMotor);
+    }
+
+    public float getTargetLinMotorVelocity() {
+        return ((SliderConstraint) constraint).getTargetLinMotorVelocity();
+    }
+
+    public void setTargetLinMotorVelocity(float targetLinMotorVelocity) {
+        ((SliderConstraint) constraint).setTargetLinMotorVelocity(targetLinMotorVelocity);
+    }
+
+    public float getMaxLinMotorForce() {
+        return ((SliderConstraint) constraint).getMaxLinMotorForce();
+    }
+
+    public void setMaxLinMotorForce(float maxLinMotorForce) {
+        ((SliderConstraint) constraint).setMaxLinMotorForce(maxLinMotorForce);
+    }
+
+    public boolean isPoweredAngMotor() {
+        return ((SliderConstraint) constraint).getPoweredAngMotor();
+    }
+
+    public void setPoweredAngMotor(boolean poweredAngMotor) {
+        ((SliderConstraint) constraint).setPoweredAngMotor(poweredAngMotor);
+    }
+
+    public float getTargetAngMotorVelocity() {
+        return ((SliderConstraint) constraint).getTargetAngMotorVelocity();
+    }
+
+    public void setTargetAngMotorVelocity(float targetAngMotorVelocity) {
+        ((SliderConstraint) constraint).setTargetAngMotorVelocity(targetAngMotorVelocity);
+    }
+
+    public float getMaxAngMotorForce() {
+        return ((SliderConstraint) constraint).getMaxAngMotorForce();
+    }
+
+    public void setMaxAngMotorForce(float maxAngMotorForce) {
+        ((SliderConstraint) constraint).setMaxAngMotorForce(maxAngMotorForce);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule capsule = ex.getCapsule(this);
+        //TODO: standard values..
+        capsule.write(((SliderConstraint) constraint).getDampingDirAng(), "dampingDirAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getDampingDirLin(), "dampingDirLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getDampingLimAng(), "dampingLimAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getDampingLimLin(), "dampingLimLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getDampingOrthoAng(), "dampingOrthoAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getDampingOrthoLin(), "dampingOrthoLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getLowerAngLimit(), "lowerAngLimit", 0f);
+        capsule.write(((SliderConstraint) constraint).getLowerLinLimit(), "lowerLinLimit", 0f);
+        capsule.write(((SliderConstraint) constraint).getMaxAngMotorForce(), "maxAngMotorForce", 0f);
+        capsule.write(((SliderConstraint) constraint).getMaxLinMotorForce(), "maxLinMotorForce", 0f);
+        capsule.write(((SliderConstraint) constraint).getPoweredAngMotor(), "poweredAngMotor", false);
+        capsule.write(((SliderConstraint) constraint).getPoweredLinMotor(), "poweredLinMotor", false);
+        capsule.write(((SliderConstraint) constraint).getRestitutionDirAng(), "restitutionDirAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getRestitutionDirLin(), "restitutionDirLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getRestitutionLimAng(), "restitutionLimAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getRestitutionLimLin(), "restitutionLimLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getRestitutionOrthoAng(), "restitutionOrthoAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getRestitutionOrthoLin(), "restitutionOrthoLin", 0f);
+
+        capsule.write(((SliderConstraint) constraint).getSoftnessDirAng(), "softnessDirAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getSoftnessDirLin(), "softnessDirLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getSoftnessLimAng(), "softnessLimAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getSoftnessLimLin(), "softnessLimLin", 0f);
+        capsule.write(((SliderConstraint) constraint).getSoftnessOrthoAng(), "softnessOrthoAng", 0f);
+        capsule.write(((SliderConstraint) constraint).getSoftnessOrthoLin(), "softnessOrthoLin", 0f);
+
+        capsule.write(((SliderConstraint) constraint).getTargetAngMotorVelocity(), "targetAngMotorVelicoty", 0f);
+        capsule.write(((SliderConstraint) constraint).getTargetLinMotorVelocity(), "targetLinMotorVelicoty", 0f);
+
+        capsule.write(((SliderConstraint) constraint).getUpperAngLimit(), "upperAngLimit", 0f);
+        capsule.write(((SliderConstraint) constraint).getUpperLinLimit(), "upperLinLimit", 0f);
+
+        capsule.write(useLinearReferenceFrameA, "useLinearReferenceFrameA", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule capsule = im.getCapsule(this);
+        float dampingDirAng = capsule.readFloat("dampingDirAng", 0f);
+        float dampingDirLin = capsule.readFloat("dampingDirLin", 0f);
+        float dampingLimAng = capsule.readFloat("dampingLimAng", 0f);
+        float dampingLimLin = capsule.readFloat("dampingLimLin", 0f);
+        float dampingOrthoAng = capsule.readFloat("dampingOrthoAng", 0f);
+        float dampingOrthoLin = capsule.readFloat("dampingOrthoLin", 0f);
+        float lowerAngLimit = capsule.readFloat("lowerAngLimit", 0f);
+        float lowerLinLimit = capsule.readFloat("lowerLinLimit", 0f);
+        float maxAngMotorForce = capsule.readFloat("maxAngMotorForce", 0f);
+        float maxLinMotorForce = capsule.readFloat("maxLinMotorForce", 0f);
+        boolean poweredAngMotor = capsule.readBoolean("poweredAngMotor", false);
+        boolean poweredLinMotor = capsule.readBoolean("poweredLinMotor", false);
+        float restitutionDirAng = capsule.readFloat("restitutionDirAng", 0f);
+        float restitutionDirLin = capsule.readFloat("restitutionDirLin", 0f);
+        float restitutionLimAng = capsule.readFloat("restitutionLimAng", 0f);
+        float restitutionLimLin = capsule.readFloat("restitutionLimLin", 0f);
+        float restitutionOrthoAng = capsule.readFloat("restitutionOrthoAng", 0f);
+        float restitutionOrthoLin = capsule.readFloat("restitutionOrthoLin", 0f);
+
+        float softnessDirAng = capsule.readFloat("softnessDirAng", 0f);
+        float softnessDirLin = capsule.readFloat("softnessDirLin", 0f);
+        float softnessLimAng = capsule.readFloat("softnessLimAng", 0f);
+        float softnessLimLin = capsule.readFloat("softnessLimLin", 0f);
+        float softnessOrthoAng = capsule.readFloat("softnessOrthoAng", 0f);
+        float softnessOrthoLin = capsule.readFloat("softnessOrthoLin", 0f);
+
+        float targetAngMotorVelicoty = capsule.readFloat("targetAngMotorVelicoty", 0f);
+        float targetLinMotorVelicoty = capsule.readFloat("targetLinMotorVelicoty", 0f);
+
+        float upperAngLimit = capsule.readFloat("upperAngLimit", 0f);
+        float upperLinLimit = capsule.readFloat("upperLinLimit", 0f);
+
+        useLinearReferenceFrameA = capsule.readBoolean("useLinearReferenceFrameA", false);
+
+        createJoint();
+
+        ((SliderConstraint)constraint).setDampingDirAng(dampingDirAng);
+        ((SliderConstraint)constraint).setDampingDirLin(dampingDirLin);
+        ((SliderConstraint)constraint).setDampingLimAng(dampingLimAng);
+        ((SliderConstraint)constraint).setDampingLimLin(dampingLimLin);
+        ((SliderConstraint)constraint).setDampingOrthoAng(dampingOrthoAng);
+        ((SliderConstraint)constraint).setDampingOrthoLin(dampingOrthoLin);
+        ((SliderConstraint)constraint).setLowerAngLimit(lowerAngLimit);
+        ((SliderConstraint)constraint).setLowerLinLimit(lowerLinLimit);
+        ((SliderConstraint)constraint).setMaxAngMotorForce(maxAngMotorForce);
+        ((SliderConstraint)constraint).setMaxLinMotorForce(maxLinMotorForce);
+        ((SliderConstraint)constraint).setPoweredAngMotor(poweredAngMotor);
+        ((SliderConstraint)constraint).setPoweredLinMotor(poweredLinMotor);
+        ((SliderConstraint)constraint).setRestitutionDirAng(restitutionDirAng);
+        ((SliderConstraint)constraint).setRestitutionDirLin(restitutionDirLin);
+        ((SliderConstraint)constraint).setRestitutionLimAng(restitutionLimAng);
+        ((SliderConstraint)constraint).setRestitutionLimLin(restitutionLimLin);
+        ((SliderConstraint)constraint).setRestitutionOrthoAng(restitutionOrthoAng);
+        ((SliderConstraint)constraint).setRestitutionOrthoLin(restitutionOrthoLin);
+
+        ((SliderConstraint)constraint).setSoftnessDirAng(softnessDirAng);
+        ((SliderConstraint)constraint).setSoftnessDirLin(softnessDirLin);
+        ((SliderConstraint)constraint).setSoftnessLimAng(softnessLimAng);
+        ((SliderConstraint)constraint).setSoftnessLimLin(softnessLimLin);
+        ((SliderConstraint)constraint).setSoftnessOrthoAng(softnessOrthoAng);
+        ((SliderConstraint)constraint).setSoftnessOrthoLin(softnessOrthoLin);
+
+        ((SliderConstraint)constraint).setTargetAngMotorVelocity(targetAngMotorVelicoty);
+        ((SliderConstraint)constraint).setTargetLinMotorVelocity(targetLinMotorVelicoty);
+
+        ((SliderConstraint)constraint).setUpperAngLimit(upperAngLimit);
+        ((SliderConstraint)constraint).setUpperLinLimit(upperLinLimit);
+    }
+
+    protected void createJoint(){
+        Transform transA = new Transform(Converter.convert(rotA));
+        Converter.convert(pivotA, transA.origin);
+        Converter.convert(rotA, transA.basis);
+
+        Transform transB = new Transform(Converter.convert(rotB));
+        Converter.convert(pivotB, transB.origin);
+        Converter.convert(rotB, transB.basis);
+
+        constraint = new SliderConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java b/engine/src/jbullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java
new file mode 100644
index 0000000..b9df96d
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/motors/RotationalLimitMotor.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints.motors;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class RotationalLimitMotor {
+
+    private com.bulletphysics.dynamics.constraintsolver.RotationalLimitMotor motor;
+
+    public RotationalLimitMotor(com.bulletphysics.dynamics.constraintsolver.RotationalLimitMotor motor) {
+        this.motor = motor;
+    }
+
+    public com.bulletphysics.dynamics.constraintsolver.RotationalLimitMotor getMotor() {
+        return motor;
+    }
+
+    public float getLoLimit() {
+        return motor.loLimit;
+    }
+
+    public void setLoLimit(float loLimit) {
+        motor.loLimit = loLimit;
+    }
+
+    public float getHiLimit() {
+        return motor.hiLimit;
+    }
+
+    public void setHiLimit(float hiLimit) {
+        motor.hiLimit = hiLimit;
+    }
+
+    public float getTargetVelocity() {
+        return motor.targetVelocity;
+    }
+
+    public void setTargetVelocity(float targetVelocity) {
+        motor.targetVelocity = targetVelocity;
+    }
+
+    public float getMaxMotorForce() {
+        return motor.maxMotorForce;
+    }
+
+    public void setMaxMotorForce(float maxMotorForce) {
+        motor.maxMotorForce = maxMotorForce;
+    }
+
+    public float getMaxLimitForce() {
+        return motor.maxLimitForce;
+    }
+
+    public void setMaxLimitForce(float maxLimitForce) {
+        motor.maxLimitForce = maxLimitForce;
+    }
+
+    public float getDamping() {
+        return motor.damping;
+    }
+
+    public void setDamping(float damping) {
+        motor.damping = damping;
+    }
+
+    public float getLimitSoftness() {
+        return motor.limitSoftness;
+    }
+
+    public void setLimitSoftness(float limitSoftness) {
+        motor.limitSoftness = limitSoftness;
+    }
+
+    public float getERP() {
+        return motor.ERP;
+    }
+
+    public void setERP(float ERP) {
+        motor.ERP = ERP;
+    }
+
+    public float getBounce() {
+        return motor.bounce;
+    }
+
+    public void setBounce(float bounce) {
+        motor.bounce = bounce;
+    }
+
+    public boolean isEnableMotor() {
+        return motor.enableMotor;
+    }
+
+    public void setEnableMotor(boolean enableMotor) {
+        motor.enableMotor = enableMotor;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java b/engine/src/jbullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java
new file mode 100644
index 0000000..d5c23cb
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.joints.motors;
+
+import com.jme3.bullet.util.Converter;
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class TranslationalLimitMotor {
+
+    private com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor motor;
+
+    public TranslationalLimitMotor(com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor motor) {
+        this.motor = motor;
+    }
+
+    public com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor getMotor() {
+        return motor;
+    }
+
+    public Vector3f getLowerLimit() {
+        return Converter.convert(motor.lowerLimit);
+    }
+
+    public void setLowerLimit(Vector3f lowerLimit) {
+        Converter.convert(lowerLimit, motor.lowerLimit);
+    }
+
+    public Vector3f getUpperLimit() {
+        return Converter.convert(motor.upperLimit);
+    }
+
+    public void setUpperLimit(Vector3f upperLimit) {
+        Converter.convert(upperLimit, motor.upperLimit);
+    }
+
+    public Vector3f getAccumulatedImpulse() {
+        return Converter.convert(motor.accumulatedImpulse);
+    }
+
+    public void setAccumulatedImpulse(Vector3f accumulatedImpulse) {
+        Converter.convert(accumulatedImpulse, motor.accumulatedImpulse);
+    }
+
+    public float getLimitSoftness() {
+        return motor.limitSoftness;
+    }
+
+    public void setLimitSoftness(float limitSoftness) {
+        motor.limitSoftness = limitSoftness;
+    }
+
+    public float getDamping() {
+        return motor.damping;
+    }
+
+    public void setDamping(float damping) {
+        motor.damping = damping;
+    }
+
+    public float getRestitution() {
+        return motor.restitution;
+    }
+
+    public void setRestitution(float restitution) {
+        motor.restitution = restitution;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/PhysicsCharacter.java b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsCharacter.java
new file mode 100644
index 0000000..932c2e1
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsCharacter.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.bulletphysics.collision.dispatch.CollisionFlags;
+import com.bulletphysics.collision.dispatch.PairCachingGhostObject;
+import com.bulletphysics.collision.shapes.ConvexShape;
+import com.bulletphysics.dynamics.character.KinematicCharacterController;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * Basic Bullet Character
+ * @author normenhansen
+ */
+public class PhysicsCharacter extends PhysicsCollisionObject {
+
+    protected KinematicCharacterController character;
+    protected float stepHeight;
+    protected Vector3f walkDirection = new Vector3f();
+    protected float fallSpeed = 55.0f;
+    protected float jumpSpeed = 10.0f;
+    protected int upAxis = 1;
+    protected PairCachingGhostObject gObject;
+    protected boolean locationDirty = false;
+    //TEMP VARIABLES
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private Transform tempTrans = new Transform(Converter.convert(new Matrix3f()));
+    private com.jme3.math.Transform physicsLocation = new com.jme3.math.Transform();
+    private javax.vecmath.Vector3f tempVec = new javax.vecmath.Vector3f();
+
+    public PhysicsCharacter() {
+    }
+
+    /**
+     * @param shape The CollisionShape (no Mesh or CompoundCollisionShapes)
+     * @param stepHeight The quantization size for vertical movement
+     */
+    public PhysicsCharacter(CollisionShape shape, float stepHeight) {
+        this.collisionShape = shape;
+        if (!(shape.getCShape() instanceof ConvexShape)) {
+            throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes"));
+        }
+        this.stepHeight = stepHeight;
+        buildObject();
+    }
+
+    protected void buildObject() {
+        if (gObject == null) {
+            gObject = new PairCachingGhostObject();
+        }
+        gObject.setCollisionFlags(CollisionFlags.CHARACTER_OBJECT);
+        gObject.setCollisionFlags(gObject.getCollisionFlags() & ~CollisionFlags.NO_CONTACT_RESPONSE);
+        gObject.setCollisionShape(collisionShape.getCShape());
+        gObject.setUserPointer(this);
+        character = new KinematicCharacterController(gObject, (ConvexShape) collisionShape.getCShape(), stepHeight);
+    }
+
+    /**
+     * Sets the location of this physics character
+     * @param location
+     */
+    public void warp(Vector3f location) {
+        character.warp(Converter.convert(location, tempVec));
+    }
+
+    /**
+     * Set the walk direction, works continuously.
+     * This should probably be called setPositionIncrementPerSimulatorStep.
+     * This is neither a direction nor a velocity, but the amount to
+     * increment the position each physics tick. So vector length = accuracy*speed in m/s
+     * @param vec the walk direction to set
+     */
+    public void setWalkDirection(Vector3f vec) {
+        walkDirection.set(vec);
+        character.setWalkDirection(Converter.convert(walkDirection, tempVec));
+    }
+
+    /**
+     * @return the currently set walkDirection
+     */
+    public Vector3f getWalkDirection() {
+        return walkDirection;
+    }
+
+    public void setUpAxis(int axis) {
+        upAxis = axis;
+        character.setUpAxis(axis);
+    }
+
+    public int getUpAxis() {
+        return upAxis;
+    }
+
+    public void setFallSpeed(float fallSpeed) {
+        this.fallSpeed = fallSpeed;
+        character.setFallSpeed(fallSpeed);
+    }
+
+    public float getFallSpeed() {
+        return fallSpeed;
+    }
+
+    public void setJumpSpeed(float jumpSpeed) {
+        this.jumpSpeed = jumpSpeed;
+        character.setJumpSpeed(jumpSpeed);
+    }
+
+    public float getJumpSpeed() {
+        return jumpSpeed;
+    }
+
+    //does nothing..
+//    public void setMaxJumpHeight(float height) {
+//        character.setMaxJumpHeight(height);
+//    }
+    public void setGravity(float value) {
+        character.setGravity(value);
+    }
+
+    public float getGravity() {
+        return character.getGravity();
+    }
+
+    public void setMaxSlope(float slopeRadians) {
+        character.setMaxSlope(slopeRadians);
+    }
+
+    public float getMaxSlope() {
+        return character.getMaxSlope();
+    }
+
+    public boolean onGround() {
+        return character.onGround();
+    }
+
+    public void jump() {
+        character.jump();
+    }
+
+    @Override
+    public void setCollisionShape(CollisionShape collisionShape) {
+        if (!(collisionShape.getCShape() instanceof ConvexShape)) {
+            throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes"));
+        }
+        super.setCollisionShape(collisionShape);
+        if (gObject == null) {
+            buildObject();
+        }else{
+            gObject.setCollisionShape(collisionShape.getCShape());
+        }
+    }
+
+    /**
+     * Set the physics location (same as warp())
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        warp(location);
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation(Vector3f trans) {
+        if (trans == null) {
+            trans = new Vector3f();
+        }
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.origin, physicsLocation.getTranslation());
+        return trans.set(physicsLocation.getTranslation());
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation() {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.origin, physicsLocation.getTranslation());
+        return physicsLocation.getTranslation();
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        gObject.setCcdSweptSphereRadius(radius);
+    }
+
+    public void setCcdMotionThreshold(float threshold) {
+        gObject.setCcdMotionThreshold(threshold);
+    }
+
+    public float getCcdSweptSphereRadius() {
+        return gObject.getCcdSweptSphereRadius();
+    }
+
+    public float getCcdMotionThreshold() {
+        return gObject.getCcdMotionThreshold();
+    }
+
+    public float getCcdSquareMotionThreshold() {
+        return gObject.getCcdSquareMotionThreshold();
+    }
+
+    /**
+     * used internally
+     */
+    public KinematicCharacterController getControllerId() {
+        return character;
+    }
+
+    /**
+     * used internally
+     */
+    public PairCachingGhostObject getObjectId() {
+        return gObject;
+    }
+
+    public void destroy() {
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(stepHeight, "stepHeight", 1.0f);
+        capsule.write(getGravity(), "gravity", 9.8f * 3);
+        capsule.write(getMaxSlope(), "maxSlope", 1.0f);
+        capsule.write(fallSpeed, "fallSpeed", 55.0f);
+        capsule.write(jumpSpeed, "jumpSpeed", 10.0f);
+        capsule.write(upAxis, "upAxis", 1);
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        stepHeight = capsule.readFloat("stepHeight", 1.0f);
+        buildObject();
+        character = new KinematicCharacterController(gObject, (ConvexShape) collisionShape.getCShape(), stepHeight);
+        setGravity(capsule.readFloat("gravity", 9.8f * 3));
+        setMaxSlope(capsule.readFloat("maxSlope", 1.0f));
+        setFallSpeed(capsule.readFloat("fallSpeed", 55.0f));
+        setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f));
+        setUpAxis(capsule.readInt("upAxis", 1));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/PhysicsGhostObject.java b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsGhostObject.java
new file mode 100644
index 0000000..2f53a82
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsGhostObject.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.bulletphysics.collision.dispatch.CollisionFlags;
+import com.bulletphysics.collision.dispatch.PairCachingGhostObject;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * <i>From Bullet manual:</i><br>
+ * GhostObject can keep track of all objects that are overlapping.
+ * By default, this overlap is based on the AABB.
+ * This is useful for creating a character controller,
+ * collision sensors/triggers, explosions etc.<br>
+ * @author normenhansen
+ */
+public class PhysicsGhostObject extends PhysicsCollisionObject {
+
+    protected PairCachingGhostObject gObject;
+    protected boolean locationDirty = false;
+    //TEMP VARIABLES
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    protected Transform tempTrans = new Transform(Converter.convert(new Matrix3f()));
+    private com.jme3.math.Transform physicsLocation = new com.jme3.math.Transform();
+    protected javax.vecmath.Quat4f tempRot = new javax.vecmath.Quat4f();
+    private List<PhysicsCollisionObject> overlappingObjects = new LinkedList<PhysicsCollisionObject>();
+
+    public PhysicsGhostObject() {
+    }
+
+    public PhysicsGhostObject(CollisionShape shape) {
+        collisionShape = shape;
+        buildObject();
+    }
+
+    public PhysicsGhostObject(Spatial child, CollisionShape shape) {
+        collisionShape = shape;
+        buildObject();
+    }
+
+    protected void buildObject() {
+        if (gObject == null) {
+            gObject = new PairCachingGhostObject();
+            gObject.setCollisionFlags(gObject.getCollisionFlags() | CollisionFlags.NO_CONTACT_RESPONSE);
+        }
+        gObject.setCollisionShape(collisionShape.getCShape());
+        gObject.setUserPointer(this);
+    }
+
+    @Override
+    public void setCollisionShape(CollisionShape collisionShape) {
+        super.setCollisionShape(collisionShape);
+        if (gObject == null) {
+            buildObject();
+        }else{
+            gObject.setCollisionShape(collisionShape.getCShape());
+        }
+    }
+
+    /**
+     * Sets the physics object location
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(location, tempTrans.origin);
+        gObject.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Matrix3f rotation) {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(rotation, tempTrans.basis);
+        gObject.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Quaternion rotation) {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(rotation, tempTrans.basis);
+        gObject.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public com.jme3.math.Transform getPhysicsTransform() {
+        return physicsLocation;
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation(Vector3f trans) {
+        if (trans == null) {
+            trans = new Vector3f();
+        }
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.origin, physicsLocation.getTranslation());
+        return trans.set(physicsLocation.getTranslation());
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation(Quaternion rot) {
+        if (rot == null) {
+            rot = new Quaternion();
+        }
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.getRotation(tempRot), physicsLocation.getRotation());
+        return rot.set(physicsLocation.getRotation());
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) {
+        if (rot == null) {
+            rot = new Matrix3f();
+        }
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.getRotation(tempRot), physicsLocation.getRotation());
+        return rot.set(physicsLocation.getRotation());
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Vector3f getPhysicsLocation() {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.origin, physicsLocation.getTranslation());
+        return physicsLocation.getTranslation();
+    }
+
+    /**
+     * @return the physicsLocation
+     */
+    public Quaternion getPhysicsRotation() {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.getRotation(tempRot), physicsLocation.getRotation());
+        return physicsLocation.getRotation();
+    }
+
+    public Matrix3f getPhysicsRotationMatrix() {
+        gObject.getWorldTransform(tempTrans);
+        Converter.convert(tempTrans.getRotation(tempRot), physicsLocation.getRotation());
+        return physicsLocation.getRotation().toRotationMatrix();
+    }
+
+    /**
+     * used internally
+     */
+    public PairCachingGhostObject getObjectId() {
+        return gObject;
+    }
+
+    /**
+     * destroys this PhysicsGhostNode and removes it from memory
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Another Object is overlapping with this GhostNode,
+     * if and if only there CollisionShapes overlaps.
+     * They could be both regular PhysicsRigidBodys or PhysicsGhostObjects.
+     * @return All CollisionObjects overlapping with this GhostNode.
+     */
+    public List<PhysicsCollisionObject> getOverlappingObjects() {
+        overlappingObjects.clear();
+        for (com.bulletphysics.collision.dispatch.CollisionObject collObj : gObject.getOverlappingPairs()) {
+            overlappingObjects.add((PhysicsCollisionObject) collObj.getUserPointer());
+        }
+        return overlappingObjects;
+    }
+
+    /**
+     *
+     * @return With how many other CollisionObjects this GhostNode is currently overlapping.
+     */
+    public int getOverlappingCount() {
+        return gObject.getNumOverlappingObjects();
+    }
+
+    /**
+     *
+     * @param index The index of the overlapping Node to retrieve.
+     * @return The Overlapping CollisionObject at the given index.
+     */
+    public PhysicsCollisionObject getOverlapping(int index) {
+        return overlappingObjects.get(index);
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        gObject.setCcdSweptSphereRadius(radius);
+    }
+
+    public void setCcdMotionThreshold(float threshold) {
+        gObject.setCcdMotionThreshold(threshold);
+    }
+
+    public float getCcdSweptSphereRadius() {
+        return gObject.getCcdSweptSphereRadius();
+    }
+
+    public float getCcdMotionThreshold() {
+        return gObject.getCcdMotionThreshold();
+    }
+
+    public float getCcdSquareMotionThreshold() {
+        return gObject.getCcdSquareMotionThreshold();
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+        capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        buildObject();
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+        setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f())));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/PhysicsRigidBody.java b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsRigidBody.java
new file mode 100644
index 0000000..d0aea98
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsRigidBody.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bullet.objects;
+
+import com.bulletphysics.collision.dispatch.CollisionFlags;
+import com.bulletphysics.dynamics.RigidBody;
+import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.infos.RigidBodyMotionState;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+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.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <p>PhysicsRigidBody - Basic physics object</p>
+ * @author normenhansen
+ */
+public class PhysicsRigidBody extends PhysicsCollisionObject {
+
+    protected RigidBodyConstructionInfo constructionInfo;
+    protected RigidBody rBody;
+    protected RigidBodyMotionState motionState = new RigidBodyMotionState();
+    protected float mass = 1.0f;
+    protected boolean kinematic = false;
+    protected javax.vecmath.Vector3f tempVec = new javax.vecmath.Vector3f();
+    protected javax.vecmath.Vector3f tempVec2 = new javax.vecmath.Vector3f();
+    protected Transform tempTrans = new Transform(new javax.vecmath.Matrix3f());
+    protected javax.vecmath.Matrix3f tempMatrix = new javax.vecmath.Matrix3f();
+    //TEMP VARIABLES
+    protected javax.vecmath.Vector3f localInertia = new javax.vecmath.Vector3f();
+    protected ArrayList<PhysicsJoint> joints = new ArrayList<PhysicsJoint>();
+
+    public PhysicsRigidBody() {
+    }
+
+    /**
+     * Creates a new PhysicsRigidBody with the supplied collision shape
+     * @param shape
+     */
+    public PhysicsRigidBody(CollisionShape shape) {
+        collisionShape = shape;
+        rebuildRigidBody();
+    }
+
+    public PhysicsRigidBody(CollisionShape shape, float mass) {
+        collisionShape = shape;
+        this.mass = mass;
+        rebuildRigidBody();
+    }
+
+    /**
+     * Builds/rebuilds the phyiscs body when parameters have changed
+     */
+    protected void rebuildRigidBody() {
+        boolean removed = false;
+        if(collisionShape instanceof MeshCollisionShape && mass != 0){
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (rBody != null) {
+            if (rBody.isInWorld()) {
+                PhysicsSpace.getPhysicsSpace().remove(this);
+                removed = true;
+            }
+            rBody.destroy();
+        }
+        preRebuild();
+        rBody = new RigidBody(constructionInfo);
+        postRebuild();
+        if (removed) {
+            PhysicsSpace.getPhysicsSpace().add(this);
+        }
+    }
+
+    protected void preRebuild() {
+        collisionShape.calculateLocalInertia(mass, localInertia);
+        if (constructionInfo == null) {
+            constructionInfo = new RigidBodyConstructionInfo(mass, motionState, collisionShape.getCShape(), localInertia);
+        } else {
+            constructionInfo.mass = mass;
+            constructionInfo.collisionShape = collisionShape.getCShape();
+            constructionInfo.motionState = motionState;
+        }
+    }
+
+    protected void postRebuild() {
+        rBody.setUserPointer(this);
+        if (mass == 0.0f) {
+            rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.STATIC_OBJECT);
+        } else {
+            rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.STATIC_OBJECT);
+        }
+    }
+
+    /**
+     * @return the motionState
+     */
+    public RigidBodyMotionState getMotionState() {
+        return motionState;
+    }
+
+    /**
+     * Sets the physics object location
+     * @param location the location of the actual physics object
+     */
+    public void setPhysicsLocation(Vector3f location) {
+        rBody.getCenterOfMassTransform(tempTrans);
+        Converter.convert(location, tempTrans.origin);
+        rBody.setCenterOfMassTransform(tempTrans);
+        motionState.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Matrix3f rotation) {
+        rBody.getCenterOfMassTransform(tempTrans);
+        Converter.convert(rotation, tempTrans.basis);
+        rBody.setCenterOfMassTransform(tempTrans);
+        motionState.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * Sets the physics object rotation
+     * @param rotation the rotation of the actual physics object
+     */
+    public void setPhysicsRotation(Quaternion rotation) {
+        rBody.getCenterOfMassTransform(tempTrans);
+        Converter.convert(rotation, tempTrans.basis);
+        rBody.setCenterOfMassTransform(tempTrans);
+        motionState.setWorldTransform(tempTrans);
+    }
+
+    /**
+     * Gets the physics object location, instantiates a new Vector3f object
+     */
+    public Vector3f getPhysicsLocation() {
+        return getPhysicsLocation(null);
+    }
+
+    /**
+     * Gets the physics object rotation
+     */
+    public Matrix3f getPhysicsRotationMatrix() {
+        return getPhysicsRotationMatrix(null);
+    }
+
+    /**
+     * Gets the physics object location, no object instantiation
+     * @param location the location of the actual physics object is stored in this Vector3f
+     */
+    public Vector3f getPhysicsLocation(Vector3f location) {
+        if (location == null) {
+            location = new Vector3f();
+        }
+        rBody.getCenterOfMassTransform(tempTrans);
+        return Converter.convert(tempTrans.origin, location);
+    }
+
+    /**
+     * Gets the physics object rotation as a matrix, no conversions and no object instantiation
+     * @param rotation the rotation of the actual physics object is stored in this Matrix3f
+     */
+    public Matrix3f getPhysicsRotationMatrix(Matrix3f rotation) {
+        if (rotation == null) {
+            rotation = new Matrix3f();
+        }
+        rBody.getCenterOfMassTransform(tempTrans);
+        return Converter.convert(tempTrans.basis, rotation);
+    }
+
+    /**
+     * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value,
+     * instantiates new object
+     */
+    public Quaternion getPhysicsRotation(){
+        return getPhysicsRotation(null);
+    }
+
+    /**
+     * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value
+     * @param rotation the rotation of the actual physics object is stored in this Quaternion
+     */
+    public Quaternion getPhysicsRotation(Quaternion rotation){
+        if (rotation == null) {
+            rotation = new Quaternion();
+        }
+        rBody.getCenterOfMassTransform(tempTrans);
+        return Converter.convert(tempTrans.basis, rotation);
+    }
+
+    /**
+     * Gets the physics object location
+     * @param location the location of the actual physics object is stored in this Vector3f
+     */
+    public Vector3f getInterpolatedPhysicsLocation(Vector3f location) {
+        if (location == null) {
+            location = new Vector3f();
+        }
+        rBody.getInterpolationWorldTransform(tempTrans);
+        return Converter.convert(tempTrans.origin, location);
+    }
+
+    /**
+     * Gets the physics object rotation
+     * @param rotation the rotation of the actual physics object is stored in this Matrix3f
+     */
+    public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) {
+        if (rotation == null) {
+            rotation = new Matrix3f();
+        }
+        rBody.getInterpolationWorldTransform(tempTrans);
+        return Converter.convert(tempTrans.basis, rotation);
+    }
+
+    /**
+     * Sets the node to kinematic mode. in this mode the node is not affected by physics
+     * but affects other physics objects. Its kinetic force is calculated by the amount
+     * of movement it is exposed to and its weight.
+     * @param kinematic
+     */
+    public void setKinematic(boolean kinematic) {
+        this.kinematic = kinematic;
+        if (kinematic) {
+            rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.KINEMATIC_OBJECT);
+            rBody.setActivationState(com.bulletphysics.collision.dispatch.CollisionObject.DISABLE_DEACTIVATION);
+        } else {
+            rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.KINEMATIC_OBJECT);
+            rBody.setActivationState(com.bulletphysics.collision.dispatch.CollisionObject.ACTIVE_TAG);
+        }
+    }
+
+    public boolean isKinematic() {
+        return kinematic;
+    }
+
+    public void setCcdSweptSphereRadius(float radius) {
+        rBody.setCcdSweptSphereRadius(radius);
+    }
+
+    /**
+     * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection<br/>
+     * This avoids the problem of fast objects moving through other objects, set to zero to disable (default)
+     * @param threshold
+     */
+    public void setCcdMotionThreshold(float threshold) {
+        rBody.setCcdMotionThreshold(threshold);
+    }
+
+    public float getCcdSweptSphereRadius() {
+        return rBody.getCcdSweptSphereRadius();
+    }
+
+    public float getCcdMotionThreshold() {
+        return rBody.getCcdMotionThreshold();
+    }
+
+    public float getCcdSquareMotionThreshold() {
+        return rBody.getCcdSquareMotionThreshold();
+    }
+
+    public float getMass() {
+        return mass;
+    }
+
+    /**
+     * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static.
+     * @param mass
+     */
+    public void setMass(float mass) {
+        this.mass = mass;
+        if(collisionShape instanceof MeshCollisionShape && mass != 0){
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (collisionShape != null) {
+            collisionShape.calculateLocalInertia(mass, localInertia);
+        }
+        if (rBody != null) {
+            rBody.setMassProps(mass, localInertia);
+            if (mass == 0.0f) {
+                rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.STATIC_OBJECT);
+            } else {
+                rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.STATIC_OBJECT);
+            }
+        }
+    }
+
+    public Vector3f getGravity() {
+        return getGravity(null);
+    }
+
+    public Vector3f getGravity(Vector3f gravity) {
+        if (gravity == null) {
+            gravity = new Vector3f();
+        }
+        rBody.getGravity(tempVec);
+        return Converter.convert(tempVec, gravity);
+    }
+
+    /**
+     * Set the local gravity of this PhysicsRigidBody<br/>
+     * Set this after adding the node to the PhysicsSpace,
+     * the PhysicsSpace assigns its current gravity to the physics node when its added.
+     * @param gravity the gravity vector to set
+     */
+    public void setGravity(Vector3f gravity) {
+        rBody.setGravity(Converter.convert(gravity, tempVec));
+    }
+
+    public float getFriction() {
+        return rBody.getFriction();
+    }
+
+    /**
+     * Sets the friction of this physics object
+     * @param friction the friction of this physics object
+     */
+    public void setFriction(float friction) {
+        constructionInfo.friction = friction;
+        rBody.setFriction(friction);
+    }
+
+    public void setDamping(float linearDamping, float angularDamping) {
+        constructionInfo.linearDamping = linearDamping;
+        constructionInfo.angularDamping = angularDamping;
+        rBody.setDamping(linearDamping, angularDamping);
+    }
+
+    public void setLinearDamping(float linearDamping) {
+        constructionInfo.linearDamping = linearDamping;
+        rBody.setDamping(linearDamping, constructionInfo.angularDamping);
+    }
+
+    public void setAngularDamping(float angularDamping) {
+        constructionInfo.angularDamping = angularDamping;
+        rBody.setDamping(constructionInfo.linearDamping, angularDamping);
+    }
+
+    public float getLinearDamping() {
+        return constructionInfo.linearDamping;
+    }
+
+    public float getAngularDamping() {
+        return constructionInfo.angularDamping;
+    }
+
+    public float getRestitution() {
+        return rBody.getRestitution();
+    }
+
+    /**
+     * The "bouncyness" of the PhysicsRigidBody, best performance if restitution=0
+     * @param restitution
+     */
+    public void setRestitution(float restitution) {
+        constructionInfo.restitution = restitution;
+        rBody.setRestitution(restitution);
+    }
+
+    /**
+     * Get the current angular velocity of this PhysicsRigidBody
+     * @return the current linear velocity
+     */
+    public Vector3f getAngularVelocity() {
+        return Converter.convert(rBody.getAngularVelocity(tempVec));
+    }
+
+    /**
+     * Get the current angular velocity of this PhysicsRigidBody
+     * @param vec the vector to store the velocity in
+     */
+    public void getAngularVelocity(Vector3f vec) {
+        Converter.convert(rBody.getAngularVelocity(tempVec), vec);
+    }
+
+    /**
+     * Sets the angular velocity of this PhysicsRigidBody
+     * @param vec the angular velocity of this PhysicsRigidBody
+     */
+    public void setAngularVelocity(Vector3f vec) {
+        rBody.setAngularVelocity(Converter.convert(vec, tempVec));
+        rBody.activate();
+    }
+
+    /**
+     * Get the current linear velocity of this PhysicsRigidBody
+     * @return the current linear velocity
+     */
+    public Vector3f getLinearVelocity() {
+        return Converter.convert(rBody.getLinearVelocity(tempVec));
+    }
+
+    /**
+     * Get the current linear velocity of this PhysicsRigidBody
+     * @param vec the vector to store the velocity in
+     */
+    public void getLinearVelocity(Vector3f vec) {
+        Converter.convert(rBody.getLinearVelocity(tempVec), vec);
+    }
+
+    /**
+     * Sets the linear velocity of this PhysicsRigidBody
+     * @param vec the linear velocity of this PhysicsRigidBody
+     */
+    public void setLinearVelocity(Vector3f vec) {
+        rBody.setLinearVelocity(Converter.convert(vec, tempVec));
+        rBody.activate();
+    }
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force.
+     * @param force the force
+     * @param location the location of the force
+     */
+    public void applyForce(final Vector3f force, final Vector3f location) {
+        rBody.applyForce(Converter.convert(force, tempVec), Converter.convert(location, tempVec2));
+        rBody.activate();
+    }
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse.
+     * 
+     * @param force the force
+     */
+    public void applyCentralForce(final Vector3f force) {
+        rBody.applyCentralForce(Converter.convert(force, tempVec));
+        rBody.activate();
+    }
+
+    /**
+     * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+     * updates the physics space.<br>
+     * To apply an impulse, use applyImpulse.
+     * 
+     * @param torque the torque
+     */
+    public void applyTorque(final Vector3f torque) {
+        rBody.applyTorque(Converter.convert(torque, tempVec));
+        rBody.activate();
+    }
+
+    /**
+     * Apply an impulse to the PhysicsRigidBody in the next physics update.
+     * @param impulse applied impulse
+     * @param rel_pos location relative to object
+     */
+    public void applyImpulse(final Vector3f impulse, final Vector3f rel_pos) {
+        rBody.applyImpulse(Converter.convert(impulse, tempVec), Converter.convert(rel_pos, tempVec2));
+        rBody.activate();
+    }
+
+    /**
+     * Apply a torque impulse to the PhysicsRigidBody in the next physics update.
+     * @param vec
+     */
+    public void applyTorqueImpulse(final Vector3f vec) {
+        rBody.applyTorqueImpulse(Converter.convert(vec, tempVec));
+        rBody.activate();
+    }
+
+    /**
+     * Clear all forces from the PhysicsRigidBody
+     * 
+     */
+    public void clearForces() {
+        rBody.clearForces();
+    }
+
+    public void setCollisionShape(CollisionShape collisionShape) {
+        super.setCollisionShape(collisionShape);
+        if(collisionShape instanceof MeshCollisionShape && mass!=0){
+            throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+        }
+        if (rBody == null) {
+            rebuildRigidBody();
+        } else {
+            collisionShape.calculateLocalInertia(mass, localInertia);
+            constructionInfo.collisionShape = collisionShape.getCShape();
+            rBody.setCollisionShape(collisionShape.getCShape());
+        }
+    }
+
+    /**
+     * reactivates this PhysicsRigidBody when it has been deactivated because it was not moving
+     */
+    public void activate() {
+        rBody.activate();
+    }
+
+    public boolean isActive() {
+        return rBody.isActive();
+    }
+
+    /**
+     * sets the sleeping thresholds, these define when the object gets deactivated
+     * to save ressources. Low values keep the object active when it barely moves
+     * @param linear the linear sleeping threshold
+     * @param angular the angular sleeping threshold
+     */
+    public void setSleepingThresholds(float linear, float angular) {
+        constructionInfo.linearSleepingThreshold = linear;
+        constructionInfo.angularSleepingThreshold = angular;
+        rBody.setSleepingThresholds(linear, angular);
+    }
+
+    public void setLinearSleepingThreshold(float linearSleepingThreshold) {
+        constructionInfo.linearSleepingThreshold = linearSleepingThreshold;
+        rBody.setSleepingThresholds(linearSleepingThreshold, constructionInfo.angularSleepingThreshold);
+    }
+
+    public void setAngularSleepingThreshold(float angularSleepingThreshold) {
+        constructionInfo.angularSleepingThreshold = angularSleepingThreshold;
+        rBody.setSleepingThresholds(constructionInfo.linearSleepingThreshold, angularSleepingThreshold);
+    }
+
+    public float getLinearSleepingThreshold() {
+        return constructionInfo.linearSleepingThreshold;
+    }
+
+    public float getAngularSleepingThreshold() {
+        return constructionInfo.angularSleepingThreshold;
+    }
+
+    public float getAngularFactor() {
+        return rBody.getAngularFactor();
+    }
+
+    public void setAngularFactor(float factor) {
+        rBody.setAngularFactor(factor);
+    }
+
+    /**
+     * do not use manually, joints are added automatically
+     */
+    public void addJoint(PhysicsJoint joint) {
+        if (!joints.contains(joint)) {
+            joints.add(joint);
+        }
+        updateDebugShape();
+    }
+
+    /**
+     * 
+     */
+    public void removeJoint(PhysicsJoint joint) {
+        joints.remove(joint);
+    }
+
+    /**
+     * Returns a list of connected joints. This list is only filled when
+     * the PhysicsRigidBody is actually added to the physics space or loaded from disk.
+     * @return list of active joints connected to this PhysicsRigidBody
+     */
+    public List<PhysicsJoint> getJoints() {
+        return joints;
+    }
+
+    /**
+     * used internally
+     */
+    public RigidBody getObjectId() {
+        return rBody;
+    }
+
+    /**
+     * destroys this PhysicsRigidBody and removes it from memory
+     */
+    public void destroy() {
+        rBody.destroy();
+    }
+
+    @Override
+    protected Spatial getDebugShape() {
+        //add joints
+        Spatial shape = super.getDebugShape();
+        Node node = null;
+        if (shape instanceof Node) {
+            node = (Node) shape;
+        } else {
+            node = new Node("DebugShapeNode");
+            node.attachChild(shape);
+        }
+        int i = 0;
+        for (Iterator<PhysicsJoint> it = joints.iterator(); it.hasNext();) {
+            PhysicsJoint physicsJoint = it.next();
+            Vector3f pivot = null;
+            if (physicsJoint.getBodyA() == this) {
+                pivot = physicsJoint.getPivotA();
+            } else {
+                pivot = physicsJoint.getPivotB();
+            }
+            Arrow arrow = new Arrow(pivot);
+            Geometry geom = new Geometry("DebugBone" + i, arrow);
+            geom.setMaterial(debugMaterialGreen);
+            node.attachChild(geom);
+            i++;
+        }
+        return node;
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+
+        capsule.write(getMass(), "mass", 1.0f);
+
+        capsule.write(getGravity(), "gravity", Vector3f.ZERO);
+        capsule.write(getFriction(), "friction", 0.5f);
+        capsule.write(getRestitution(), "restitution", 0);
+        capsule.write(getAngularFactor(), "angularFactor", 1);
+        capsule.write(kinematic, "kinematic", false);
+
+        capsule.write(constructionInfo.linearDamping, "linearDamping", 0);
+        capsule.write(constructionInfo.angularDamping, "angularDamping", 0);
+        capsule.write(constructionInfo.linearSleepingThreshold, "linearSleepingThreshold", 0.8f);
+        capsule.write(constructionInfo.angularSleepingThreshold, "angularSleepingThreshold", 1.0f);
+
+        capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+        capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+
+        capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+        capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+
+        capsule.writeSavableArrayList(joints, "joints", null);
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+
+        InputCapsule capsule = e.getCapsule(this);
+        float mass = capsule.readFloat("mass", 1.0f);
+        this.mass = mass;
+        rebuildRigidBody();
+        setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone()));
+        setFriction(capsule.readFloat("friction", 0.5f));
+        setKinematic(capsule.readBoolean("kinematic", false));
+
+        setRestitution(capsule.readFloat("restitution", 0));
+        setAngularFactor(capsule.readFloat("angularFactor", 1));
+        setDamping(capsule.readFloat("linearDamping", 0), capsule.readFloat("angularDamping", 0));
+        setSleepingThresholds(capsule.readFloat("linearSleepingThreshold", 0.8f), capsule.readFloat("angularSleepingThreshold", 1.0f));
+        setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+        setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+
+        setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+        setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()));
+
+        joints = capsule.readSavableArrayList("joints", null);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/PhysicsVehicle.java b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsVehicle.java
new file mode 100644
index 0000000..eacf534
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/PhysicsVehicle.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects;
+
+import com.bulletphysics.collision.dispatch.CollisionObject;
+import com.bulletphysics.dynamics.vehicle.*;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * <p>PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions</p>
+ * <p>
+ * <i>From bullet manual:</i><br>
+ * For most vehicle simulations, it is recommended to use the simplified Bullet
+ * vehicle model as provided in btRaycastVehicle. Instead of simulation each wheel
+ * and chassis as separate rigid bodies, connected by constraints, it uses a simplified model.
+ * This simplified model has many benefits, and is widely used in commercial driving games.<br>
+ * The entire vehicle is represented as a single rigidbody, the chassis.
+ * The collision detection of the wheels is approximated by ray casts,
+ * and the tire friction is a basic anisotropic friction model.
+ * </p>
+ * @author normenhansen
+ */
+public class PhysicsVehicle extends PhysicsRigidBody {
+
+    protected RaycastVehicle vehicle;
+    protected VehicleTuning tuning;
+    protected VehicleRaycaster rayCaster;
+    protected ArrayList<VehicleWheel> wheels = new ArrayList<VehicleWheel>();
+    protected PhysicsSpace physicsSpace;
+
+    public PhysicsVehicle() {
+    }
+
+    public PhysicsVehicle(CollisionShape shape) {
+        super(shape);
+    }
+
+    public PhysicsVehicle(CollisionShape shape, float mass) {
+        super(shape, mass);
+    }
+
+    /**
+     * used internally
+     */
+    public void updateWheels() {
+        if (vehicle != null) {
+            for (int i = 0; i < wheels.size(); i++) {
+                vehicle.updateWheelTransform(i, true);
+                wheels.get(i).updatePhysicsState();
+            }
+        }
+    }
+
+    /**
+     * used internally
+     */
+    public void applyWheelTransforms() {
+        if (wheels != null) {
+            for (int i = 0; i < wheels.size(); i++) {
+                wheels.get(i).applyWheelTransform();
+            }
+        }
+    }
+
+    @Override
+    protected void postRebuild() {
+        super.postRebuild();
+        if (tuning == null) {
+            tuning = new VehicleTuning();
+        }
+        rBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
+        motionState.setVehicle(this);
+        if (physicsSpace != null) {
+            createVehicle(physicsSpace);
+        }
+    }
+
+    /**
+     * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace
+     */
+    public void createVehicle(PhysicsSpace space) {
+        physicsSpace = space;
+        if (space == null) {
+            return;
+        }
+        rayCaster = new DefaultVehicleRaycaster(space.getDynamicsWorld());
+        vehicle = new RaycastVehicle(tuning, rBody, rayCaster);
+        vehicle.setCoordinateSystem(0, 1, 2);
+        for (VehicleWheel wheel : wheels) {
+            wheel.setWheelInfo(vehicle.addWheel(Converter.convert(wheel.getLocation()), Converter.convert(wheel.getDirection()), Converter.convert(wheel.getAxle()),
+                    wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
+        }
+    }
+
+    /**
+     * Add a wheel to this vehicle
+     * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+     * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+     * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+     * @param suspensionRestLength The current length of the suspension (metres)
+     * @param wheelRadius the wheel radius
+     * @param isFrontWheel sets if this wheel is a front wheel (steering)
+     * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+     */
+    public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+        return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+    }
+
+    /**
+     * Add a wheel to this vehicle
+     * @param spat the wheel Geometry
+     * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+     * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+     * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+     * @param suspensionRestLength The current length of the suspension (metres)
+     * @param wheelRadius the wheel radius
+     * @param isFrontWheel sets if this wheel is a front wheel (steering)
+     * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+     */
+    public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+        VehicleWheel wheel = null;
+        if (spat == null) {
+            wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+        } else {
+            wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+        }
+        if (vehicle != null) {
+            WheelInfo info = vehicle.addWheel(Converter.convert(connectionPoint), Converter.convert(direction), Converter.convert(axle),
+                    suspensionRestLength, wheelRadius, tuning, isFrontWheel);
+            wheel.setWheelInfo(info);
+        }
+        wheel.setFrictionSlip(tuning.frictionSlip);
+        wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
+        wheel.setSuspensionStiffness(tuning.suspensionStiffness);
+        wheel.setWheelsDampingCompression(tuning.suspensionCompression);
+        wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
+        wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
+        wheels.add(wheel);
+        if (debugShape != null) {
+            detachDebugShape();
+        }
+//        updateDebugShape();
+        return wheel;
+    }
+
+    /**
+     * This rebuilds the vehicle as there is no way in bullet to remove a wheel.
+     * @param wheel
+     */
+    public void removeWheel(int wheel) {
+        wheels.remove(wheel);
+        rebuildRigidBody();
+//        updateDebugShape();
+    }
+
+    /**
+     * You can get access to the single wheels via this method.
+     * @param wheel the wheel index
+     * @return the WheelInfo of the selected wheel
+     */
+    public VehicleWheel getWheel(int wheel) {
+        return wheels.get(wheel);
+    }
+
+    public int getNumWheels() {
+        return wheels.size();
+    }
+
+    /**
+     * @return the frictionSlip
+     */
+    public float getFrictionSlip() {
+        return tuning.frictionSlip;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param frictionSlip the frictionSlip to set
+     */
+    public void setFrictionSlip(float frictionSlip) {
+        tuning.frictionSlip = frictionSlip;
+    }
+
+    /**
+     * The coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param wheel
+     * @param frictionSlip
+     */
+    public void setFrictionSlip(int wheel, float frictionSlip) {
+        wheels.get(wheel).setFrictionSlip(frictionSlip);
+    }
+
+    /**
+     * Reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+     * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+     * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+     * You should also try lowering the vehicle's centre of mass
+     */
+    public void setRollInfluence(int wheel, float rollInfluence) {
+        wheels.get(wheel).setRollInfluence(rollInfluence);
+    }
+
+    /**
+     * @return the maxSuspensionTravelCm
+     */
+    public float getMaxSuspensionTravelCm() {
+        return tuning.maxSuspensionTravelCm;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The maximum distance the suspension can be compressed (centimetres)
+     * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set
+     */
+    public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+        tuning.maxSuspensionTravelCm = maxSuspensionTravelCm;
+    }
+
+    /**
+     * The maximum distance the suspension can be compressed (centimetres)
+     * @param wheel
+     * @param maxSuspensionTravelCm
+     */
+    public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
+        wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
+    }
+
+    public float getMaxSuspensionForce() {
+        return tuning.maxSuspensionForce;
+    }
+
+    /**
+     * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param maxSuspensionForce
+     */
+    public void setMaxSuspensionForce(float maxSuspensionForce) {
+        tuning.maxSuspensionForce = maxSuspensionForce;
+    }
+
+    /**
+     * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param wheel
+     * @param maxSuspensionForce
+     */
+    public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
+        wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
+    }
+
+    /**
+     * @return the suspensionCompression
+     */
+    public float getSuspensionCompression() {
+        return tuning.suspensionCompression;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param suspensionCompression the suspensionCompression to set
+     */
+    public void setSuspensionCompression(float suspensionCompression) {
+        tuning.suspensionCompression = suspensionCompression;
+    }
+
+    /**
+     * The damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param wheel
+     * @param suspensionCompression
+     */
+    public void setSuspensionCompression(int wheel, float suspensionCompression) {
+        wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
+    }
+
+    /**
+     * @return the suspensionDamping
+     */
+    public float getSuspensionDamping() {
+        return tuning.suspensionDamping;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The damping coefficient for when the suspension is expanding.
+     * See the comments for setSuspensionCompression for how to set k.
+     * @param suspensionDamping the suspensionDamping to set
+     */
+    public void setSuspensionDamping(float suspensionDamping) {
+        tuning.suspensionDamping = suspensionDamping;
+    }
+
+    /**
+     * The damping coefficient for when the suspension is expanding.
+     * See the comments for setSuspensionCompression for how to set k.
+     * @param wheel
+     * @param suspensionDamping
+     */
+    public void setSuspensionDamping(int wheel, float suspensionDamping) {
+        wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
+    }
+
+    /**
+     * @return the suspensionStiffness
+     */
+    public float getSuspensionStiffness() {
+        return tuning.suspensionStiffness;
+    }
+
+    /**
+     * Use before adding wheels, this is the default used when adding wheels.
+     * After adding the wheel, use direct wheel access.<br>
+     * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param suspensionStiffness 
+     */
+    public void setSuspensionStiffness(float suspensionStiffness) {
+        tuning.suspensionStiffness = suspensionStiffness;
+    }
+
+    /**
+     * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param wheel
+     * @param suspensionStiffness
+     */
+    public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
+        wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
+    }
+
+    /**
+     * Reset the suspension
+     */
+    public void resetSuspension() {
+        vehicle.resetSuspension();
+    }
+
+    /**
+     * Apply the given engine force to all wheels, works continuously
+     * @param force the force
+     */
+    public void accelerate(float force) {
+        for (int i = 0; i < wheels.size(); i++) {
+            vehicle.applyEngineForce(force, i);
+        }
+    }
+
+    /**
+     * Apply the given engine force, works continuously
+     * @param wheel the wheel to apply the force on
+     * @param force the force
+     */
+    public void accelerate(int wheel, float force) {
+        vehicle.applyEngineForce(force, wheel);
+    }
+
+    /**
+     * Set the given steering value to all front wheels (0 = forward)
+     * @param value the steering angle of the front wheels (Pi = 360deg)
+     */
+    public void steer(float value) {
+        for (int i = 0; i < wheels.size(); i++) {
+            if (getWheel(i).isFrontWheel()) {
+                vehicle.setSteeringValue(value, i);
+            }
+        }
+    }
+
+    /**
+     * Set the given steering value to the given wheel (0 = forward)
+     * @param wheel the wheel to set the steering on
+     * @param value the steering angle of the front wheels (Pi = 360deg)
+     */
+    public void steer(int wheel, float value) {
+        vehicle.setSteeringValue(value, wheel);
+    }
+
+    /**
+     * Apply the given brake force to all wheels, works continuously
+     * @param force the force
+     */
+    public void brake(float force) {
+        for (int i = 0; i < wheels.size(); i++) {
+            vehicle.setBrake(force, i);
+        }
+    }
+
+    /**
+     * Apply the given brake force, works continuously
+     * @param wheel the wheel to apply the force on
+     * @param force the force
+     */
+    public void brake(int wheel, float force) {
+        vehicle.setBrake(force, wheel);
+    }
+
+    /**
+     * Get the current speed of the vehicle in km/h
+     * @return
+     */
+    public float getCurrentVehicleSpeedKmHour() {
+        return vehicle.getCurrentSpeedKmHour();
+    }
+
+    /**
+     * Get the current forward vector of the vehicle in world coordinates
+     * @param vector
+     * @return
+     */
+    public Vector3f getForwardVector(Vector3f vector) {
+        if (vector == null) {
+            vector = new Vector3f();
+        }
+        vehicle.getForwardVector(tempVec);
+        Converter.convert(tempVec, vector);
+        return vector;
+    }
+
+    /**
+     * used internally
+     */
+    public RaycastVehicle getVehicleId() {
+        return vehicle;
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+    }
+
+    @Override
+    protected Spatial getDebugShape() {
+        Spatial shape = super.getDebugShape();
+        Node node = null;
+        if (shape instanceof Node) {
+            node = (Node) shape;
+        } else {
+            node = new Node("DebugShapeNode");
+            node.attachChild(shape);
+        }
+        int i = 0;
+        for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+            VehicleWheel physicsVehicleWheel = it.next();
+            Vector3f location = physicsVehicleWheel.getLocation().clone();
+            Vector3f direction = physicsVehicleWheel.getDirection().clone();
+            Vector3f axle = physicsVehicleWheel.getAxle().clone();
+            float restLength = physicsVehicleWheel.getRestLength();
+            float radius = physicsVehicleWheel.getRadius();
+
+            Arrow locArrow = new Arrow(location);
+            Arrow axleArrow = new Arrow(axle.normalizeLocal().multLocal(0.3f));
+            Arrow wheelArrow = new Arrow(direction.normalizeLocal().multLocal(radius));
+            Arrow dirArrow = new Arrow(direction.normalizeLocal().multLocal(restLength));
+            Geometry locGeom = new Geometry("WheelLocationDebugShape" + i, locArrow);
+            Geometry dirGeom = new Geometry("WheelDirectionDebugShape" + i, dirArrow);
+            Geometry axleGeom = new Geometry("WheelAxleDebugShape" + i, axleArrow);
+            Geometry wheelGeom = new Geometry("WheelRadiusDebugShape" + i, wheelArrow);
+            dirGeom.setLocalTranslation(location);
+            axleGeom.setLocalTranslation(location.add(direction));
+            wheelGeom.setLocalTranslation(location.add(direction));
+            locGeom.setMaterial(debugMaterialGreen);
+            dirGeom.setMaterial(debugMaterialGreen);
+            axleGeom.setMaterial(debugMaterialGreen);
+            wheelGeom.setMaterial(debugMaterialGreen);
+            node.attachChild(locGeom);
+            node.attachChild(dirGeom);
+            node.attachChild(axleGeom);
+            node.attachChild(wheelGeom);
+            i++;
+        }
+        return node;
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        tuning = new VehicleTuning();
+        tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+        tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+        tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+        tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f);
+        tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f);
+        tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f);
+        wheels = capsule.readSavableArrayList("wheelsList", new ArrayList<VehicleWheel>());
+        motionState.setVehicle(this);
+        super.read(im);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f);
+        capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+        capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f);
+        capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f);
+        capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f);
+        capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f);
+        capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList<VehicleWheel>());
+        super.write(ex);
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/VehicleWheel.java b/engine/src/jbullet/com/jme3/bullet/objects/VehicleWheel.java
new file mode 100644
index 0000000..7ac6bb0
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/VehicleWheel.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bullet.objects;
+
+import com.bulletphysics.dynamics.RigidBody;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.util.Converter;
+import com.jme3.export.*;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Stores info about one wheel of a PhysicsVehicle
+ * @author normenhansen
+ */
+public class VehicleWheel implements Savable {
+
+    protected com.bulletphysics.dynamics.vehicle.WheelInfo wheelInfo;
+    protected boolean frontWheel;
+    protected Vector3f location = new Vector3f();
+    protected Vector3f direction = new Vector3f();
+    protected Vector3f axle = new Vector3f();
+    protected float suspensionStiffness = 20.0f;
+    protected float wheelsDampingRelaxation = 2.3f;
+    protected float wheelsDampingCompression = 4.4f;
+    protected float frictionSlip = 10.5f;
+    protected float rollInfluence = 1.0f;
+    protected float maxSuspensionTravelCm = 500f;
+    protected float maxSuspensionForce = 6000f;
+    protected float radius = 0.5f;
+    protected float restLength = 1f;
+    protected Vector3f wheelWorldLocation = new Vector3f();
+    protected Quaternion wheelWorldRotation = new Quaternion();
+    protected Spatial wheelSpatial;
+    protected com.jme3.math.Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f();
+    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private boolean applyLocal = false;
+
+    public VehicleWheel() {
+    }
+
+    public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle,
+            float restLength, float radius, boolean frontWheel) {
+        this(location, direction, axle, restLength, radius, frontWheel);
+        wheelSpatial = spat;
+    }
+
+    public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle,
+            float restLength, float radius, boolean frontWheel) {
+        this.location.set(location);
+        this.direction.set(direction);
+        this.axle.set(axle);
+        this.frontWheel = frontWheel;
+        this.restLength = restLength;
+        this.radius = radius;
+    }
+
+    public synchronized void updatePhysicsState() {
+        Converter.convert(wheelInfo.worldTransform.origin, wheelWorldLocation);
+        Converter.convert(wheelInfo.worldTransform.basis, tmp_Matrix);
+        wheelWorldRotation.fromRotationMatrix(tmp_Matrix);
+    }
+
+    public synchronized void applyWheelTransform() {
+        if (wheelSpatial == null) {
+            return;
+        }
+        Quaternion localRotationQuat = wheelSpatial.getLocalRotation();
+        Vector3f localLocation = wheelSpatial.getLocalTranslation();
+        if (!applyLocal && wheelSpatial.getParent() != null) {
+            localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation());
+            localLocation.divideLocal(wheelSpatial.getParent().getWorldScale());
+            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+            localRotationQuat.set(wheelWorldRotation);
+            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
+
+            wheelSpatial.setLocalTranslation(localLocation);
+            wheelSpatial.setLocalRotation(localRotationQuat);
+        } else {
+            wheelSpatial.setLocalTranslation(wheelWorldLocation);
+            wheelSpatial.setLocalRotation(wheelWorldRotation);
+        }
+    }
+
+    public com.bulletphysics.dynamics.vehicle.WheelInfo getWheelInfo() {
+        return wheelInfo;
+    }
+
+    public void setWheelInfo(com.bulletphysics.dynamics.vehicle.WheelInfo wheelInfo) {
+        this.wheelInfo = wheelInfo;
+        applyInfo();
+    }
+
+    public boolean isFrontWheel() {
+        return frontWheel;
+    }
+
+    public void setFrontWheel(boolean frontWheel) {
+        this.frontWheel = frontWheel;
+        applyInfo();
+    }
+
+    public Vector3f getLocation() {
+        return location;
+    }
+
+    public Vector3f getDirection() {
+        return direction;
+    }
+
+    public Vector3f getAxle() {
+        return axle;
+    }
+
+    public float getSuspensionStiffness() {
+        return suspensionStiffness;
+    }
+
+    /**
+     * the stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+     * @param suspensionStiffness
+     */
+    public void setSuspensionStiffness(float suspensionStiffness) {
+        this.suspensionStiffness = suspensionStiffness;
+        applyInfo();
+    }
+
+    public float getWheelsDampingRelaxation() {
+        return wheelsDampingRelaxation;
+    }
+
+    /**
+     * the damping coefficient for when the suspension is expanding.
+     * See the comments for setWheelsDampingCompression for how to set k.
+     * @param wheelsDampingRelaxation
+     */
+    public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) {
+        this.wheelsDampingRelaxation = wheelsDampingRelaxation;
+        applyInfo();
+    }
+
+    public float getWheelsDampingCompression() {
+        return wheelsDampingCompression;
+    }
+
+    /**
+     * the damping coefficient for when the suspension is compressed.
+     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+     * 0.1 to 0.3 are good values
+     * @param wheelsDampingCompression
+     */
+    public void setWheelsDampingCompression(float wheelsDampingCompression) {
+        this.wheelsDampingCompression = wheelsDampingCompression;
+        applyInfo();
+    }
+
+    public float getFrictionSlip() {
+        return frictionSlip;
+    }
+
+    /**
+     * the coefficient of friction between the tyre and the ground.
+     * Should be about 0.8 for realistic cars, but can increased for better handling.
+     * Set large (10000.0) for kart racers
+     * @param frictionSlip
+     */
+    public void setFrictionSlip(float frictionSlip) {
+        this.frictionSlip = frictionSlip;
+        applyInfo();
+    }
+
+    public float getRollInfluence() {
+        return rollInfluence;
+    }
+
+    /**
+     * reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+     * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+     * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+     * You should also try lowering the vehicle's centre of mass
+     * @param rollInfluence the rollInfluence to set
+     */
+    public void setRollInfluence(float rollInfluence) {
+        this.rollInfluence = rollInfluence;
+        applyInfo();
+    }
+
+    public float getMaxSuspensionTravelCm() {
+        return maxSuspensionTravelCm;
+    }
+
+    /**
+     * the maximum distance the suspension can be compressed (centimetres)
+     * @param maxSuspensionTravelCm
+     */
+    public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+        this.maxSuspensionTravelCm = maxSuspensionTravelCm;
+        applyInfo();
+    }
+
+    public float getMaxSuspensionForce() {
+        return maxSuspensionForce;
+    }
+
+    /**
+     * The maximum suspension force, raise this above the default 6000 if your suspension cannot
+     * handle the weight of your vehcile.
+     * @param maxSuspensionForce
+     */
+    public void setMaxSuspensionForce(float maxSuspensionForce) {
+        this.maxSuspensionForce = maxSuspensionForce;
+        applyInfo();
+    }
+
+    private void applyInfo() {
+        if (wheelInfo == null) {
+            return;
+        }
+        wheelInfo.suspensionStiffness = suspensionStiffness;
+        wheelInfo.wheelsDampingRelaxation = wheelsDampingRelaxation;
+        wheelInfo.wheelsDampingCompression = wheelsDampingCompression;
+        wheelInfo.frictionSlip = frictionSlip;
+        wheelInfo.rollInfluence = rollInfluence;
+        wheelInfo.maxSuspensionTravelCm = maxSuspensionTravelCm;
+        wheelInfo.maxSuspensionForce = maxSuspensionForce;
+        wheelInfo.wheelsRadius = radius;
+        wheelInfo.bIsFrontWheel = frontWheel;
+        wheelInfo.suspensionRestLength1 = restLength;
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void setRadius(float radius) {
+        this.radius = radius;
+        applyInfo();
+    }
+
+    public float getRestLength() {
+        return restLength;
+    }
+
+    public void setRestLength(float restLength) {
+        this.restLength = restLength;
+        applyInfo();
+    }
+
+    /**
+     * returns the object this wheel is in contact with or null if no contact
+     * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject)
+     */
+    public PhysicsCollisionObject getGroundObject() {
+        if (wheelInfo.raycastInfo.groundObject == null) {
+            return null;
+        } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) {
+            System.out.println("RigidBody");
+            return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * returns the location where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionLocation(Vector3f vec) {
+        Converter.convert(wheelInfo.raycastInfo.contactPointWS, vec);
+        return vec;
+    }
+
+    /**
+     * returns the location where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionLocation() {
+        return Converter.convert(wheelInfo.raycastInfo.contactPointWS);
+    }
+
+    /**
+     * returns the normal where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionNormal(Vector3f vec) {
+        Converter.convert(wheelInfo.raycastInfo.contactNormalWS, vec);
+        return vec;
+    }
+
+    /**
+     * returns the normal where the wheel collides with the ground (world space)
+     */
+    public Vector3f getCollisionNormal() {
+        return Converter.convert(wheelInfo.raycastInfo.contactNormalWS);
+    }
+
+    /**
+     * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)<br>
+     * 0.0 = wheels are sliding, 1.0 = wheels have traction.
+     */
+    public float getSkidInfo() {
+        return wheelInfo.skidInfo;
+    }
+    
+    /**
+     * returns how many degrees the wheel has turned since the last physics
+     * step.
+     */
+    public float getDeltaRotation() {
+        return wheelInfo.deltaRotation;
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule capsule = im.getCapsule(this);
+        wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null);
+        frontWheel = capsule.readBoolean("frontWheel", false);
+        location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f());
+        direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f());
+        axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f());
+        suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f);
+        wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f);
+        wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f);
+        frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+        rollInfluence = capsule.readFloat("rollInfluence", 1.0f);
+        maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+        maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+        radius = capsule.readFloat("wheelRadius", 0.5f);
+        restLength = capsule.readFloat("restLength", 1f);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule capsule = ex.getCapsule(this);
+        capsule.write(wheelSpatial, "wheelSpatial", null);
+        capsule.write(frontWheel, "frontWheel", false);
+        capsule.write(location, "wheelLocation", new Vector3f());
+        capsule.write(direction, "wheelDirection", new Vector3f());
+        capsule.write(axle, "wheelAxle", new Vector3f());
+        capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f);
+        capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f);
+        capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f);
+        capsule.write(frictionSlip, "frictionSlip", 10.5f);
+        capsule.write(rollInfluence, "rollInfluence", 1.0f);
+        capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+        capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f);
+        capsule.write(radius, "wheelRadius", 0.5f);
+        capsule.write(restLength, "restLength", 1f);
+    }
+
+    /**
+     * @return the wheelSpatial
+     */
+    public Spatial getWheelSpatial() {
+        return wheelSpatial;
+    }
+
+    /**
+     * @param wheelSpatial the wheelSpatial to set
+     */
+    public void setWheelSpatial(Spatial wheelSpatial) {
+        this.wheelSpatial = wheelSpatial;
+    }
+
+    public boolean isApplyLocal() {
+        return applyLocal;
+    }
+
+    public void setApplyLocal(boolean applyLocal) {
+        this.applyLocal = applyLocal;
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/engine/src/jbullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
new file mode 100644
index 0000000..8dd46bb
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.objects.infos;
+
+import com.bulletphysics.linearmath.MotionState;
+import com.bulletphysics.linearmath.Transform;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.bullet.util.Converter;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+
+/**
+ * stores transform info of a PhysicsNode in a threadsafe manner to
+ * allow multithreaded access from the jme scenegraph and the bullet physicsspace
+ * @author normenhansen
+ */
+public class RigidBodyMotionState extends MotionState {
+    //stores the bullet transform
+
+    private Transform motionStateTrans = new Transform(Converter.convert(new Matrix3f()));
+    private Vector3f worldLocation = new Vector3f();
+    private Matrix3f worldRotation = new Matrix3f();
+    private Quaternion worldRotationQuat = new Quaternion();
+    private Vector3f localLocation = new Vector3f();
+    private Quaternion localRotationQuat = new Quaternion();
+    //keep track of transform changes
+    private boolean physicsLocationDirty = false;
+    private boolean jmeLocationDirty = false;
+    //temp variable for conversion
+    private Quaternion tmp_inverseWorldRotation = new Quaternion();
+    private PhysicsVehicle vehicle;
+    private boolean applyPhysicsLocal = false;
+//    protected LinkedList<PhysicsMotionStateListener> listeners = new LinkedList<PhysicsMotionStateListener>();
+
+    public RigidBodyMotionState() {
+    }
+
+    /**
+     * called from bullet when creating the rigidbody
+     * @param t
+     * @return
+     */
+    public synchronized Transform getWorldTransform(Transform t) {
+        t.set(motionStateTrans);
+        return t;
+    }
+
+    /**
+     * called from bullet when the transform of the rigidbody changes
+     * @param worldTrans
+     */
+    public synchronized void setWorldTransform(Transform worldTrans) {
+        if (jmeLocationDirty) {
+            return;
+        }
+        motionStateTrans.set(worldTrans);
+        Converter.convert(worldTrans.origin, worldLocation);
+        Converter.convert(worldTrans.basis, worldRotation);
+        worldRotationQuat.fromRotationMatrix(worldRotation);
+//        for (Iterator<PhysicsMotionStateListener> it = listeners.iterator(); it.hasNext();) {
+//            PhysicsMotionStateListener physicsMotionStateListener = it.next();
+//            physicsMotionStateListener.stateChanged(worldLocation, worldRotation);
+//        }
+        physicsLocationDirty = true;
+        if (vehicle != null) {
+            vehicle.updateWheels();
+        }
+    }
+
+    /**
+     * applies the current transform to the given jme Node if the location has been updated on the physics side
+     * @param spatial
+     */
+    public synchronized boolean applyTransform(Spatial spatial) {
+        if (!physicsLocationDirty) {
+            return false;
+        }
+        if (!applyPhysicsLocal && spatial.getParent() != null) {
+            localLocation.set(worldLocation).subtractLocal(spatial.getParent().getWorldTranslation());
+            localLocation.divideLocal(spatial.getParent().getWorldScale());
+            tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+            localRotationQuat.set(worldRotationQuat);
+            tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
+
+            spatial.setLocalTranslation(localLocation);
+            spatial.setLocalRotation(localRotationQuat);
+        } else {
+            spatial.setLocalTranslation(worldLocation);
+            spatial.setLocalRotation(worldRotationQuat);
+        }
+        physicsLocationDirty = false;
+        return true;
+    }
+
+    /**
+     * @return the worldLocation
+     */
+    public Vector3f getWorldLocation() {
+        return worldLocation;
+    }
+
+    /**
+     * @return the worldRotation
+     */
+    public Matrix3f getWorldRotation() {
+        return worldRotation;
+    }
+
+    /**
+     * @return the worldRotationQuat
+     */
+    public Quaternion getWorldRotationQuat() {
+        return worldRotationQuat;
+    }
+
+    /**
+     * @param vehicle the vehicle to set
+     */
+    public void setVehicle(PhysicsVehicle vehicle) {
+        this.vehicle = vehicle;
+    }
+
+    public boolean isApplyPhysicsLocal() {
+        return applyPhysicsLocal;
+    }
+
+    public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+        this.applyPhysicsLocal = applyPhysicsLocal;
+    }
+//    public void addMotionStateListener(PhysicsMotionStateListener listener){
+//        listeners.add(listener);
+//    }
+//
+//    public void removeMotionStateListener(PhysicsMotionStateListener listener){
+//        listeners.remove(listener);
+//    }
+//    public synchronized boolean applyTransform(com.jme3.math.Transform trans) {
+//        if (!physicsLocationDirty) {
+//            return false;
+//        }
+//        trans.setTranslation(worldLocation);
+//        trans.setRotation(worldRotationQuat);
+//        physicsLocationDirty = false;
+//        return true;
+//    }
+//    
+//    /**
+//     * called from jme when the location of the jme Node changes
+//     * @param location
+//     * @param rotation
+//     */
+//    public synchronized void setWorldTransform(Vector3f location, Quaternion rotation) {
+//        worldLocation.set(location);
+//        worldRotationQuat.set(rotation);
+//        worldRotation.set(rotation.toRotationMatrix());
+//        Converter.convert(worldLocation, motionStateTrans.origin);
+//        Converter.convert(worldRotation, motionStateTrans.basis);
+//        jmeLocationDirty = true;
+//    }
+//
+//    /**
+//     * applies the current transform to the given RigidBody if the value has been changed on the jme side
+//     * @param rBody
+//     */
+//    public synchronized void applyTransform(RigidBody rBody) {
+//        if (!jmeLocationDirty) {
+//            return;
+//        }
+//        assert (rBody != null);
+//        rBody.setWorldTransform(motionStateTrans);
+//        rBody.activate();
+//        jmeLocationDirty = false;
+//    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/util/Converter.java b/engine/src/jbullet/com/jme3/bullet/util/Converter.java
new file mode 100644
index 0000000..15fb42c
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/util/Converter.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.util;
+
+import com.bulletphysics.collision.shapes.IndexedMesh;
+import com.bulletphysics.dom.HeightfieldTerrainShape;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+/**
+ * Nice convenience methods for conversion between javax.vecmath and com.jme3.math
+ * Objects, also some jme to jbullet mesh conversion.
+ * @author normenhansen
+ */
+public class Converter {
+
+    private Converter() {
+    }
+
+    public static com.jme3.math.Vector3f convert(javax.vecmath.Vector3f oldVec) {
+        com.jme3.math.Vector3f newVec = new com.jme3.math.Vector3f();
+        convert(oldVec, newVec);
+        return newVec;
+    }
+
+    public static com.jme3.math.Vector3f convert(javax.vecmath.Vector3f oldVec, com.jme3.math.Vector3f newVec) {
+        newVec.x = oldVec.x;
+        newVec.y = oldVec.y;
+        newVec.z = oldVec.z;
+        return newVec;
+    }
+
+    public static javax.vecmath.Vector3f convert(com.jme3.math.Vector3f oldVec) {
+        javax.vecmath.Vector3f newVec = new javax.vecmath.Vector3f();
+        convert(oldVec, newVec);
+        return newVec;
+    }
+
+    public static javax.vecmath.Vector3f convert(com.jme3.math.Vector3f oldVec, javax.vecmath.Vector3f newVec) {
+        newVec.x = oldVec.x;
+        newVec.y = oldVec.y;
+        newVec.z = oldVec.z;
+        return newVec;
+    }
+
+    public static javax.vecmath.Quat4f convert(com.jme3.math.Quaternion oldQuat, javax.vecmath.Quat4f newQuat) {
+        newQuat.w = oldQuat.getW();
+        newQuat.x = oldQuat.getX();
+        newQuat.y = oldQuat.getY();
+        newQuat.z = oldQuat.getZ();
+        return newQuat;
+    }
+
+    public static javax.vecmath.Quat4f convert(com.jme3.math.Quaternion oldQuat) {
+        javax.vecmath.Quat4f newQuat = new javax.vecmath.Quat4f();
+        convert(oldQuat, newQuat);
+        return newQuat;
+    }
+
+    public static com.jme3.math.Quaternion convert(javax.vecmath.Quat4f oldQuat, com.jme3.math.Quaternion newQuat) {
+        newQuat.set(oldQuat.x, oldQuat.y, oldQuat.z, oldQuat.w);
+        return newQuat;
+    }
+
+    public static com.jme3.math.Quaternion convert(javax.vecmath.Quat4f oldQuat) {
+        com.jme3.math.Quaternion newQuat = new com.jme3.math.Quaternion();
+        convert(oldQuat, newQuat);
+        return newQuat;
+    }
+
+    public static com.jme3.math.Quaternion convert(javax.vecmath.Matrix3f oldMatrix, com.jme3.math.Quaternion newQuaternion) {
+        // the trace is the sum of the diagonal elements; see
+        // http://mathworld.wolfram.com/MatrixTrace.html
+        float t = oldMatrix.m00 + oldMatrix.m11 + oldMatrix.m22;
+        float w, x, y, z;
+        // we protect the division by s by ensuring that s>=1
+        if (t >= 0) { // |w| >= .5
+            float s = FastMath.sqrt(t + 1); // |s|>=1 ...
+            w = 0.5f * s;
+            s = 0.5f / s;                 // so this division isn't bad
+            x = (oldMatrix.m21 - oldMatrix.m12) * s;
+            y = (oldMatrix.m02 - oldMatrix.m20) * s;
+            z = (oldMatrix.m10 - oldMatrix.m01) * s;
+        } else if ((oldMatrix.m00 > oldMatrix.m11) && (oldMatrix.m00 > oldMatrix.m22)) {
+            float s = FastMath.sqrt(1.0f + oldMatrix.m00 - oldMatrix.m11 - oldMatrix.m22); // |s|>=1
+            x = s * 0.5f; // |x| >= .5
+            s = 0.5f / s;
+            y = (oldMatrix.m10 + oldMatrix.m01) * s;
+            z = (oldMatrix.m02 + oldMatrix.m20) * s;
+            w = (oldMatrix.m21 - oldMatrix.m12) * s;
+        } else if (oldMatrix.m11 > oldMatrix.m22) {
+            float s = FastMath.sqrt(1.0f + oldMatrix.m11 - oldMatrix.m00 - oldMatrix.m22); // |s|>=1
+            y = s * 0.5f; // |y| >= .5
+            s = 0.5f / s;
+            x = (oldMatrix.m10 + oldMatrix.m01) * s;
+            z = (oldMatrix.m21 + oldMatrix.m12) * s;
+            w = (oldMatrix.m02 - oldMatrix.m20) * s;
+        } else {
+            float s = FastMath.sqrt(1.0f + oldMatrix.m22 - oldMatrix.m00 - oldMatrix.m11); // |s|>=1
+            z = s * 0.5f; // |z| >= .5
+            s = 0.5f / s;
+            x = (oldMatrix.m02 + oldMatrix.m20) * s;
+            y = (oldMatrix.m21 + oldMatrix.m12) * s;
+            w = (oldMatrix.m10 - oldMatrix.m01) * s;
+        }
+        return newQuaternion.set(x, y, z, w);
+    }
+
+    public static javax.vecmath.Matrix3f convert(com.jme3.math.Quaternion oldQuaternion, javax.vecmath.Matrix3f newMatrix) {
+        float norm = oldQuaternion.getW() * oldQuaternion.getW() + oldQuaternion.getX() * oldQuaternion.getX() + oldQuaternion.getY() * oldQuaternion.getY() + oldQuaternion.getZ() * oldQuaternion.getZ();
+        float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
+
+        // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+        // will be used 2-4 times each.
+        float xs = oldQuaternion.getX() * s;
+        float ys = oldQuaternion.getY() * s;
+        float zs = oldQuaternion.getZ() * s;
+        float xx = oldQuaternion.getX() * xs;
+        float xy = oldQuaternion.getX() * ys;
+        float xz = oldQuaternion.getX() * zs;
+        float xw = oldQuaternion.getW() * xs;
+        float yy = oldQuaternion.getY() * ys;
+        float yz = oldQuaternion.getY() * zs;
+        float yw = oldQuaternion.getW() * ys;
+        float zz = oldQuaternion.getZ() * zs;
+        float zw = oldQuaternion.getW() * zs;
+
+        // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+        newMatrix.m00 = 1 - (yy + zz);
+        newMatrix.m01 = (xy - zw);
+        newMatrix.m02 = (xz + yw);
+        newMatrix.m10 = (xy + zw);
+        newMatrix.m11 = 1 - (xx + zz);
+        newMatrix.m12 = (yz - xw);
+        newMatrix.m20 = (xz - yw);
+        newMatrix.m21 = (yz + xw);
+        newMatrix.m22 = 1 - (xx + yy);
+
+        return newMatrix;
+    }
+
+    public static com.jme3.math.Matrix3f convert(javax.vecmath.Matrix3f oldMatrix) {
+        com.jme3.math.Matrix3f newMatrix = new com.jme3.math.Matrix3f();
+        convert(oldMatrix, newMatrix);
+        return newMatrix;
+    }
+
+    public static com.jme3.math.Matrix3f convert(javax.vecmath.Matrix3f oldMatrix, com.jme3.math.Matrix3f newMatrix) {
+        newMatrix.set(0, 0, oldMatrix.m00);
+        newMatrix.set(0, 1, oldMatrix.m01);
+        newMatrix.set(0, 2, oldMatrix.m02);
+        newMatrix.set(1, 0, oldMatrix.m10);
+        newMatrix.set(1, 1, oldMatrix.m11);
+        newMatrix.set(1, 2, oldMatrix.m12);
+        newMatrix.set(2, 0, oldMatrix.m20);
+        newMatrix.set(2, 1, oldMatrix.m21);
+        newMatrix.set(2, 2, oldMatrix.m22);
+        return newMatrix;
+    }
+
+    public static javax.vecmath.Matrix3f convert(com.jme3.math.Matrix3f oldMatrix) {
+        javax.vecmath.Matrix3f newMatrix = new javax.vecmath.Matrix3f();
+        convert(oldMatrix, newMatrix);
+        return newMatrix;
+    }
+
+    public static javax.vecmath.Matrix3f convert(com.jme3.math.Matrix3f oldMatrix, javax.vecmath.Matrix3f newMatrix) {
+        newMatrix.m00 = oldMatrix.get(0, 0);
+        newMatrix.m01 = oldMatrix.get(0, 1);
+        newMatrix.m02 = oldMatrix.get(0, 2);
+        newMatrix.m10 = oldMatrix.get(1, 0);
+        newMatrix.m11 = oldMatrix.get(1, 1);
+        newMatrix.m12 = oldMatrix.get(1, 2);
+        newMatrix.m20 = oldMatrix.get(2, 0);
+        newMatrix.m21 = oldMatrix.get(2, 1);
+        newMatrix.m22 = oldMatrix.get(2, 2);
+        return newMatrix;
+    }
+
+    public static com.bulletphysics.linearmath.Transform convert(com.jme3.math.Transform in, com.bulletphysics.linearmath.Transform out) {
+        convert(in.getTranslation(), out.origin);
+        convert(in.getRotation(), out.basis);
+        return out;
+    }
+
+    public static com.jme3.math.Transform convert(com.bulletphysics.linearmath.Transform in,  com.jme3.math.Transform out) {
+        convert(in.origin, out.getTranslation());
+        convert(in.basis, out.getRotation());
+        return out;
+    }
+
+    public static IndexedMesh convert(Mesh mesh) {
+        IndexedMesh jBulletIndexedMesh = new IndexedMesh();
+        jBulletIndexedMesh.triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4);
+        jBulletIndexedMesh.vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4);
+
+        IndexBuffer indices = mesh.getIndicesAsList();
+        
+        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
+        vertices.rewind();
+
+        int verticesLength = mesh.getVertexCount() * 3;
+        jBulletIndexedMesh.numVertices = mesh.getVertexCount();
+        jBulletIndexedMesh.vertexStride = 12; //3 verts * 4 bytes per.
+        for (int i = 0; i < verticesLength; i++) {
+            float tempFloat = vertices.get();
+            jBulletIndexedMesh.vertexBase.putFloat(tempFloat);
+        }
+
+        int indicesLength = mesh.getTriangleCount() * 3;
+        jBulletIndexedMesh.numTriangles = mesh.getTriangleCount();
+        jBulletIndexedMesh.triangleIndexStride = 12; //3 index entries * 4 bytes each.
+        for (int i = 0; i < indicesLength; i++) {
+            jBulletIndexedMesh.triangleIndexBase.putInt(indices.get(i));
+        }
+        vertices.rewind();
+        vertices.clear();
+
+        return jBulletIndexedMesh;
+    }
+
+    public static Mesh convert(IndexedMesh mesh) {
+        Mesh jmeMesh = new Mesh();
+
+        jmeMesh.setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(mesh.numTriangles * 3));
+        jmeMesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(mesh.numVertices * 3));
+
+        IndexBuffer indicess = jmeMesh.getIndexBuffer();
+        FloatBuffer vertices = jmeMesh.getFloatBuffer(Type.Position);
+
+        for (int i = 0; i < mesh.numTriangles * 3; i++) {
+            indicess.put(i, mesh.triangleIndexBase.getInt(i * 4));
+        }
+
+        for (int i = 0; i < mesh.numVertices * 3; i++) {
+            vertices.put(i, mesh.vertexBase.getFloat(i * 4));
+        }
+        jmeMesh.updateCounts();
+        jmeMesh.updateBound();
+        jmeMesh.getFloatBuffer(Type.Position).clear();
+
+        return jmeMesh;
+    }
+
+    public static Mesh convert(HeightfieldTerrainShape heightfieldShape) {
+        return null; //TODO!!
+    }
+}
diff --git a/engine/src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java b/engine/src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java
new file mode 100644
index 0000000..ff0009c
--- /dev/null
+++ b/engine/src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet.util;
+
+import com.bulletphysics.collision.shapes.ConcaveShape;
+import com.bulletphysics.collision.shapes.ConvexShape;
+import com.bulletphysics.collision.shapes.ShapeHull;
+import com.bulletphysics.collision.shapes.TriangleCallback;
+import com.bulletphysics.util.IntArrayList;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
+import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
+import com.jme3.math.Matrix3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.vecmath.Vector3f;
+
+/**
+ *
+ * @author CJ Hare, normenhansen
+ */
+public class DebugShapeFactory {
+
+    /** The maximum corner for the aabb used for triangles to include in ConcaveShape processing.*/
+    private static final Vector3f aabbMax = new Vector3f(1e30f, 1e30f, 1e30f);
+    /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/
+    private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f);
+
+    /**
+     * Creates a debug shape from the given collision shape. This is mostly used internally.<br>
+     * To attach a debug shape to a physics object, call <code>attachDebugShape(AssetManager manager);</code> on it.
+     * @param collisionShape
+     * @return
+     */
+    public static Spatial getDebugShape(CollisionShape collisionShape) {
+        if (collisionShape == null) {
+            return null;
+        }
+        Spatial debugShape;
+        if (collisionShape instanceof CompoundCollisionShape) {
+            CompoundCollisionShape shape = (CompoundCollisionShape) collisionShape;
+            List<ChildCollisionShape> children = shape.getChildren();
+            Node node = new Node("DebugShapeNode");
+            for (Iterator<ChildCollisionShape> it = children.iterator(); it.hasNext();) {
+                ChildCollisionShape childCollisionShape = it.next();
+                CollisionShape ccollisionShape = childCollisionShape.shape;
+                Geometry geometry = createDebugShape(ccollisionShape);
+
+                // apply translation
+                geometry.setLocalTranslation(childCollisionShape.location);
+
+                // apply rotation
+                TempVars vars = TempVars.get();
+
+                Matrix3f tempRot = vars.tempMat3;
+
+                tempRot.set(geometry.getLocalRotation());
+                childCollisionShape.rotation.mult(tempRot, tempRot);
+                geometry.setLocalRotation(tempRot);
+
+                vars.release();
+
+                node.attachChild(geometry);
+            }
+            debugShape = node;
+        } else {
+            debugShape = createDebugShape(collisionShape);
+        }
+        if (debugShape == null) {
+            return null;
+        }
+        debugShape.updateGeometricState();
+        return debugShape;
+    }
+
+    private static Geometry createDebugShape(CollisionShape shape) {
+        Geometry geom = new Geometry();
+        geom.setMesh(DebugShapeFactory.getDebugMesh(shape));
+//        geom.setLocalScale(shape.getScale());
+        geom.updateModelBound();
+        return geom;
+    }
+
+    public static Mesh getDebugMesh(CollisionShape shape) {
+        Mesh mesh = null;
+        if (shape.getCShape() instanceof ConvexShape) {
+            mesh = new Mesh();
+            mesh.setBuffer(Type.Position, 3, getVertices((ConvexShape) shape.getCShape()));
+            mesh.getFloatBuffer(Type.Position).clear();
+        } else if (shape.getCShape() instanceof ConcaveShape) {
+            mesh = new Mesh();
+            mesh.setBuffer(Type.Position, 3, getVertices((ConcaveShape) shape.getCShape()));
+            mesh.getFloatBuffer(Type.Position).clear();
+        }
+        return mesh;
+    }
+
+    /**
+     *  Constructs the buffer for the vertices of the concave shape.
+     *
+     * @param concaveShape the shape to get the vertices for / from.
+     * @return the shape as stored by the given broadphase rigid body.
+     */
+    private static FloatBuffer getVertices(ConcaveShape concaveShape) {
+        // Create the call back that'll create the vertex buffer
+        BufferedTriangleCallback triangleProcessor = new BufferedTriangleCallback();
+        concaveShape.processAllTriangles(triangleProcessor, aabbMin, aabbMax);
+
+        // Retrieve the vextex and index buffers
+        return triangleProcessor.getVertices();
+    }
+
+    /**
+     *  Processes the given convex shape to retrieve a correctly ordered FloatBuffer to
+     *  construct the shape from with a TriMesh.
+     *
+     * @param convexShape the shape to retreieve the vertices from.
+     * @return the vertices as a FloatBuffer, ordered as Triangles.
+     */
+    private static FloatBuffer getVertices(ConvexShape convexShape) {
+        // Check there is a hull shape to render
+        if (convexShape.getUserPointer() == null) {
+            // create a hull approximation
+            ShapeHull hull = new ShapeHull(convexShape);
+            float margin = convexShape.getMargin();
+            hull.buildHull(margin);
+            convexShape.setUserPointer(hull);
+        }
+
+        // Assert state - should have a pointer to a hull (shape) that'll be drawn
+        assert convexShape.getUserPointer() != null : "Should have a shape for the userPointer, instead got null";
+        ShapeHull hull = (ShapeHull) convexShape.getUserPointer();
+
+        // Assert we actually have a shape to render
+        assert hull.numTriangles() > 0 : "Expecting the Hull shape to have triangles";
+        int numberOfTriangles = hull.numTriangles();
+
+        // The number of bytes needed is: (floats in a vertex) * (vertices in a triangle) * (# of triangles) * (size of float in bytes)
+        final int numberOfFloats = 3 * 3 * numberOfTriangles;
+        FloatBuffer vertices = BufferUtils.createFloatBuffer(numberOfFloats); 
+
+        // Force the limit, set the cap - most number of floats we will use the buffer for
+        vertices.limit(numberOfFloats);
+
+        // Loop variables
+        final IntArrayList hullIndicies = hull.getIndexPointer();
+        final List<Vector3f> hullVertices = hull.getVertexPointer();
+        Vector3f vertexA, vertexB, vertexC;
+        int index = 0;
+
+        for (int i = 0; i < numberOfTriangles; i++) {
+            // Grab the data for this triangle from the hull
+            vertexA = hullVertices.get(hullIndicies.get(index++));
+            vertexB = hullVertices.get(hullIndicies.get(index++));
+            vertexC = hullVertices.get(hullIndicies.get(index++));
+
+            // Put the verticies into the vertex buffer
+            vertices.put(vertexA.x).put(vertexA.y).put(vertexA.z);
+            vertices.put(vertexB.x).put(vertexB.y).put(vertexB.z);
+            vertices.put(vertexC.x).put(vertexC.y).put(vertexC.z);
+        }
+
+        vertices.clear();
+        return vertices;
+    }
+}
+
+/**
+ *  A callback is used to process the triangles of the shape as there is no direct access to a concave shapes, shape.
+ *  <p/>
+ *  The triangles are simply put into a list (which in extreme condition will cause memory problems) then put into a direct buffer.
+ *
+ * @author CJ Hare
+ */
+class BufferedTriangleCallback extends TriangleCallback {
+
+    private ArrayList<Vector3f> vertices;
+
+    public BufferedTriangleCallback() {
+        vertices = new ArrayList<Vector3f>();
+    }
+
+    @Override
+    public void processTriangle(Vector3f[] triangle, int partId, int triangleIndex) {
+        // Three sets of individual lines
+        // The new Vector is needed as the given triangle reference is from a pool
+        vertices.add(new Vector3f(triangle[0]));
+        vertices.add(new Vector3f(triangle[1]));
+        vertices.add(new Vector3f(triangle[2]));
+    }
+
+    /**
+     *  Retrieves the vertices from the Triangle buffer.
+     */
+    public FloatBuffer getVertices() {
+        // There are 3 floats needed for each vertex (x,y,z)
+        final int numberOfFloats = vertices.size() * 3;
+        FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(numberOfFloats); 
+
+        // Force the limit, set the cap - most number of floats we will use the buffer for
+        verticesBuffer.limit(numberOfFloats);
+
+        // Copy the values from the list to the direct float buffer
+        for (Vector3f v : vertices) {
+            verticesBuffer.put(v.x).put(v.y).put(v.z);
+        }
+
+        vertices.clear();
+        return verticesBuffer;
+    }
+}
diff --git a/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java b/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java
new file mode 100644
index 0000000..9b7c980
--- /dev/null
+++ b/engine/src/jogg/com/jme3/audio/plugins/CachedOggStream.java
@@ -0,0 +1,142 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.audio.plugins;

+

+import com.jme3.util.IntMap;

+import de.jarnbjo.ogg.LogicalOggStream;

+import de.jarnbjo.ogg.LogicalOggStreamImpl;

+import de.jarnbjo.ogg.OggPage;

+import de.jarnbjo.ogg.PhysicalOggStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ *  Implementation of the <code>PhysicalOggStream</code> interface for reading

+ *  and caching an Ogg stream from a URL. This class reads the data as fast as

+ *  possible from the URL, caches it locally either in memory or on disk, and

+ *  supports seeking within the available data.

+ */

+public class CachedOggStream implements PhysicalOggStream {

+

+    private boolean closed = false;

+    private boolean eos = false;

+    private boolean bos = false;

+    private InputStream sourceStream;

+    private HashMap<Integer, LogicalOggStream> logicalStreams 

+            = new HashMap<Integer, LogicalOggStream>();

+    

+    private IntMap<OggPage> oggPages = new IntMap<OggPage>();

+    private OggPage lastPage;

+   

+    private int pageNumber;

+    

+    public CachedOggStream(InputStream in) throws IOException {

+        sourceStream = in;

+

+        // Read all OGG pages in file

+        long time = System.nanoTime();

+        while (!eos){

+            readOggNextPage();

+        }

+        long dt = System.nanoTime() - time;

+        Logger.getLogger(CachedOggStream.class.getName()).log(Level.INFO, "Took {0} ms to load OGG", dt/1000000);

+    }

+

+    public OggPage getLastOggPage() {

+        return lastPage;

+    }

+    

+    private LogicalOggStream getLogicalStream(int serialNumber) {

+        return logicalStreams.get(Integer.valueOf(serialNumber));

+    }

+

+    public Collection<LogicalOggStream> getLogicalStreams() {

+        return logicalStreams.values();

+    }

+

+    public boolean isOpen() {

+        return !closed;

+    }

+

+    public void close() throws IOException {

+        closed = true;

+        sourceStream.close();

+    }

+

+    public OggPage getOggPage(int index) throws IOException {

+        return oggPages.get(index);

+    }

+

+   public void setTime(long granulePosition) throws IOException {

+       for (LogicalOggStream los : getLogicalStreams()){

+           los.setTime(granulePosition);

+       }

+   }

+

+   private int readOggNextPage() throws IOException {

+       if (eos)

+           return -1;

+

+       OggPage op = OggPage.create(sourceStream);

+       if (!op.isBos()){

+           bos = true;

+       }

+       if (op.isEos()){

+           eos = true;

+           lastPage = op;

+       }

+

+       LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber());

+       if(los == null) {

+          los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber());

+          logicalStreams.put(op.getStreamSerialNumber(), los);

+          los.checkFormat(op);

+       }

+

+       los.addPageNumberMapping(pageNumber);

+       los.addGranulePosition(op.getAbsoluteGranulePosition());

+

+       oggPages.put(pageNumber, op);

+       pageNumber++;

+

+       return pageNumber-1;

+   }

+

+   public boolean isSeekable() {

+      return true;

+   }

+}
\ No newline at end of file
diff --git a/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java b/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java
new file mode 100644
index 0000000..b342023
--- /dev/null
+++ b/engine/src/jogg/com/jme3/audio/plugins/OGGLoader.java
@@ -0,0 +1,310 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.audio.plugins;

+

+import com.jme3.asset.AssetInfo;

+import com.jme3.asset.AssetLoader;

+import com.jme3.audio.AudioBuffer;

+import com.jme3.audio.AudioData;

+import com.jme3.audio.AudioKey;

+import com.jme3.audio.AudioStream;

+import com.jme3.audio.SeekableStream;

+import com.jme3.util.BufferUtils;

+import de.jarnbjo.ogg.EndOfOggStreamException;

+import de.jarnbjo.ogg.LogicalOggStream;

+import de.jarnbjo.ogg.PhysicalOggStream;

+import de.jarnbjo.vorbis.IdentificationHeader;

+import de.jarnbjo.vorbis.VorbisStream;

+import java.io.ByteArrayOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.nio.ByteBuffer;

+import java.util.Collection;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class OGGLoader implements AssetLoader {

+

+//    private static int BLOCK_SIZE = 4096*64;

+

+    private PhysicalOggStream oggStream;

+    private LogicalOggStream loStream;

+    private VorbisStream vorbisStream;

+

+//    private CommentHeader commentHdr;

+    private IdentificationHeader streamHdr;

+  

+    private static class JOggInputStream extends InputStream {

+

+        private boolean endOfStream = false;

+        protected final VorbisStream vs;

+

+        public JOggInputStream(VorbisStream vs){           

+            this.vs = vs;       

+        }

+

+        @Override

+        public int read() throws IOException {

+            return 0;

+        }

+

+        @Override

+        public int read(byte[] buf) throws IOException{

+            return read(buf,0,buf.length);

+        }

+

+        @Override

+        public int read(byte[] buf, int offset, int length) throws IOException{

+            if (endOfStream)

+                return -1;

+

+            int bytesRead = 0, cnt = 0;

+            assert length % 2 == 0; // read buffer should be even

+            

+            while (bytesRead <length) {

+                if ((cnt = vs.readPcm(buf, offset + bytesRead,length - bytesRead)) <= 0) {

+                    System.out.println("Read "+cnt+" bytes");

+                    System.out.println("offset "+offset);

+                    System.out.println("bytesRead "+bytesRead);

+                    System.out.println("buf length "+length);

+                    for (int i = 0; i < bytesRead; i++) {

+                       System.out.print(buf[i]);

+                    }

+                    System.out.println("");

+                    

+                    

+                    System.out.println("EOS");

+                    endOfStream = true;                    

+                    break;

+                }               

+                bytesRead += cnt;               

+           }

+                         

+            swapBytes(buf, offset, bytesRead);

+            return bytesRead;

+

+        }

+

+        @Override

+        public void close() throws IOException{

+            vs.close();

+        }

+

+    }

+    

+    private static class SeekableJOggInputStream extends JOggInputStream implements SeekableStream {

+      

+        private LogicalOggStream los;

+        private float duration;

+        

+        public SeekableJOggInputStream(VorbisStream vs, LogicalOggStream los, float duration){           

+            super(vs);

+            this.los = los;

+            this.duration = duration;

+        }

+

+        public void setTime(float time) {

+            System.out.println("--setTime--)");

+            System.out.println("max granule : "+los.getMaximumGranulePosition());

+            System.out.println("current granule : "+los.getTime());

+            System.out.println("asked Time : "+time);

+            System.out.println("new granule : "+(time/duration*los.getMaximumGranulePosition()));

+            System.out.println("new granule2 : "+(time*vs.getIdentificationHeader().getSampleRate()));

+            

+             

+            

+            try {

+                los.setTime((long)(time*vs.getIdentificationHeader().getSampleRate()));                

+            } catch (IOException ex) {

+                Logger.getLogger(OGGLoader.class.getName()).log(Level.SEVERE, null, ex);

+            }

+        }

+        

+    }

+    

+    /**

+     * Returns the total of expected OGG bytes. 

+     * 

+     * @param dataBytesTotal The number of bytes in the input

+     * @return If the computed number of bytes is less than the number

+     * of bytes in the input, it is returned, otherwise the number 

+     * of bytes in the input is returned.

+     */

+    private int getOggTotalBytes(int dataBytesTotal){

+        // Vorbis stream could have more samples than than the duration of the sound

+        // Must truncate.

+        int numSamples;

+        if (oggStream instanceof CachedOggStream){

+            CachedOggStream cachedOggStream = (CachedOggStream) oggStream;

+            numSamples = (int) cachedOggStream.getLastOggPage().getAbsoluteGranulePosition();

+        }else{

+            UncachedOggStream uncachedOggStream = (UncachedOggStream) oggStream;

+            numSamples = (int) uncachedOggStream.getLastOggPage().getAbsoluteGranulePosition();

+        }

+

+        // Number of Samples * Number of Channels * Bytes Per Sample

+        int totalBytes = numSamples * streamHdr.getChannels() * 2;

+

+//        System.out.println("Sample Rate: " + streamHdr.getSampleRate());

+//        System.out.println("Channels: " + streamHdr.getChannels());

+//        System.out.println("Stream Length: " + numSamples);

+//        System.out.println("Bytes Calculated: " + totalBytes);

+//        System.out.println("Bytes Available:  " + dataBytes.length);

+

+        // Take the minimum of the number of bytes available

+        // and the expected duration of the audio.

+        return Math.min(totalBytes, dataBytesTotal);

+    }

+    

+    private float computeStreamDuration(){

+        // for uncached stream sources, the granule position is not known.

+        if (oggStream instanceof UncachedOggStream)

+            return -1;

+        

+        // 2 bytes(16bit) * channels * sampleRate

+        int bytesPerSec = 2 * streamHdr.getChannels() * streamHdr.getSampleRate();

+        

+        // Don't know how many bytes are in input, pass MAX_VALUE

+        int totalBytes = getOggTotalBytes(Integer.MAX_VALUE);

+        

+        return (float)totalBytes / bytesPerSec;

+    }

+

+    private ByteBuffer readToBuffer() throws IOException{

+        ByteArrayOutputStream baos = new ByteArrayOutputStream();

+

+        byte[] buf = new byte[512];

+        int read = 0;

+

+        try {

+            while ( (read = vorbisStream.readPcm(buf, 0, buf.length)) > 0){

+                baos.write(buf, 0, read);

+            }

+        } catch (EndOfOggStreamException ex){

+        }

+

+       

+        byte[] dataBytes = baos.toByteArray();

+        swapBytes(dataBytes, 0, dataBytes.length);

+        

+        int bytesToCopy = getOggTotalBytes( dataBytes.length );

+

+        ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy);

+        data.put(dataBytes, 0, bytesToCopy).flip();

+

+        vorbisStream.close();

+        loStream.close();

+        oggStream.close();

+

+        return data;

+    }

+

+    private static void swapBytes(byte[] b, int off, int len) {

+        byte tempByte;

+        for (int i = off; i < (off+len); i+=2) {

+            tempByte = b[i];

+            b[i] = b[i+1];

+            b[i+1] = tempByte;

+        }

+    }

+

+    private InputStream readToStream(boolean seekable,float streamDuration){

+        if(seekable){

+            return new SeekableJOggInputStream(vorbisStream,loStream,streamDuration);

+        }else{

+            return new JOggInputStream(vorbisStream);

+        }

+    }

+    

+    private AudioData load(InputStream in, boolean readStream, boolean streamCache) throws IOException{

+        if (readStream && streamCache){

+            oggStream = new CachedOggStream(in);

+        }else{

+            oggStream = new UncachedOggStream(in);

+        }

+

+        Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();

+        loStream = streams.iterator().next();

+

+//        if (loStream == null){

+//            throw new IOException("OGG File does not contain vorbis audio stream");

+//        }

+

+        vorbisStream = new VorbisStream(loStream);

+        streamHdr = vorbisStream.getIdentificationHeader();

+//        commentHdr = vorbisStream.getCommentHeader();

+    

+        if (!readStream){

+            AudioBuffer audioBuffer = new AudioBuffer();

+            audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());

+            audioBuffer.updateData(readToBuffer());

+            return audioBuffer;

+        }else{

+            AudioStream audioStream = new AudioStream();

+            audioStream.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());

+            

+            // might return -1 if unknown

+            float streamDuration = computeStreamDuration();

+            

+            audioStream.updateData(readToStream(oggStream.isSeekable(),streamDuration), streamDuration);

+            return audioStream;

+        }

+    }

+

+    public Object load(AssetInfo info) throws IOException {

+        if (!(info.getKey() instanceof AudioKey)){

+            throw new IllegalArgumentException("Audio assets must be loaded using an AudioKey");

+        }

+        

+        AudioKey key = (AudioKey) info.getKey();

+        boolean readStream = key.isStream();

+        boolean streamCache = key.useStreamCache();

+        

+        InputStream in = null;

+        try {

+            in = info.openStream();

+            AudioData data = load(in, readStream, streamCache);

+            if (data instanceof AudioStream){

+                // audio streams must remain open

+                in = null;

+            }

+            return data;

+        } finally {

+            if (in != null){

+                in.close();

+            }

+        }

+        

+    }

+

+}

diff --git a/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java b/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java
new file mode 100644
index 0000000..de81af9
--- /dev/null
+++ b/engine/src/jogg/com/jme3/audio/plugins/UncachedOggStream.java
@@ -0,0 +1,140 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.audio.plugins;

+

+import de.jarnbjo.ogg.*;

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.LinkedList;

+

+/**

+ * Single-threaded physical ogg stream. Decodes audio in the same thread

+ * that reads.

+ */

+public class UncachedOggStream implements PhysicalOggStream {

+

+    private boolean closed = false;

+    private boolean eos = false;

+    private boolean bos = false;

+    private InputStream sourceStream;

+    private LinkedList<OggPage> pageCache = new LinkedList<OggPage>();

+    private HashMap<Integer, LogicalOggStream> logicalStreams 

+            = new HashMap<Integer, LogicalOggStream>();

+    private OggPage lastPage = null;

+

+    public UncachedOggStream(InputStream in) throws OggFormatException, IOException {

+        this.sourceStream = in;

+

+        // read until beginning of stream

+        while (!bos){

+            readNextOggPage();

+        }

+

+        // now buffer up an addition 25 pages

+//        while (pageCache.size() < 25 && !eos){

+//            readNextOggPage();

+//        }

+    }

+

+    public OggPage getLastOggPage() {

+        return lastPage;

+    }

+

+    private void readNextOggPage() throws IOException {

+        OggPage op = OggPage.create(sourceStream);

+        if (!op.isBos()){

+            bos = true;

+        }

+        if (op.isEos()){

+            eos = true;

+            lastPage = op;

+        }

+

+        LogicalOggStreamImpl los = (LogicalOggStreamImpl) getLogicalStream(op.getStreamSerialNumber());

+        if (los == null){

+            los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber());

+            logicalStreams.put(op.getStreamSerialNumber(), los);

+            los.checkFormat(op);

+        }

+

+        pageCache.add(op);

+    }

+

+    public OggPage getOggPage(int index) throws IOException {

+        if (eos){

+            return null;

+        }

+

+//        if (!eos){

+//            int num = pageCache.size();

+//            long fiveMillis = 5000000;

+//            long timeStart  = System.nanoTime();

+//            do {

+//                readNextOggPage();

+//            } while ( !eos && (System.nanoTime() - timeStart) < fiveMillis );

+//            System.out.println( pageCache.size() - num );

+

+            if (pageCache.size() == 0 /*&& !eos*/){

+                readNextOggPage();

+            }

+//        }

+

+        return pageCache.removeFirst();

+    }

+

+    private LogicalOggStream getLogicalStream(int serialNumber) {

+        return logicalStreams.get(Integer.valueOf(serialNumber));

+    }

+

+    public Collection<LogicalOggStream> getLogicalStreams() {

+        return logicalStreams.values();

+    }

+

+    public void setTime(long granulePosition) throws IOException {

+    }

+

+    public boolean isSeekable() {

+        return false;

+    }

+

+    public boolean isOpen() {

+        return !closed;

+    }

+

+    public void close() throws IOException {

+        closed = true;

+        sourceStream.close();

+    }

+}

diff --git a/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java b/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java
new file mode 100644
index 0000000..2570b6a
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/audio/lwjgl/LwjglAudioRenderer.java
@@ -0,0 +1,1056 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.audio.lwjgl;

+

+import com.jme3.audio.AudioNode.Status;

+import com.jme3.audio.*;

+import com.jme3.math.Vector3f;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.NativeObjectManager;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+import java.util.ArrayList;

+import java.util.concurrent.atomic.AtomicBoolean;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.lwjgl.LWJGLException;

+import static org.lwjgl.openal.AL10.*;

+import org.lwjgl.openal.*;

+

+public class LwjglAudioRenderer implements AudioRenderer, Runnable {

+

+    private static final Logger logger = Logger.getLogger(LwjglAudioRenderer.class.getName());

+

+    private final NativeObjectManager objManager = new NativeObjectManager();

+    

+    // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2

+    // which is exactly 1 second of audio.

+    private static final int BUFFER_SIZE = 35280;

+    private static final int STREAMING_BUFFER_COUNT = 5;

+

+    private final static int MAX_NUM_CHANNELS = 64;

+    private IntBuffer ib = BufferUtils.createIntBuffer(1);

+    private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);

+    private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE);

+    private final byte[] arrayBuf = new byte[BUFFER_SIZE];

+

+    private int[] channels;

+    private AudioNode[] chanSrcs;

+    private int nextChan = 0;

+    private ArrayList<Integer> freeChans = new ArrayList<Integer>();

+

+    private Listener listener;

+    private boolean audioDisabled = false;

+

+    private boolean supportEfx = false;

+    private int auxSends = 0;

+    private int reverbFx = -1;

+    private int reverbFxSlot = -1;

+

+    // Update audio 20 times per second

+    private static final float UPDATE_RATE = 0.05f;

+

+    private final Thread audioThread = new Thread(this, "jME3 Audio Thread");

+    private final AtomicBoolean threadLock = new AtomicBoolean(false);

+

+    public LwjglAudioRenderer(){

+    }

+

+    public void initialize(){

+        if (!audioThread.isAlive()){

+            audioThread.setDaemon(true);

+            audioThread.setPriority(Thread.NORM_PRIORITY+1);

+            audioThread.start();

+        }else{

+            throw new IllegalStateException("Initialize already called");

+        }

+    }

+

+    private void checkDead(){

+        if (audioThread.getState() == Thread.State.TERMINATED)

+            throw new IllegalStateException("Audio thread is terminated");

+    }

+

+    public void run(){

+        initInThread();

+        synchronized (threadLock){

+            threadLock.set(true);

+            threadLock.notifyAll();

+        }

+        

+        long updateRateNanos = (long) (UPDATE_RATE * 1000000000);

+        mainloop: while (true){

+            long startTime = System.nanoTime();

+

+            if (Thread.interrupted())

+                break;

+

+            synchronized (threadLock){

+                updateInThread(UPDATE_RATE);

+            }

+

+            long endTime = System.nanoTime();

+            long diffTime = endTime - startTime;

+

+            if (diffTime < updateRateNanos){

+                long desiredEndTime = startTime + updateRateNanos;

+                while (System.nanoTime() < desiredEndTime){

+                    try{

+                        Thread.sleep(1);

+                    }catch (InterruptedException ex){

+                        break mainloop;

+                    }

+                }

+            }

+        }

+

+        synchronized (threadLock){

+            cleanupInThread();

+        }

+    }

+

+    public void initInThread(){

+        try{

+            if (!AL.isCreated()){

+                AL.create();

+            }

+        }catch (OpenALException ex){

+            logger.log(Level.SEVERE, "Failed to load audio library", ex);

+            audioDisabled = true;

+            return;

+        }catch (LWJGLException ex){

+            logger.log(Level.SEVERE, "Failed to load audio library", ex);

+            audioDisabled = true;

+            return;

+        } catch (UnsatisfiedLinkError ex){

+            logger.log(Level.SEVERE, "Failed to load audio library", ex);

+            audioDisabled = true;

+            return;

+        }

+

+        ALCdevice device = AL.getDevice();

+        String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER);

+

+        logger.log(Level.FINER, "Audio Device: {0}", deviceName);

+        logger.log(Level.FINER, "Audio Vendor: {0}", alGetString(AL_VENDOR));

+        logger.log(Level.FINER, "Audio Renderer: {0}", alGetString(AL_RENDERER));

+        logger.log(Level.FINER, "Audio Version: {0}", alGetString(AL_VERSION));

+

+        // Find maximum # of sources supported by this implementation

+        ArrayList<Integer> channelList = new ArrayList<Integer>();

+        for (int i = 0; i < MAX_NUM_CHANNELS; i++){

+            int chan = alGenSources();

+            if (alGetError() != 0){

+                break;

+            }else{

+                channelList.add(chan);

+            }

+        }

+

+        channels = new int[channelList.size()];

+        for (int i = 0; i < channels.length; i++){

+            channels[i] = channelList.get(i);

+        }

+

+        ib = BufferUtils.createIntBuffer(channels.length);

+        chanSrcs = new AudioNode[channels.length];

+

+        logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length);

+

+        supportEfx = ALC10.alcIsExtensionPresent(device, "ALC_EXT_EFX");

+        if (supportEfx){

+            ib.position(0).limit(1);

+            ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib);

+            int major = ib.get(0);

+            ib.position(0).limit(1);

+            ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib);

+            int minor = ib.get(0);

+            logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor});

+

+            ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, ib);

+            auxSends = ib.get(0);

+            logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends);

+

+            // create slot

+            ib.position(0).limit(1);

+            EFX10.alGenAuxiliaryEffectSlots(ib);

+            reverbFxSlot = ib.get(0);

+

+            // create effect

+            ib.position(0).limit(1);

+            EFX10.alGenEffects(ib);

+            reverbFx = ib.get(0);

+            EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB);

+

+            // attach reverb effect to effect slot

+            EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx);

+        }else{

+            logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work.");

+        }

+    }

+

+    public void cleanupInThread(){

+        if (audioDisabled){

+            AL.destroy();

+            return;

+        }

+

+        // stop any playing channels

+        for (int i = 0; i < chanSrcs.length; i++){

+            if (chanSrcs[i] != null){

+                clearChannel(i);

+            }

+        }

+        

+        // delete channel-based sources

+        ib.clear();

+        ib.put(channels);

+        ib.flip();

+        alDeleteSources(ib);

+        

+        // delete audio buffers and filters

+        objManager.deleteAllObjects(this);

+

+        if (supportEfx){

+            ib.position(0).limit(1);

+            ib.put(0, reverbFx);

+            EFX10.alDeleteEffects(ib);

+

+            // If this is not allocated, why is it deleted?

+            // Commented out to fix native crash in OpenAL.

+            ib.position(0).limit(1);

+            ib.put(0, reverbFxSlot);

+            EFX10.alDeleteAuxiliaryEffectSlots(ib);

+        }

+

+        AL.destroy();

+    }

+

+    public void cleanup(){

+        // kill audio thread

+        if (audioThread.isAlive()){

+            audioThread.interrupt();

+        }

+    }

+

+    private void updateFilter(Filter f){

+        int id = f.getId();

+        if (id == -1){

+            ib.position(0).limit(1);

+            EFX10.alGenFilters(ib);

+            id = ib.get(0);

+            f.setId(id);

+            

+            objManager.registerForCleanup(f);

+        }

+

+        if (f instanceof LowPassFilter){

+            LowPassFilter lpf = (LowPassFilter) f;

+            EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE,    EFX10.AL_FILTER_LOWPASS);

+            EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN,   lpf.getVolume());

+            EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume());

+        }else{

+            throw new UnsupportedOperationException("Filter type unsupported: "+

+                                                    f.getClass().getName());

+        }

+

+        f.clearUpdateNeeded();

+    }

+    

+    public void updateSourceParam(AudioNode src, AudioParam param){

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+ 

+            // There is a race condition in AudioNode that can

+            // cause this to be called for a node that has been

+            // detached from its channel.  For example, setVolume()

+            // called from the render thread may see that that AudioNode

+            // still has a channel value but the audio thread may

+            // clear that channel before setVolume() gets to call

+            // updateSourceParam() (because the audio stopped playing

+            // on its own right as the volume was set).  In this case, 

+            // it should be safe to just ignore the update

+            if (src.getChannel() < 0) 

+                return;

+           

+            assert src.getChannel() >= 0;

+

+            int id = channels[src.getChannel()];

+            switch (param){

+                case Position:

+                    if (!src.isPositional())

+                        return;

+

+                    Vector3f pos = src.getWorldTranslation();

+                    alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z);

+                    break;

+                case Velocity:

+                    if (!src.isPositional())

+                        return;

+

+                    Vector3f vel = src.getVelocity();

+                    alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z);

+                    break;

+                case MaxDistance:

+                    if (!src.isPositional())

+                        return;

+

+                    alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance());

+                    break;

+                case RefDistance:

+                    if (!src.isPositional())

+                        return;

+

+                    alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance());

+                    break;

+                case ReverbFilter:

+                    if (!supportEfx || !src.isPositional() || !src.isReverbEnabled())

+                        return;

+

+                    int filter = EFX10.AL_FILTER_NULL;

+                    if (src.getReverbFilter() != null){

+                        Filter f = src.getReverbFilter();

+                        if (f.isUpdateNeeded()){

+                            updateFilter(f);

+                        }

+                        filter = f.getId();

+                    }

+                    AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter);

+                    break;

+                case ReverbEnabled:

+                    if (!supportEfx || !src.isPositional())

+                        return;

+

+                    if (src.isReverbEnabled()){

+                        updateSourceParam(src, AudioParam.ReverbFilter);

+                    }else{

+                        AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL);

+                    }

+                    break;

+                case IsPositional:

+                    if (!src.isPositional()){

+                        // play in headspace

+                        alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE);

+                        alSource3f(id, AL_POSITION, 0,0,0);

+                        alSource3f(id, AL_VELOCITY, 0,0,0);

+                    }else{

+                        alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE);

+                        updateSourceParam(src, AudioParam.Position);

+                        updateSourceParam(src, AudioParam.Velocity);

+                        updateSourceParam(src, AudioParam.MaxDistance);

+                        updateSourceParam(src, AudioParam.RefDistance);

+                        updateSourceParam(src, AudioParam.ReverbEnabled);

+                    }

+                    break;

+                case Direction:

+                    if (!src.isDirectional())

+                        return;

+

+                    Vector3f dir = src.getDirection();

+                    alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z);

+                    break;

+                case InnerAngle:

+                    if (!src.isDirectional())

+                        return;

+

+                    alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle());

+                    break;

+                case OuterAngle:

+                    if (!src.isDirectional())

+                        return;

+

+                    alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle());

+                    break;

+                case IsDirectional:

+                    if (src.isDirectional()){

+                        updateSourceParam(src, AudioParam.Direction);

+                        updateSourceParam(src, AudioParam.InnerAngle);

+                        updateSourceParam(src, AudioParam.OuterAngle);

+                        alSourcef(id, AL_CONE_OUTER_GAIN, 0);

+                    }else{

+                        alSourcef(id, AL_CONE_INNER_ANGLE, 360);

+                        alSourcef(id, AL_CONE_OUTER_ANGLE, 360);

+                        alSourcef(id, AL_CONE_OUTER_GAIN, 1f);

+                    }

+                    break;

+                case DryFilter:

+                    if (!supportEfx)

+                        return;

+                    

+                    if (src.getDryFilter() != null){

+                        Filter f = src.getDryFilter();

+                        if (f.isUpdateNeeded()){

+                            updateFilter(f);

+

+                            // NOTE: must re-attach filter for changes to apply.

+                            alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId());

+                        }

+                    }else{

+                        alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL);

+                    }

+                    break;

+                case Looping:

+                    if (src.isLooping()){

+                        if (!(src.getAudioData() instanceof AudioStream)){

+                            alSourcei(id, AL_LOOPING, AL_TRUE);

+                        }

+                    }else{

+                        alSourcei(id, AL_LOOPING, AL_FALSE);

+                    }

+                    break;

+                case Volume:

+                    alSourcef(id, AL_GAIN, src.getVolume());

+                    break;

+                case Pitch:

+                    alSourcef(id, AL_PITCH, src.getPitch());

+                    break;

+            }

+        }

+    }

+

+    private void setSourceParams(int id, AudioNode src, boolean forceNonLoop){

+        if (src.isPositional()){

+            Vector3f pos = src.getWorldTranslation();

+            Vector3f vel = src.getVelocity();

+            alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z);

+            alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z);

+            alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance());

+            alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance());

+            alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE);

+

+            if (src.isReverbEnabled() && supportEfx){

+                int filter = EFX10.AL_FILTER_NULL;

+                if (src.getReverbFilter() != null){

+                    Filter f = src.getReverbFilter();

+                    if (f.isUpdateNeeded()){

+                        updateFilter(f);

+                    }

+                    filter = f.getId();

+                }

+                AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter);

+            }

+        }else{

+            // play in headspace

+            alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE);

+            alSource3f(id, AL_POSITION, 0,0,0);

+            alSource3f(id, AL_VELOCITY, 0,0,0);

+        }

+

+        if (src.getDryFilter() != null && supportEfx){

+            Filter f = src.getDryFilter();

+            if (f.isUpdateNeeded()){

+                updateFilter(f);

+                

+                // NOTE: must re-attach filter for changes to apply.

+                alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId());

+            }

+        }

+

+        if (forceNonLoop){

+            alSourcei(id,  AL_LOOPING, AL_FALSE);

+        }else{

+            alSourcei(id,  AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE);

+        }

+        alSourcef(id,  AL_GAIN, src.getVolume());

+        alSourcef(id,  AL_PITCH, src.getPitch());

+        alSourcef(id,  AL11.AL_SEC_OFFSET, src.getTimeOffset());

+

+        if (src.isDirectional()){

+            Vector3f dir = src.getDirection();

+            alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z);

+            alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle());

+            alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle());

+            alSourcef(id, AL_CONE_OUTER_GAIN,  0);

+        }else{

+            alSourcef(id, AL_CONE_INNER_ANGLE, 360);

+            alSourcef(id, AL_CONE_OUTER_ANGLE, 360);

+            alSourcef(id, AL_CONE_OUTER_GAIN, 1f);

+        }

+    }

+

+    public void updateListenerParam(Listener listener, ListenerParam param){

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+            

+            switch (param){

+                case Position:

+                    Vector3f pos = listener.getLocation();

+                    alListener3f(AL_POSITION, pos.x, pos.y, pos.z);

+                    break;

+                case Rotation:

+                    Vector3f dir = listener.getDirection();

+                    Vector3f up  = listener.getUp();

+                    fb.rewind();

+                    fb.put(dir.x).put(dir.y).put(dir.z);

+                    fb.put(up.x).put(up.y).put(up.z);

+                    fb.flip();

+                    alListener(AL_ORIENTATION, fb);

+                    break;

+                case Velocity:

+                    Vector3f vel = listener.getVelocity();

+                    alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z);

+                    break;

+                case Volume:

+                    alListenerf(AL_GAIN, listener.getVolume());

+                    break;

+            }

+        }

+    }

+

+    private void setListenerParams(Listener listener){

+        Vector3f pos = listener.getLocation();

+        Vector3f vel = listener.getVelocity();

+        Vector3f dir = listener.getDirection();

+        Vector3f up  = listener.getUp();

+

+        alListener3f(AL_POSITION, pos.x, pos.y, pos.z);

+        alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z);

+        fb.rewind();

+        fb.put(dir.x).put(dir.y).put(dir.z);

+        fb.put(up.x).put(up.y).put(up.z);

+        fb.flip();

+        alListener(AL_ORIENTATION, fb);

+        alListenerf(AL_GAIN, listener.getVolume());

+    }

+

+    private int newChannel(){

+        if (freeChans.size() > 0)

+            return freeChans.remove(0);

+        else if (nextChan < channels.length){

+            return nextChan++;

+        }else{

+            return -1;

+        }

+    }

+

+    private void freeChannel(int index){

+        if (index == nextChan-1){

+            nextChan--;

+        } else{

+            freeChans.add(index);

+        }

+    }

+

+    public void setEnvironment(Environment env){

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled || !supportEfx)

+                return;

+            

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY,             env.getDensity());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION,           env.getDiffusion());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN,                env.getGain());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF,              env.getGainHf());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME,          env.getDecayTime());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO,       env.getDecayHFRatio());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN,    env.getReflectGain());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY,   env.getReflectDelay());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN,    env.getLateReverbGain());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY,   env.getLateReverbDelay());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf());

+            EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor());

+

+            // attach effect to slot

+            EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx);

+        }

+    }

+

+    private boolean fillBuffer(AudioStream stream, int id){

+        int size = 0;

+        int result;

+

+        while (size < arrayBuf.length){

+            result = stream.readSamples(arrayBuf, size, arrayBuf.length - size);

+

+            if(result > 0){

+                size += result;

+            }else{

+                break;

+            }

+        }

+

+        if(size == 0)

+            return false;

+

+        nativeBuf.clear();

+        nativeBuf.put(arrayBuf, 0, size);

+        nativeBuf.flip();

+

+        alBufferData(id, convertFormat(stream), nativeBuf, stream.getSampleRate());

+

+        return true;

+    }

+

+    private boolean fillStreamingSource(int sourceId, AudioStream stream){

+        if (!stream.isOpen())

+            return false;

+

+        boolean active = true;

+        int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED);

+

+//        while((processed--) != 0){

+        if (processed > 0){

+            int buffer;

+

+            ib.position(0).limit(1);

+            alSourceUnqueueBuffers(sourceId, ib);

+            buffer = ib.get(0);

+

+            active = fillBuffer(stream, buffer);

+

+            ib.position(0).limit(1);

+            ib.put(0, buffer);

+            alSourceQueueBuffers(sourceId, ib);

+        }

+

+        if (!active && stream.isOpen())

+            stream.close();

+        

+        return active;

+    }

+

+    private boolean attachStreamToSource(int sourceId, AudioStream stream){

+        boolean active = true;

+        for (int id : stream.getIds()){

+            active = fillBuffer(stream, id);

+            ib.position(0).limit(1);

+            ib.put(id).flip();

+            alSourceQueueBuffers(sourceId, ib);

+        }

+        return active;

+    }

+

+    private boolean attachBufferToSource(int sourceId, AudioBuffer buffer){

+        alSourcei(sourceId, AL_BUFFER, buffer.getId());

+        return true;

+    }

+

+    private boolean attachAudioToSource(int sourceId, AudioData data){

+        if (data instanceof AudioBuffer){

+            return attachBufferToSource(sourceId, (AudioBuffer) data);

+        }else if (data instanceof AudioStream){

+            return attachStreamToSource(sourceId, (AudioStream) data);

+        }

+        throw new UnsupportedOperationException();

+    }

+

+    private void clearChannel(int index){

+        // make room at this channel

+        if (chanSrcs[index] != null){

+            AudioNode src = chanSrcs[index];

+

+            int sourceId = channels[index];

+            alSourceStop(sourceId);

+

+            if (src.getAudioData() instanceof AudioStream){

+                AudioStream str = (AudioStream) src.getAudioData();

+                ib.position(0).limit(STREAMING_BUFFER_COUNT);

+                ib.put(str.getIds()).flip();

+                alSourceUnqueueBuffers(sourceId, ib);

+            }else if (src.getAudioData() instanceof AudioBuffer){

+                alSourcei(sourceId, AL_BUFFER, 0);

+            }

+

+            if (src.getDryFilter() != null && supportEfx){

+                // detach filter

+                alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL);

+            }

+            if (src.isPositional()){

+                AudioNode pas = (AudioNode) src;

+                if (pas.isReverbEnabled() && supportEfx) {

+                    AL11.alSource3i(sourceId, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL);

+                }

+            }

+

+            chanSrcs[index] = null;

+        }

+    }

+

+    public void update(float tpf){

+        // does nothing

+    }

+

+    public void updateInThread(float tpf){

+        if (audioDisabled)

+            return;

+

+        for (int i = 0; i < channels.length; i++){

+            AudioNode src = chanSrcs[i];

+            if (src == null)

+                continue;

+

+            int sourceId = channels[i];

+

+            // is the source bound to this channel

+            // if false, it's an instanced playback

+            boolean boundSource = i == src.getChannel();

+

+            // source's data is streaming

+            boolean streaming = src.getAudioData() instanceof AudioStream;

+

+            // only buffered sources can be bound

+            assert (boundSource && streaming) || (!streaming);

+

+            int state = alGetSourcei(sourceId, AL_SOURCE_STATE);

+            boolean wantPlaying = src.getStatus() == Status.Playing;

+            boolean stopped = state == AL_STOPPED;

+

+            if (streaming && wantPlaying){

+                AudioStream stream = (AudioStream) src.getAudioData();

+                if (stream.isOpen()){

+                    fillStreamingSource(sourceId, stream);

+                    if (stopped)

+                        alSourcePlay(sourceId);

+                }else{

+                    if (stopped){

+                        // became inactive

+                        src.setStatus(Status.Stopped);

+                        src.setChannel(-1);

+                        clearChannel(i);

+                        freeChannel(i);

+ 

+                        // And free the audio since it cannot be

+                        // played again anyway.

+                        deleteAudioData(stream);

+                    }

+                }

+            }else if (!streaming){

+                boolean paused = state == AL_PAUSED;

+

+                // make sure OAL pause state & source state coincide

+                assert (src.getStatus() == Status.Paused && paused) || (!paused);

+

+                if (stopped){

+                    if (boundSource){

+                        src.setStatus(Status.Stopped);

+                        src.setChannel(-1);

+                    }

+                    clearChannel(i);

+                    freeChannel(i);  

+                }

+            }

+        }

+        

+        // Delete any unused objects.

+        objManager.deleteUnused(this);

+    }

+

+    public void setListener(Listener listener) {

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+

+            if (this.listener != null){

+                // previous listener no longer associated with current

+                // renderer

+                this.listener.setRenderer(null);

+            }

+            

+            this.listener = listener;

+            this.listener.setRenderer(this);

+            setListenerParams(listener);

+        }

+    }

+

+    public void playSourceInstance(AudioNode src){

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+            

+            if (src.getAudioData() instanceof AudioStream)

+                throw new UnsupportedOperationException(

+                        "Cannot play instances " +

+                        "of audio streams. Use playSource() instead.");

+

+            if (src.getAudioData().isUpdateNeeded()){

+                updateAudioData(src.getAudioData());

+            }

+

+            // create a new index for an audio-channel

+            int index = newChannel();

+            if (index == -1)

+                return;

+

+            int sourceId = channels[index];

+

+            clearChannel(index);

+

+            // set parameters, like position and max distance

+            setSourceParams(sourceId, src, true);

+            attachAudioToSource(sourceId, src.getAudioData());

+            chanSrcs[index] = src;

+

+            // play the channel

+            alSourcePlay(sourceId);

+        }

+    }

+

+    

+    public void playSource(AudioNode src) {

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+

+            //assert src.getStatus() == Status.Stopped || src.getChannel() == -1;

+

+            if (src.getStatus() == Status.Playing){

+                return;

+            }else if (src.getStatus() == Status.Stopped){

+

+                // allocate channel to this source

+                int index = newChannel();

+                if (index == -1) {

+                    logger.log(Level.WARNING, "No channel available to play {0}", src);

+                    return;

+                }

+                clearChannel(index);

+                src.setChannel(index);

+

+                AudioData data = src.getAudioData();

+                if (data.isUpdateNeeded())

+                    updateAudioData(data);

+

+                chanSrcs[index] = src;

+                setSourceParams(channels[index], src, false);

+                attachAudioToSource(channels[index], data);

+            }

+

+            alSourcePlay(channels[src.getChannel()]);

+            src.setStatus(Status.Playing);

+        }

+    }

+

+    

+    public void pauseSource(AudioNode src) {

+        checkDead();

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+            

+            if (src.getStatus() == Status.Playing){

+                assert src.getChannel() != -1;

+

+                alSourcePause(channels[src.getChannel()]);

+                src.setStatus(Status.Paused);

+            }

+        }

+    }

+

+    

+    public void stopSource(AudioNode src) {

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+            

+            if (src.getStatus() != Status.Stopped){

+                int chan = src.getChannel();

+                assert chan != -1; // if it's not stopped, must have id

+

+                src.setStatus(Status.Stopped);

+                src.setChannel(-1);

+                clearChannel(chan);

+                freeChannel(chan);

+                

+                if (src.getAudioData() instanceof AudioStream) {

+                    AudioStream stream = (AudioStream)src.getAudioData();

+                    if (stream.isOpen()) {

+                        stream.close();

+                    }

+                

+                    // And free the audio since it cannot be

+                    // played again anyway.

+                    deleteAudioData(src.getAudioData());

+                }                    

+            }

+        }

+    }

+

+    private int convertFormat(AudioData ad){

+        switch (ad.getBitsPerSample()){

+            case 8:

+                if (ad.getChannels() == 1)

+                    return AL_FORMAT_MONO8;

+                else if (ad.getChannels() == 2)

+                    return AL_FORMAT_STEREO8;

+

+                break;

+            case 16:

+                if (ad.getChannels() == 1)

+                    return AL_FORMAT_MONO16;

+                else

+                    return AL_FORMAT_STEREO16;

+        }

+        throw new UnsupportedOperationException("Unsupported channels/bits combination: "+

+                                                "bits="+ad.getBitsPerSample()+", channels="+ad.getChannels());

+    }

+

+    private void updateAudioBuffer(AudioBuffer ab){

+        int id = ab.getId();

+        if (ab.getId() == -1){

+            ib.position(0).limit(1);

+            alGenBuffers(ib);

+            id = ib.get(0);

+            ab.setId(id);

+            

+            objManager.registerForCleanup(ab);

+        }

+

+        ab.getData().clear();

+        alBufferData(id, convertFormat(ab), ab.getData(), ab.getSampleRate());

+        ab.clearUpdateNeeded();

+    }

+

+    private void updateAudioStream(AudioStream as){

+        if (as.getIds() != null){

+            deleteAudioData(as);

+        }

+

+        int[] ids = new int[STREAMING_BUFFER_COUNT];

+        ib.position(0).limit(STREAMING_BUFFER_COUNT);

+        alGenBuffers(ib);

+        ib.position(0).limit(STREAMING_BUFFER_COUNT);        

+        ib.get(ids);

+        

+        // Not registered with object manager.

+        // AudioStreams can be handled without object manager

+        // since their lifecycle is known to the audio renderer.

+        

+        as.setIds(ids);

+        as.clearUpdateNeeded();

+    }

+

+    private void updateAudioData(AudioData ad){

+        if (ad instanceof AudioBuffer){

+            updateAudioBuffer((AudioBuffer) ad);

+        }else if (ad instanceof AudioStream){

+            updateAudioStream((AudioStream) ad);

+        }

+    }

+    

+    public void deleteFilter(Filter filter) {

+        int id = filter.getId();

+        if (id != -1){

+            EFX10.alDeleteFilters(id);

+        }

+    }

+

+    public void deleteAudioData(AudioData ad){

+        synchronized (threadLock){

+            while (!threadLock.get()){

+                try {

+                    threadLock.wait();

+                } catch (InterruptedException ex) {

+                }

+            }

+            if (audioDisabled)

+                return;

+        

+            if (ad instanceof AudioBuffer){

+                AudioBuffer ab = (AudioBuffer) ad;

+                int id = ab.getId();

+                if (id != -1){

+                    ib.put(0,id);

+                    ib.position(0).limit(1);

+                    alDeleteBuffers(ib);

+                    ab.resetObject();

+                }

+            }else if (ad instanceof AudioStream){

+                AudioStream as = (AudioStream) ad;

+                int[] ids = as.getIds();

+                if (ids != null){

+                    ib.clear();

+                    ib.put(ids).flip();

+                    alDeleteBuffers(ib);

+                    as.resetObject();

+                }

+            }

+        }            

+    }

+

+}

diff --git a/engine/src/lwjgl/com/jme3/input/lwjgl/JInputJoyInput.java b/engine/src/lwjgl/com/jme3/input/lwjgl/JInputJoyInput.java
new file mode 100644
index 0000000..28e35f0
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/input/lwjgl/JInputJoyInput.java
@@ -0,0 +1,194 @@
+package com.jme3.input.lwjgl;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.JoyInput;
+import com.jme3.input.Joystick;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.input.event.JoyButtonEvent;
+import com.jme3.util.IntMap;
+import java.util.HashMap;
+import net.java.games.input.Component.Identifier;
+import net.java.games.input.Component.Identifier.Axis;
+import net.java.games.input.Component.Identifier.Button;
+import net.java.games.input.Component.POV;
+import net.java.games.input.*;
+
+public class JInputJoyInput implements JoyInput {
+
+    private boolean inited = false;
+    private Joystick[] joysticks;
+    private RawInputListener listener;
+
+    private HashMap<Button, Integer>[] buttonIdsToIndices;
+    private HashMap<Axis, Integer>[] axisIdsToIndices;
+    private HashMap<Controller, Integer> controllerToIndices;
+    private IntMap<Controller> indicesToController;
+
+    private int xAxis, yAxis;
+
+    private void loadIdentifiers(int controllerIdx, Controller c){
+        Component[] ces = c.getComponents();
+        int numButtons = 0;
+        int numAxes = 0;
+        xAxis = -1;
+        yAxis = -1;
+        for (Component comp : ces){
+            Identifier id = comp.getIdentifier();
+            if (id instanceof Button){
+                buttonIdsToIndices[controllerIdx].put((Button)id, numButtons);
+                numButtons ++;
+            }else if (id instanceof Axis){
+                Axis axis = (Axis) id;
+                if (axis == Axis.X){
+                    xAxis = numAxes;
+                }else if (axis == Axis.Y){
+                    yAxis = numAxes;
+                }
+
+                axisIdsToIndices[controllerIdx].put((Axis)id, numAxes);
+                numAxes ++;
+            }
+        }
+    }
+
+    public void setJoyRumble(int joyId, float amount){
+        Controller c = indicesToController.get(joyId);
+        if (c == null)
+            throw new IllegalArgumentException();
+
+        for (Rumbler r : c.getRumblers()){
+            r.rumble(amount);
+        }
+    }
+
+    public Joystick[] loadJoysticks(InputManager inputManager){
+        ControllerEnvironment ce =
+            ControllerEnvironment.getDefaultEnvironment();
+
+        int joyIndex = 0;
+        controllerToIndices = new HashMap<Controller, Integer>();
+        indicesToController = new IntMap<Controller>();
+        Controller[] cs = ce.getControllers();
+        for (int i = 0; i < cs.length; i++){
+            Controller c = cs[i];
+            if (c.getType() == Controller.Type.KEYBOARD
+             || c.getType() == Controller.Type.MOUSE)
+                continue;
+
+            controllerToIndices.put(c, joyIndex);
+            indicesToController.put(joyIndex, c);
+            joyIndex ++;
+        }
+
+        buttonIdsToIndices = new HashMap[joyIndex];
+        axisIdsToIndices = new HashMap[joyIndex];
+        joysticks = new Joystick[joyIndex];
+
+        joyIndex = 0;
+
+        for (int i = 0; i < cs.length; i++){
+            Controller c = cs[i];
+            if (c.getType() == Controller.Type.KEYBOARD
+             || c.getType() == Controller.Type.MOUSE)
+                continue;
+
+            buttonIdsToIndices[joyIndex] = new HashMap<Button, Integer>();
+            axisIdsToIndices[joyIndex] = new HashMap<Axis, Integer>();
+            loadIdentifiers(joyIndex, c);
+            Joystick joy = new Joystick(inputManager,
+                                        this,
+                                        joyIndex, c.getName(),
+                                        buttonIdsToIndices[joyIndex].size(),
+                                        axisIdsToIndices[joyIndex].size(),
+                                        xAxis, yAxis);
+            joysticks[joyIndex] = joy;
+            joyIndex++;
+        }
+
+        return joysticks;
+    }
+
+    public void initialize() {
+        inited = true;
+    }
+
+    public void update() {
+        ControllerEnvironment ce =
+            ControllerEnvironment.getDefaultEnvironment();
+
+        Controller[] cs = ce.getControllers();
+        Event e = new Event();
+        for (int i = 0; i < cs.length; i++){
+            Controller c = cs[i];
+            if (c.getType() == Controller.Type.UNKNOWN
+             || c.getType() == Controller.Type.KEYBOARD
+             || c.getType() == Controller.Type.MOUSE)
+                continue;
+
+            if (!c.poll())
+                continue;
+
+            int joyId = controllerToIndices.get(c);
+            EventQueue q = c.getEventQueue();
+            while (q.getNextEvent(e)){
+                Identifier id = e.getComponent().getIdentifier();
+                if (id == Identifier.Axis.POV){
+                    float x = 0, y = 0;
+                    float v = e.getValue();
+                    
+                    if (v == POV.CENTER){
+                        x = 0; y = 0;
+                    }else if (v == POV.DOWN){
+                        x = 0; y = -1f;
+                    }else if (v == POV.DOWN_LEFT){
+                        x = -1f; y = -1f;
+                    }else if (v == POV.DOWN_RIGHT){
+                        x = -1f; y = 1f;
+                    }else if (v == POV.LEFT){
+                        x = -1f; y = 0;
+                    }else if (v == POV.RIGHT){
+                        x = 1f; y = 0;
+                    }else if (v == POV.UP){
+                        x = 0; y = 1f;
+                    }else if (v == POV.UP_LEFT){
+                        x = -1f; y = 1f;
+                    }else if (v == POV.UP_RIGHT){
+                        x = 1f; y = 1f;
+                    }
+
+                    JoyAxisEvent evt1 = new JoyAxisEvent(joyId, JoyInput.AXIS_POV_X, x);
+                    JoyAxisEvent evt2 = new JoyAxisEvent(joyId, JoyInput.AXIS_POV_Y, y);
+                    listener.onJoyAxisEvent(evt1);
+                    listener.onJoyAxisEvent(evt2);
+                }else if (id instanceof Axis){
+                    float value = e.getValue();   
+                    Axis axis = (Axis) id;
+                    JoyAxisEvent evt = new JoyAxisEvent(joyId, axisIdsToIndices[joyId].get(axis), value);
+                    listener.onJoyAxisEvent(evt);
+                }else if (id instanceof Button){
+                    Button button = (Button) id;
+                    JoyButtonEvent evt = new JoyButtonEvent(joyId, buttonIdsToIndices[joyId].get(button), e.getValue() == 1f);
+                    listener.onJoyButtonEvent(evt);
+                }
+            }
+        }
+    }
+
+    public void destroy() {
+        inited = false;
+    }
+
+    public boolean isInitialized() {
+        return inited;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return 0;
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglKeyInput.java b/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglKeyInput.java
new file mode 100644
index 0000000..1e31e60
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglKeyInput.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.lwjgl;
+
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.system.lwjgl.LwjglAbstractDisplay;
+import com.jme3.system.lwjgl.LwjglTimer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.Sys;
+import org.lwjgl.input.Keyboard;
+
+public class LwjglKeyInput implements KeyInput {
+
+    private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName());
+
+    private LwjglAbstractDisplay context;
+
+    private RawInputListener listener;
+
+    public LwjglKeyInput(LwjglAbstractDisplay context){
+        this.context = context;
+    }
+
+    public void initialize() {
+        if (!context.isRenderable())
+            return;
+        
+        try {
+            Keyboard.create();
+            Keyboard.enableRepeatEvents(true);
+            logger.info("Keyboard created.");
+        } catch (LWJGLException ex) {
+            logger.log(Level.SEVERE, "Error while creating keyboard.", ex);
+        }
+    }
+
+    public int getKeyCount(){
+        return Keyboard.KEYBOARD_SIZE;
+    }
+
+    public void update() {
+        if (!context.isRenderable())
+            return;
+        
+        Keyboard.poll();
+        while (Keyboard.next()){
+            int keyCode = Keyboard.getEventKey();
+            char keyChar = Keyboard.getEventCharacter();
+            boolean pressed = Keyboard.getEventKeyState();
+            boolean down = Keyboard.isRepeatEvent();
+            long time = Keyboard.getEventNanoseconds();
+            KeyInputEvent evt = new KeyInputEvent(keyCode, keyChar, pressed, down);
+            evt.setTime(time);
+            listener.onKeyEvent(evt);
+        }
+    }
+
+    public void destroy() {
+        if (!context.isRenderable())
+            return;
+        
+        Keyboard.destroy();
+        logger.info("Keyboard destroyed.");
+    }
+
+    public boolean isInitialized() {
+        return Keyboard.isCreated();
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS;
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglMouseInput.java b/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglMouseInput.java
new file mode 100644
index 0000000..2e45d94
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/input/lwjgl/LwjglMouseInput.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.lwjgl;
+
+import com.jme3.input.MouseInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.system.lwjgl.LwjglAbstractDisplay;
+import com.jme3.system.lwjgl.LwjglTimer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.Sys;
+import org.lwjgl.input.Cursor;
+import org.lwjgl.input.Mouse;
+
+public class LwjglMouseInput implements MouseInput {
+
+    private static final Logger logger = Logger.getLogger(LwjglMouseInput.class.getName());
+
+    private LwjglAbstractDisplay context;
+
+    private RawInputListener listener;
+
+    private boolean supportHardwareCursor = false;
+    private boolean cursorVisible = true;
+
+    private int curX, curY, curWheel;
+
+    public LwjglMouseInput(LwjglAbstractDisplay context){
+        this.context = context;
+    }
+
+    public void initialize() {
+        if (!context.isRenderable())
+            return;
+
+        try {
+            Mouse.create();
+            logger.info("Mouse created.");
+            supportHardwareCursor = (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) != 0;
+            
+            // Recall state that was set before initialization
+            Mouse.setGrabbed(!cursorVisible);
+        } catch (LWJGLException ex) {
+            logger.log(Level.SEVERE, "Error while creating mouse", ex);
+        }
+    }
+
+    public boolean isInitialized(){
+        return Mouse.isCreated();
+    }
+
+    public int getButtonCount(){
+        return Mouse.getButtonCount();
+    }
+
+    public void update() {
+        if (!context.isRenderable())
+            return;
+
+        while (Mouse.next()){
+            int btn = Mouse.getEventButton();
+
+            int wheelDelta = Mouse.getEventDWheel();
+            int xDelta = Mouse.getEventDX();
+            int yDelta = Mouse.getEventDY();
+            int x = Mouse.getX();
+            int y = Mouse.getY();
+
+            curWheel += wheelDelta;
+            if (cursorVisible){
+                xDelta = x - curX;
+                yDelta = y - curY;
+                curX = x;
+                curY = y;
+            }else{
+                x = curX + xDelta;
+                y = curY + yDelta;
+                curX = x;
+                curY = y;
+            }
+
+            if (xDelta != 0 || yDelta != 0 || wheelDelta != 0){
+                MouseMotionEvent evt = new MouseMotionEvent(x, y, xDelta, yDelta, curWheel, wheelDelta);
+                evt.setTime(Mouse.getEventNanoseconds());
+                listener.onMouseMotionEvent(evt);
+            }
+            if (btn != -1){
+                MouseButtonEvent evt = new MouseButtonEvent(btn,
+                                                            Mouse.getEventButtonState(), x, y);
+                evt.setTime(Mouse.getEventNanoseconds());
+                listener.onMouseButtonEvent(evt);
+            }
+        }
+    }
+
+    public void destroy() {
+        if (!context.isRenderable())
+            return;
+
+        Mouse.destroy();
+        logger.info("Mouse destroyed.");
+    }
+
+    public void setCursorVisible(boolean visible){
+        cursorVisible = visible;
+        if (!context.isRenderable())
+            return;
+        
+        Mouse.setGrabbed(!visible);
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS;
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
new file mode 100644
index 0000000..ec41e0c
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
@@ -0,0 +1,1169 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.light.*;
+import com.jme3.material.FixedFuncBinding;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.GL1Renderer;
+import com.jme3.renderer.RenderContext;
+import com.jme3.renderer.Statistics;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapAxis;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.NativeObjectManager;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jme3tools.converters.MipMapGenerator;
+import static org.lwjgl.opengl.GL11.*;
+import org.lwjgl.opengl.GL12;
+import org.lwjgl.opengl.GL14;
+import org.lwjgl.opengl.GLContext;
+
+public class LwjglGL1Renderer implements GL1Renderer {
+
+    private static final Logger logger = Logger.getLogger(LwjglRenderer.class.getName());
+    private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250);
+    private final StringBuilder stringBuf = new StringBuilder(250);
+    private final IntBuffer ib1 = BufferUtils.createIntBuffer(1);
+    private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16);
+    private final FloatBuffer fb16 = BufferUtils.createFloatBuffer(16);
+    private final FloatBuffer fb4Null = BufferUtils.createFloatBuffer(4);
+    private final RenderContext context = new RenderContext();
+    private final NativeObjectManager objManager = new NativeObjectManager();
+    private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
+    private int maxTexSize;
+    private int maxCubeTexSize;
+    private int maxVertCount;
+    private int maxTriCount;
+    private int maxLights;
+    private boolean gl12 = false;
+    private final Statistics statistics = new Statistics();
+    private int vpX, vpY, vpW, vpH;
+    private int clipX, clipY, clipW, clipH;
+    
+    private Matrix4f worldMatrix = new Matrix4f();
+    private Matrix4f viewMatrix = new Matrix4f();
+    
+    private ArrayList<Light> lightList = new ArrayList<Light>(8);
+    private ColorRGBA materialAmbientColor = new ColorRGBA();
+    private Vector3f tempVec = new Vector3f();
+
+    protected void updateNameBuffer() {
+        int len = stringBuf.length();
+
+        nameBuf.position(0);
+        nameBuf.limit(len);
+        for (int i = 0; i < len; i++) {
+            nameBuf.put((byte) stringBuf.charAt(i));
+        }
+
+        nameBuf.rewind();
+    }
+
+    public Statistics getStatistics() {
+        return statistics;
+    }
+
+    public EnumSet<Caps> getCaps() {
+        return caps;
+    }
+
+    public void initialize() {
+        if (GLContext.getCapabilities().OpenGL12){
+            gl12 = true;
+        }
+        
+        // Default values for certain GL state.
+        glShadeModel(GL_SMOOTH);
+        glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
+        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
+        
+		// Enable rescaling/normaling of normal vectors.
+		// Fixes lighting issues with scaled models.
+        if (gl12){
+            glEnable(GL12.GL_RESCALE_NORMAL);
+        }else{
+            glEnable(GL_NORMALIZE);
+        }
+
+        if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
+            caps.add(Caps.NonPowerOfTwoTextures);
+        } else {
+            logger.log(Level.WARNING, "Your graphics card does not "
+                    + "support non-power-of-2 textures. "
+                    + "Some features might not work.");
+        }
+        
+        maxLights = glGetInteger(GL_MAX_LIGHTS);
+        
+    }
+    
+    public void invalidateState() {
+        context.reset();
+    }
+
+    public void resetGLObjects() {
+        logger.log(Level.INFO, "Reseting objects and invalidating state");
+        objManager.resetObjects();
+        statistics.clearMemory();
+        invalidateState();
+    }
+
+    public void cleanup() {
+        logger.log(Level.INFO, "Deleting objects and invalidating state");
+        objManager.deleteAllObjects(this);
+        statistics.clearMemory();
+        invalidateState();
+    }
+
+    public void setDepthRange(float start, float end) {
+        glDepthRange(start, end);
+    }
+
+    public void clearBuffers(boolean color, boolean depth, boolean stencil) {
+        int bits = 0;
+        if (color) {
+            //See explanations of the depth below, we must enable color write to be able to clear the color buffer
+            if (context.colorWriteEnabled == false) {
+                glColorMask(true, true, true, true);
+                context.colorWriteEnabled = true;
+            }
+            bits = GL_COLOR_BUFFER_BIT;
+        }
+        if (depth) {
+
+            //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false
+            //here s some link on openl board
+            //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223
+            //if depth clear is requested, we enable the depthMask
+            if (context.depthWriteEnabled == false) {
+                glDepthMask(true);
+                context.depthWriteEnabled = true;
+            }
+            bits |= GL_DEPTH_BUFFER_BIT;
+        }
+        if (stencil) {
+            bits |= GL_STENCIL_BUFFER_BIT;
+        }
+        if (bits != 0) {
+            glClear(bits);
+        }
+    }
+
+    public void setBackgroundColor(ColorRGBA color) {
+        glClearColor(color.r, color.g, color.b, color.a);
+    }
+
+    private void setMaterialColor(int type, ColorRGBA color, ColorRGBA defaultColor) {
+        if (color != null){
+            fb16.put(color.r).put(color.g).put(color.b).put(color.a).flip();
+        }else{
+            fb16.put(defaultColor.r).put(defaultColor.g).put(defaultColor.b).put(defaultColor.a).flip();
+        }
+        glMaterial(GL_FRONT_AND_BACK, type, fb16);
+    }
+    
+    /**
+     * Applies fixed function bindings from the context to OpenGL
+     */
+    private void applyFixedFuncBindings(boolean forLighting){
+        if (forLighting){
+            glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, context.shininess);
+            setMaterialColor(GL_AMBIENT,  context.ambient,  ColorRGBA.DarkGray);
+            setMaterialColor(GL_DIFFUSE,  context.diffuse,  ColorRGBA.White);
+            setMaterialColor(GL_SPECULAR, context.specular, ColorRGBA.Black);
+            
+            if (context.useVertexColor){
+                glEnable(GL_COLOR_MATERIAL);
+            }else{
+                glDisable(GL_COLOR_MATERIAL);
+            }
+        }else{
+            // Ignore other values as they have no effect when 
+            // GL_LIGHTING is disabled.
+            ColorRGBA color = context.color;
+            if (color != null){
+                glColor4f(color.r, color.g, color.b, color.a);
+            }else{
+                glColor4f(1,1,1,1);
+            }
+        }
+    }
+    
+    /**
+     * Reset fixed function bindings to default values.
+     */
+    private void resetFixedFuncBindings(){
+        context.color = null;
+        context.ambient = null;
+        context.diffuse = null;
+        context.specular = null;
+        context.shininess = 0;
+        context.useVertexColor = false;
+    }
+    
+    public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val) {
+        switch (ffBinding) {
+            case Color:
+                context.color = (ColorRGBA) val;
+                break;
+            case MaterialAmbient:
+                context.ambient = (ColorRGBA) val;
+                break;
+            case MaterialDiffuse:
+                context.diffuse = (ColorRGBA) val;
+                break;
+            case MaterialSpecular:
+                context.specular = (ColorRGBA) val;
+                break;
+            case MaterialShininess:
+                context.shininess = (Float) val;
+                break;
+            case UseVertexColor:
+                context.useVertexColor = (Boolean) val;
+                break;
+        }
+    }
+    
+    public void applyRenderState(RenderState state) {
+        if (state.isWireframe() && !context.wireframe) {
+            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+            context.wireframe = true;
+        } else if (!state.isWireframe() && context.wireframe) {
+            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+            context.wireframe = false;
+        }
+
+        if (state.isDepthTest() && !context.depthTestEnabled) {
+            glEnable(GL_DEPTH_TEST);
+            glDepthFunc(GL_LEQUAL);
+            context.depthTestEnabled = true;
+        } else if (!state.isDepthTest() && context.depthTestEnabled) {
+            glDisable(GL_DEPTH_TEST);
+            context.depthTestEnabled = false;
+        }
+
+        if (state.isAlphaTest() && !context.alphaTestEnabled) {
+            glEnable(GL_ALPHA_TEST);
+            glAlphaFunc(GL_GREATER, state.getAlphaFallOff());
+            context.alphaTestEnabled = true;
+        } else if (!state.isAlphaTest() && context.alphaTestEnabled) {
+            glDisable(GL_ALPHA_TEST);
+            context.alphaTestEnabled = false;
+        }
+
+        if (state.isDepthWrite() && !context.depthWriteEnabled) {
+            glDepthMask(true);
+            context.depthWriteEnabled = true;
+        } else if (!state.isDepthWrite() && context.depthWriteEnabled) {
+            glDepthMask(false);
+            context.depthWriteEnabled = false;
+        }
+
+        if (state.isColorWrite() && !context.colorWriteEnabled) {
+            glColorMask(true, true, true, true);
+            context.colorWriteEnabled = true;
+        } else if (!state.isColorWrite() && context.colorWriteEnabled) {
+            glColorMask(false, false, false, false);
+            context.colorWriteEnabled = false;
+        }
+
+        if (state.isPointSprite()) {
+            logger.log(Level.WARNING, "Point Sprite unsupported!");
+        }
+
+        if (state.isPolyOffset()) {
+            if (!context.polyOffsetEnabled) {
+                glEnable(GL_POLYGON_OFFSET_FILL);
+                glPolygonOffset(state.getPolyOffsetFactor(),
+                        state.getPolyOffsetUnits());
+                context.polyOffsetEnabled = true;
+                context.polyOffsetFactor = state.getPolyOffsetFactor();
+                context.polyOffsetUnits = state.getPolyOffsetUnits();
+            } else {
+                if (state.getPolyOffsetFactor() != context.polyOffsetFactor
+                        || state.getPolyOffsetUnits() != context.polyOffsetUnits) {
+                    glPolygonOffset(state.getPolyOffsetFactor(),
+                            state.getPolyOffsetUnits());
+                    context.polyOffsetFactor = state.getPolyOffsetFactor();
+                    context.polyOffsetUnits = state.getPolyOffsetUnits();
+                }
+            }
+        } else {
+            if (context.polyOffsetEnabled) {
+                glDisable(GL_POLYGON_OFFSET_FILL);
+                context.polyOffsetEnabled = false;
+                context.polyOffsetFactor = 0;
+                context.polyOffsetUnits = 0;
+            }
+        }
+        if (state.getFaceCullMode() != context.cullMode) {
+            if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) {
+                glDisable(GL_CULL_FACE);
+            } else {
+                glEnable(GL_CULL_FACE);
+            }
+
+            switch (state.getFaceCullMode()) {
+                case Off:
+                    break;
+                case Back:
+                    glCullFace(GL_BACK);
+                    break;
+                case Front:
+                    glCullFace(GL_FRONT);
+                    break;
+                case FrontAndBack:
+                    glCullFace(GL_FRONT_AND_BACK);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unrecognized face cull mode: "
+                            + state.getFaceCullMode());
+            }
+
+            context.cullMode = state.getFaceCullMode();
+        }
+
+        if (state.getBlendMode() != context.blendMode) {
+            if (state.getBlendMode() == RenderState.BlendMode.Off) {
+                glDisable(GL_BLEND);
+            } else {
+                glEnable(GL_BLEND);
+                switch (state.getBlendMode()) {
+                    case Off:
+                        break;
+                    case Additive:
+                        glBlendFunc(GL_ONE, GL_ONE);
+                        break;
+                    case AlphaAdditive:
+                        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+                        break;
+                    case Color:
+                        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
+                        break;
+                    case Alpha:
+                        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+                        break;
+                    case PremultAlpha:
+                        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+                        break;
+                    case Modulate:
+                        glBlendFunc(GL_DST_COLOR, GL_ZERO);
+                        break;
+                    case ModulateX2:
+                        glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unrecognized blend mode: "
+                                + state.getBlendMode());
+                }
+            }
+
+            context.blendMode = state.getBlendMode();
+        }
+
+        if (state.isStencilTest()) {
+            throw new UnsupportedOperationException("OpenGL 1.1 doesn't support two sided stencil operations.");
+        }
+
+    }
+
+    public void setViewPort(int x, int y, int w, int h) {
+        if (x != vpX || vpY != y || vpW != w || vpH != h) {
+            glViewport(x, y, w, h);
+            vpX = x;
+            vpY = y;
+            vpW = w;
+            vpH = h;
+        }
+    }
+
+    public void setClipRect(int x, int y, int width, int height) {
+        if (!context.clipRectEnabled) {
+            glEnable(GL_SCISSOR_TEST);
+            context.clipRectEnabled = true;
+        }
+        if (clipX != x || clipY != y || clipW != width || clipH != height) {
+            glScissor(x, y, width, height);
+            clipX = x;
+            clipY = y;
+            clipW = width;
+            clipH = height;
+        }
+    }
+
+    public void clearClipRect() {
+        if (context.clipRectEnabled) {
+            glDisable(GL_SCISSOR_TEST);
+            context.clipRectEnabled = false;
+
+            clipX = 0;
+            clipY = 0;
+            clipW = 0;
+            clipH = 0;
+        }
+    }
+
+    public void onFrame() {
+        objManager.deleteUnused(this);
+//        statistics.clearFrame();
+    }
+
+    private FloatBuffer storeMatrix(Matrix4f matrix, FloatBuffer store) {
+        store.clear();
+        matrix.fillFloatBuffer(store, true);
+        store.clear();
+        return store;
+    }
+    
+    private void setModelView(Matrix4f modelMatrix, Matrix4f viewMatrix){
+        if (context.matrixMode != GL_MODELVIEW) {
+            glMatrixMode(GL_MODELVIEW);
+            context.matrixMode = GL_MODELVIEW;
+        }
+
+        glLoadMatrix(storeMatrix(viewMatrix, fb16));
+        glMultMatrix(storeMatrix(modelMatrix, fb16));
+    }
+    
+    private void setProjection(Matrix4f projMatrix){
+        if (context.matrixMode != GL_PROJECTION) {
+            glMatrixMode(GL_PROJECTION);
+            context.matrixMode = GL_PROJECTION;
+        }
+
+        glLoadMatrix(storeMatrix(projMatrix, fb16));
+    }
+
+    public void setWorldMatrix(Matrix4f worldMatrix) {
+        this.worldMatrix.set(worldMatrix);
+    }
+
+    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
+        this.viewMatrix.set(viewMatrix);
+        setProjection(projMatrix);
+    }
+
+    public void setLighting(LightList list) {
+        // XXX: This is abuse of setLighting() to
+		// apply fixed function bindings
+        // and do other book keeping.
+        if (list == null || list.size() == 0){
+            glDisable(GL_LIGHTING);
+            applyFixedFuncBindings(false);
+            setModelView(worldMatrix, viewMatrix);
+            return;
+        }
+        
+        // Number of lights set previously
+        int numLightsSetPrev = lightList.size();
+        
+        // If more than maxLights are defined, they will be ignored.
+        // The GL1 renderer is not permitted to crash due to a 
+        // GL1 limitation. It must render anything that the GL2 renderer
+        // can render (even incorrectly).
+        lightList.clear();
+        materialAmbientColor.set(0, 0, 0, 0);
+        
+        for (int i = 0; i < list.size(); i++){
+            Light l = list.get(i);
+            if (l.getType() == Light.Type.Ambient){
+                // Gather
+                materialAmbientColor.addLocal(l.getColor());
+            }else{
+                // Add to list
+                lightList.add(l);
+                
+                // Once maximum lights reached, exit loop.
+                if (lightList.size() >= maxLights){
+                    break;
+                }
+            }
+        }
+        
+        applyFixedFuncBindings(true);
+        
+        glEnable(GL_LIGHTING);
+        
+        fb16.clear();
+        fb16.put(materialAmbientColor.r)
+            .put(materialAmbientColor.g)
+            .put(materialAmbientColor.b)
+            .put(1).flip();
+        
+        glLightModel(GL_LIGHT_MODEL_AMBIENT, fb16);
+        
+        if (context.matrixMode != GL_MODELVIEW) {
+            glMatrixMode(GL_MODELVIEW);
+            context.matrixMode = GL_MODELVIEW;
+        }
+        // Lights are already in world space, so just convert
+        // them to view space.
+        glLoadMatrix(storeMatrix(viewMatrix, fb16));
+        
+        for (int i = 0; i < lightList.size(); i++){
+            int glLightIndex = GL_LIGHT0 + i;
+            Light light = lightList.get(i);
+            Light.Type lightType = light.getType();
+            ColorRGBA col = light.getColor();
+            Vector3f pos;
+            
+            // Enable the light
+            glEnable(glLightIndex);
+            
+            // OGL spec states default value for light ambient is black
+            switch (lightType){
+                case Directional:
+                    DirectionalLight dLight = (DirectionalLight) light;
+
+                    fb16.clear();
+                    fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip();
+                    glLight(glLightIndex, GL_DIFFUSE, fb16);
+                    glLight(glLightIndex, GL_SPECULAR, fb16);
+
+                    pos = tempVec.set(dLight.getDirection()).negateLocal().normalizeLocal();
+                    fb16.clear();
+                    fb16.put(pos.x).put(pos.y).put(pos.z).put(0.0f).flip();
+                    glLight(glLightIndex, GL_POSITION, fb16);
+                    glLightf(glLightIndex, GL_SPOT_CUTOFF, 180);
+                    break;
+                case Point:
+                    PointLight pLight = (PointLight) light;
+      
+                    fb16.clear();
+                    fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip();
+                    glLight(glLightIndex, GL_DIFFUSE, fb16);
+                    glLight(glLightIndex, GL_SPECULAR, fb16);
+
+                    pos = pLight.getPosition();
+                    fb16.clear();
+                    fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip();
+                    glLight(glLightIndex, GL_POSITION, fb16);
+                    glLightf(glLightIndex, GL_SPOT_CUTOFF, 180);
+
+                    if (pLight.getRadius() > 0) {
+                        // Note: this doesn't follow the same attenuation model
+                        // as the one used in the lighting shader.
+                        glLightf(glLightIndex, GL_CONSTANT_ATTENUATION,  1);
+                        glLightf(glLightIndex, GL_LINEAR_ATTENUATION,    pLight.getInvRadius() * 2);
+                        glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, pLight.getInvRadius() * pLight.getInvRadius()); 
+                    }else{
+                        glLightf(glLightIndex, GL_CONSTANT_ATTENUATION,  1);
+                        glLightf(glLightIndex, GL_LINEAR_ATTENUATION,    0);
+                        glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, 0);
+                    }
+
+                    break;
+                case Spot:
+                    SpotLight sLight = (SpotLight) light;
+
+                    fb16.clear();
+                    fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip();
+                    glLight(glLightIndex, GL_DIFFUSE, fb16);
+                    glLight(glLightIndex, GL_SPECULAR, fb16);
+
+                    pos = sLight.getPosition();
+                    fb16.clear();
+                    fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip();
+                    glLight(glLightIndex, GL_POSITION, fb16);
+
+                    Vector3f dir = sLight.getDirection();
+                    fb16.clear();
+                    fb16.put(dir.x).put(dir.y).put(dir.z).put(1.0f).flip();
+                    glLight(glLightIndex, GL_SPOT_DIRECTION, fb16);
+
+                    float outerAngleRad = sLight.getSpotOuterAngle();
+                    float innerAngleRad = sLight.getSpotInnerAngle();
+                    float spotCut = outerAngleRad * FastMath.RAD_TO_DEG;
+                    float spotExpo = 0.0f;
+                    if (outerAngleRad > 0) {
+                        spotExpo = (1.0f - (innerAngleRad / outerAngleRad)) * 128.0f;
+                    }
+
+                    glLightf(glLightIndex, GL_SPOT_CUTOFF, spotCut);
+                    glLightf(glLightIndex, GL_SPOT_EXPONENT, spotExpo);
+
+                    if (sLight.getSpotRange() > 0) {
+                        glLightf(glLightIndex, GL_LINEAR_ATTENUATION, sLight.getInvSpotRange());
+                    }else{
+                        glLightf(glLightIndex, GL_LINEAR_ATTENUATION, 0);
+                    }
+
+                    break;
+                default:
+                    throw new UnsupportedOperationException(
+                            "Unrecognized light type: " + lightType);
+            }
+        }
+        
+        // Disable lights after the index
+        for (int i = lightList.size(); i < numLightsSetPrev; i++){
+            glDisable(GL_LIGHT0 + i);
+        }
+        
+        // This will set view matrix as well.
+        setModelView(worldMatrix, viewMatrix);
+    }
+
+    private int convertTextureType(Texture.Type type) {
+        switch (type) {
+            case TwoDimensional:
+                return GL_TEXTURE_2D;
+//            case ThreeDimensional:
+//                return GL_TEXTURE_3D;
+//            case CubeMap:
+//                return GL_TEXTURE_CUBE_MAP;
+            default:
+                throw new UnsupportedOperationException("Unknown texture type: " + type);
+        }
+    }
+
+    private int convertMagFilter(Texture.MagFilter filter) {
+        switch (filter) {
+            case Bilinear:
+                return GL_LINEAR;
+            case Nearest:
+                return GL_NEAREST;
+            default:
+                throw new UnsupportedOperationException("Unknown mag filter: " + filter);
+        }
+    }
+
+    private int convertMinFilter(Texture.MinFilter filter) {
+        switch (filter) {
+            case Trilinear:
+                return GL_LINEAR_MIPMAP_LINEAR;
+            case BilinearNearestMipMap:
+                return GL_LINEAR_MIPMAP_NEAREST;
+            case NearestLinearMipMap:
+                return GL_NEAREST_MIPMAP_LINEAR;
+            case NearestNearestMipMap:
+                return GL_NEAREST_MIPMAP_NEAREST;
+            case BilinearNoMipMaps:
+                return GL_LINEAR;
+            case NearestNoMipMaps:
+                return GL_NEAREST;
+            default:
+                throw new UnsupportedOperationException("Unknown min filter: " + filter);
+        }
+    }
+
+    private int convertWrapMode(Texture.WrapMode mode) {
+        switch (mode) {
+            case EdgeClamp:
+            case Clamp:
+            case BorderClamp:
+                return GL_CLAMP;
+            case Repeat:
+                return GL_REPEAT;
+            default:
+                throw new UnsupportedOperationException("Unknown wrap mode: " + mode);
+        }
+    }
+
+    private void setupTextureParams(Texture tex) {
+        int target = convertTextureType(tex.getType());
+
+        // filter things
+        int minFilter = convertMinFilter(tex.getMinFilter());
+        int magFilter = convertMagFilter(tex.getMagFilter());
+        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter);
+        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter);
+
+        // repeat modes
+        switch (tex.getType()) {
+//            case ThreeDimensional:
+//            case CubeMap:
+//                glTexParameteri(target, GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R)));
+            case TwoDimensional:
+                glTexParameteri(target, GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T)));
+                // fall down here is intentional..
+//            case OneDimensional:
+                glTexParameteri(target, GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S)));
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown texture type: " + tex.getType());
+        }
+    }
+
+    public void updateTexImageData(Image img, Texture.Type type, boolean mips, int unit) {
+        int texId = img.getId();
+        if (texId == -1) {
+            // create texture
+            glGenTextures(ib1);
+            texId = ib1.get(0);
+            img.setId(texId);
+            objManager.registerForCleanup(img);
+
+            statistics.onNewTexture();
+        }
+
+        // bind texture
+        int target = convertTextureType(type);
+//        if (context.boundTextureUnit != unit) {
+//            glActiveTexture(GL_TEXTURE0 + unit);
+//            context.boundTextureUnit = unit;
+//        }
+        if (context.boundTextures[unit] != img) {
+            glEnable(target);
+            glBindTexture(target, texId);
+            context.boundTextures[unit] = img;
+
+            statistics.onTextureUse(img, true);
+        }
+
+        // Check sizes if graphics card doesn't support NPOT
+        if (!GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
+            if (img.getWidth() != 0 && img.getHeight() != 0) {
+                if (!FastMath.isPowerOfTwo(img.getWidth())
+                        || !FastMath.isPowerOfTwo(img.getHeight())) {
+
+                    // Resize texture to Power-of-2 size
+                    MipMapGenerator.resizeToPowerOf2(img);
+
+                }
+            }
+        }
+
+        if (!img.hasMipmaps() && mips) {
+            // No pregenerated mips available,
+            // generate from base level if required
+
+            // Check if hardware mips are supported
+            if (GLContext.getCapabilities().OpenGL14) {
+                glTexParameteri(target, GL14.GL_GENERATE_MIPMAP, GL_TRUE);
+            } else {
+                MipMapGenerator.generateMipMaps(img);
+            }
+        } else {
+        }
+
+        /*
+        if (target == GL_TEXTURE_CUBE_MAP) {
+        List<ByteBuffer> data = img.getData();
+        if (data.size() != 6) {
+        logger.log(Level.WARNING, "Invalid texture: {0}\n"
+        + "Cubemap textures must contain 6 data units.", img);
+        return;
+        }
+        for (int i = 0; i < 6; i++) {
+        TextureUtil.uploadTexture(img, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc);
+        }
+        } else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT) {
+        List<ByteBuffer> data = img.getData();
+        // -1 index specifies prepare data for 2D Array
+        TextureUtil.uploadTexture(img, target, -1, 0, tdc);
+        for (int i = 0; i < data.size(); i++) {
+        // upload each slice of 2D array in turn
+        // this time with the appropriate index
+        TextureUtil.uploadTexture(img, target, i, 0, tdc);
+        }
+        } else {*/
+        TextureUtil.uploadTexture(img, target, 0, 0, false);
+        //}
+
+        img.clearUpdateNeeded();
+    }
+
+    public void setTexture(int unit, Texture tex) {
+        if (unit != 0 || tex.getType() != Texture.Type.TwoDimensional) {
+            //throw new UnsupportedOperationException();
+            return;
+        }
+
+        Image image = tex.getImage();
+        if (image.isUpdateNeeded()) {
+            updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels(), unit);
+        }
+
+        int texId = image.getId();
+        assert texId != -1;
+
+        Image[] textures = context.boundTextures;
+
+        int type = convertTextureType(tex.getType());
+//        if (!context.textureIndexList.moveToNew(unit)) {
+//             if (context.boundTextureUnit != unit){
+//                glActiveTexture(GL_TEXTURE0 + unit);
+//                context.boundTextureUnit = unit;
+//             }
+//             glEnable(type);
+//        }
+
+//        if (context.boundTextureUnit != unit) {
+//            glActiveTexture(GL_TEXTURE0 + unit);
+//            context.boundTextureUnit = unit;
+//        }
+
+        if (textures[unit] != image) {
+            glEnable(type);
+            glBindTexture(type, texId);
+            textures[unit] = image;
+
+            statistics.onTextureUse(image, true);
+        } else {
+            statistics.onTextureUse(image, false);
+        }
+
+        setupTextureParams(tex);
+    }
+
+    private void clearTextureUnits() {
+        Image[] textures = context.boundTextures;
+        if (textures[0] != null) {
+            glDisable(GL_TEXTURE_2D);
+            textures[0] = null;
+        }
+    }
+
+    public void deleteImage(Image image) {
+        int texId = image.getId();
+        if (texId != -1) {
+            ib1.put(0, texId);
+            ib1.position(0).limit(1);
+            glDeleteTextures(ib1);
+            image.resetObject();
+        }
+    }
+
+    private int convertArrayType(VertexBuffer.Type type) {
+        switch (type) {
+            case Position:
+                return GL_VERTEX_ARRAY;
+            case Normal:
+                return GL_NORMAL_ARRAY;
+            case TexCoord:
+                return GL_TEXTURE_COORD_ARRAY;
+            case Color:
+                return GL_COLOR_ARRAY;
+            default:
+                return -1; // unsupported
+        }
+    }
+
+    private int convertVertexFormat(VertexBuffer.Format fmt) {
+        switch (fmt) {
+            case Byte:
+                return GL_BYTE;
+            case Float:
+                return GL_FLOAT;
+            case Int:
+                return GL_INT;
+            case Short:
+                return GL_SHORT;
+            case UnsignedByte:
+                return GL_UNSIGNED_BYTE;
+            case UnsignedInt:
+                return GL_UNSIGNED_INT;
+            case UnsignedShort:
+                return GL_UNSIGNED_SHORT;
+            default:
+                throw new UnsupportedOperationException("Unrecognized vertex format: " + fmt);
+        }
+    }
+
+    private int convertElementMode(Mesh.Mode mode) {
+        switch (mode) {
+            case Points:
+                return GL_POINTS;
+            case Lines:
+                return GL_LINES;
+            case LineLoop:
+                return GL_LINE_LOOP;
+            case LineStrip:
+                return GL_LINE_STRIP;
+            case Triangles:
+                return GL_TRIANGLES;
+            case TriangleFan:
+                return GL_TRIANGLE_FAN;
+            case TriangleStrip:
+                return GL_TRIANGLE_STRIP;
+            default:
+                throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode);
+        }
+    }
+
+    public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
+        if (count > 1) {
+            throw new UnsupportedOperationException();
+        }
+
+        glDrawArrays(convertElementMode(mode), 0, vertCount);
+    }
+
+    public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
+        int arrayType = convertArrayType(vb.getBufferType());
+        if (arrayType == -1) {
+            return; // unsupported
+        }
+        glEnableClientState(arrayType);
+        context.boundAttribs[vb.getBufferType().ordinal()] = vb;
+
+        if (vb.getBufferType() == Type.Normal) {
+            // normalize if requested
+            if (vb.isNormalized() && !context.normalizeEnabled) {
+                glEnable(GL_NORMALIZE);
+                context.normalizeEnabled = true;
+            } else if (!vb.isNormalized() && context.normalizeEnabled) {
+                glDisable(GL_NORMALIZE);
+                context.normalizeEnabled = false;
+            }
+        }
+
+        // NOTE: Use data from interleaved buffer if specified
+        Buffer data = idb != null ? idb.getData() : vb.getData();
+        int comps = vb.getNumComponents();
+        int type = convertVertexFormat(vb.getFormat());
+
+        data.rewind();
+
+        switch (vb.getBufferType()) {
+            case Position:
+                if (!(data instanceof FloatBuffer)) {
+                    throw new UnsupportedOperationException();
+                }
+
+                glVertexPointer(comps, vb.getStride(), (FloatBuffer) data);
+                break;
+            case Normal:
+                if (!(data instanceof FloatBuffer)) {
+                    throw new UnsupportedOperationException();
+                }
+
+                glNormalPointer(vb.getStride(), (FloatBuffer) data);
+                break;
+            case Color:
+                if (data instanceof FloatBuffer) {
+                    glColorPointer(comps, vb.getStride(), (FloatBuffer) data);
+                } else if (data instanceof ByteBuffer) {
+                    glColorPointer(comps, true, vb.getStride(), (ByteBuffer) data);
+                } else {
+                    throw new UnsupportedOperationException();
+                }
+                break;
+            case TexCoord:
+                if (!(data instanceof FloatBuffer)) {
+                    throw new UnsupportedOperationException();
+                }
+
+                glTexCoordPointer(comps, vb.getStride(), (FloatBuffer) data);
+                break;
+            default:
+                // Ignore, this is an unsupported attribute for OpenGL1.
+                break;
+        }
+    }
+
+    public void setVertexAttrib(VertexBuffer vb) {
+        setVertexAttrib(vb, null);
+    }
+
+    private void drawElements(int mode, int format, Buffer data) {
+        switch (format) {
+            case GL_UNSIGNED_BYTE:
+                glDrawElements(mode, (ByteBuffer) data);
+                break;
+            case GL_UNSIGNED_SHORT:
+                glDrawElements(mode, (ShortBuffer) data);
+                break;
+            case GL_UNSIGNED_INT:
+                glDrawElements(mode, (IntBuffer) data);
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) {
+        Mesh.Mode mode = mesh.getMode();
+
+        Buffer indexData = indexBuf.getData();
+        indexData.rewind();
+
+        if (mesh.getMode() == Mode.Hybrid) {
+            throw new UnsupportedOperationException();
+            /*
+            int[] modeStart = mesh.getModeStart();
+            int[] elementLengths = mesh.getElementLengths();
+            
+            int elMode = convertElementMode(Mode.Triangles);
+            int fmt = convertVertexFormat(indexBuf.getFormat());
+            //            int elSize = indexBuf.getFormat().getComponentSize();
+            //            int listStart = modeStart[0];
+            int stripStart = modeStart[1];
+            int fanStart = modeStart[2];
+            int curOffset = 0;
+            for (int i = 0; i < elementLengths.length; i++) {
+            if (i == stripStart) {
+            elMode = convertElementMode(Mode.TriangleStrip);
+            } else if (i == fanStart) {
+            elMode = convertElementMode(Mode.TriangleStrip);
+            }
+            int elementLength = elementLengths[i];
+            indexData.position(curOffset);
+            
+            drawElements(elMode,
+            fmt,
+            indexData);
+            
+            curOffset += elementLength;
+            }*/
+        } else {
+            drawElements(convertElementMode(mode),
+                    convertVertexFormat(indexBuf.getFormat()),
+                    indexData);
+        }
+    }
+
+    public void clearVertexAttribs() {
+        for (int i = 0; i < 16; i++) {
+            VertexBuffer vb = context.boundAttribs[i];
+            if (vb != null) {
+                int arrayType = convertArrayType(vb.getBufferType());
+                glDisableClientState(arrayType);
+                context.boundAttribs[vb.getBufferType().ordinal()] = null;
+            }
+        }
+    }
+
+    private void renderMeshDefault(Mesh mesh, int lod, int count) {
+        VertexBuffer indices = null;
+
+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
+        if (interleavedData != null && interleavedData.isUpdateNeeded()) {
+            updateBufferData(interleavedData);
+        }
+
+        IntMap<VertexBuffer> buffers = mesh.getBuffers();
+        if (mesh.getNumLodLevels() > 0) {
+            indices = mesh.getLodLevel(lod);
+        } else {
+            indices = buffers.get(Type.Index.ordinal());
+        }
+        for (Entry<VertexBuffer> entry : buffers) {
+            VertexBuffer vb = entry.getValue();
+
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index) {
+                continue;
+            }
+
+            if (vb.getStride() == 0) {
+                // not interleaved
+                setVertexAttrib(vb);
+            } else {
+                // interleaved
+                setVertexAttrib(vb, interleavedData);
+            }
+        }
+
+        if (indices != null) {
+            drawTriangleList(indices, mesh, count);
+        } else {
+            glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount());
+        }
+        
+        // TODO: Fix these to use IDList??
+        clearVertexAttribs();
+        clearTextureUnits();
+        resetFixedFuncBindings();
+    }
+
+    public void renderMesh(Mesh mesh, int lod, int count) {
+        if (mesh.getVertexCount() == 0) {
+            return;
+        }
+
+        if (context.pointSize != mesh.getPointSize()) {
+            glPointSize(mesh.getPointSize());
+            context.pointSize = mesh.getPointSize();
+        }
+        if (context.lineWidth != mesh.getLineWidth()) {
+            glLineWidth(mesh.getLineWidth());
+            context.lineWidth = mesh.getLineWidth();
+        }
+        
+        boolean dynamic = false;
+        if (mesh.getBuffer(Type.InterleavedData) != null) {
+            throw new UnsupportedOperationException("Interleaved meshes are not supported");
+        }
+
+        if (mesh.getNumLodLevels() == 0) {
+            IntMap<VertexBuffer> bufs = mesh.getBuffers();
+            for (Entry<VertexBuffer> entry : bufs) {
+                if (entry.getValue().getUsage() != VertexBuffer.Usage.Static) {
+                    dynamic = true;
+                    break;
+                }
+            }
+        } else {
+            dynamic = true;
+        }
+
+        statistics.onMeshDrawn(mesh, lod);
+
+//        if (!dynamic) {
+        // dealing with a static object, generate display list
+//            renderMeshDisplayList(mesh);
+//        } else {
+        renderMeshDefault(mesh, lod, count);
+//        }
+
+
+    }
+
+    public void setAlphaToCoverage(boolean value) {
+    }
+
+    public void setShader(Shader shader) {
+    }
+
+    public void deleteShader(Shader shader) {
+    }
+
+    public void deleteShaderSource(ShaderSource source) {
+    }
+
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
+    }
+
+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
+    }
+
+    public void setMainFrameBufferOverride(FrameBuffer fb){
+    }
+    
+    public void setFrameBuffer(FrameBuffer fb) {
+    }
+
+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
+    }
+
+    public void deleteFrameBuffer(FrameBuffer fb) {
+    }
+
+    public void updateBufferData(VertexBuffer vb) {
+    }
+
+    public void deleteBuffer(VertexBuffer vb) {
+    }
+}
diff --git a/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java
new file mode 100644
index 0000000..2c8707f
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/renderer/lwjgl/LwjglRenderer.java
@@ -0,0 +1,2468 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.renderer.lwjgl;

+

+import com.jme3.light.LightList;

+import com.jme3.material.RenderState;

+import com.jme3.material.RenderState.StencilOperation;

+import com.jme3.material.RenderState.TestFunction;

+import com.jme3.math.*;

+import com.jme3.renderer.*;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Mesh.Mode;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Format;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.VertexBuffer.Usage;

+import com.jme3.shader.Attribute;

+import com.jme3.shader.Shader;

+import com.jme3.shader.Shader.ShaderSource;

+import com.jme3.shader.Shader.ShaderType;

+import com.jme3.shader.Uniform;

+import com.jme3.texture.FrameBuffer;

+import com.jme3.texture.FrameBuffer.RenderBuffer;

+import com.jme3.texture.Image;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.WrapAxis;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.IntMap;

+import com.jme3.util.IntMap.Entry;

+import com.jme3.util.ListMap;

+import com.jme3.util.NativeObjectManager;

+import com.jme3.util.SafeArrayList;

+import java.nio.*;

+import java.util.EnumSet;

+import java.util.List;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import jme3tools.converters.MipMapGenerator;

+import static org.lwjgl.opengl.ARBTextureMultisample.*;

+import static org.lwjgl.opengl.EXTFramebufferBlit.*;

+import static org.lwjgl.opengl.EXTFramebufferMultisample.*;

+import static org.lwjgl.opengl.EXTFramebufferObject.*;

+import static org.lwjgl.opengl.GL11.*;

+import static org.lwjgl.opengl.GL12.*;

+import static org.lwjgl.opengl.GL13.*;

+import static org.lwjgl.opengl.GL14.*;

+import static org.lwjgl.opengl.GL15.*;

+import static org.lwjgl.opengl.GL20.*;

+import org.lwjgl.opengl.*;

+//import static org.lwjgl.opengl.ARBDrawInstanced.*;

+

+public class LwjglRenderer implements Renderer {

+

+    private static final Logger logger = Logger.getLogger(LwjglRenderer.class.getName());

+    private static final boolean VALIDATE_SHADER = false;

+    private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250);

+    private final StringBuilder stringBuf = new StringBuilder(250);

+    private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1);

+    private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16);

+    private final RenderContext context = new RenderContext();

+    private final NativeObjectManager objManager = new NativeObjectManager();

+    private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);

+    // current state

+    private Shader boundShader;

+    private int initialDrawBuf, initialReadBuf;

+    private int glslVer;

+    private int vertexTextureUnits;

+    private int fragTextureUnits;

+    private int vertexUniforms;

+    private int fragUniforms;

+    private int vertexAttribs;

+    private int maxFBOSamples;

+    private int maxFBOAttachs;

+    private int maxMRTFBOAttachs;

+    private int maxRBSize;

+    private int maxTexSize;

+    private int maxCubeTexSize;

+    private int maxVertCount;

+    private int maxTriCount;

+    private int maxColorTexSamples;

+    private int maxDepthTexSamples;

+    private boolean tdc;

+    private FrameBuffer lastFb = null;

+    private FrameBuffer mainFbOverride = null;

+    private final Statistics statistics = new Statistics();

+    private int vpX, vpY, vpW, vpH;

+    private int clipX, clipY, clipW, clipH;

+

+    public LwjglRenderer() {

+    }

+

+    protected void updateNameBuffer() {

+        int len = stringBuf.length();

+

+        nameBuf.position(0);

+        nameBuf.limit(len);

+        for (int i = 0; i < len; i++) {

+            nameBuf.put((byte) stringBuf.charAt(i));

+        }

+

+        nameBuf.rewind();

+    }

+

+    public Statistics getStatistics() {

+        return statistics;

+    }

+

+    public EnumSet<Caps> getCaps() {

+        return caps;

+    }

+

+    @SuppressWarnings("fallthrough")

+    public void initialize() {

+        ContextCapabilities ctxCaps = GLContext.getCapabilities();

+        if (ctxCaps.OpenGL20) {

+            caps.add(Caps.OpenGL20);

+            if (ctxCaps.OpenGL21) {

+                caps.add(Caps.OpenGL21);

+                if (ctxCaps.OpenGL30) {

+                    caps.add(Caps.OpenGL30);

+                    if (ctxCaps.OpenGL31) {

+                        caps.add(Caps.OpenGL31);

+                        if (ctxCaps.OpenGL32) {

+                            caps.add(Caps.OpenGL32);

+                        }

+                    }

+                }

+            }

+        }

+

+        String versionStr = null;

+        if (ctxCaps.OpenGL20) {

+            versionStr = glGetString(GL_SHADING_LANGUAGE_VERSION);

+        }

+        if (versionStr == null || versionStr.equals("")) {

+            glslVer = -1;

+            throw new UnsupportedOperationException("GLSL and OpenGL2 is "

+                    + "required for the LWJGL "

+                    + "renderer!");

+        }

+

+        // Fix issue in TestRenderToMemory when GL_FRONT is the main

+        // buffer being used.

+        initialDrawBuf = glGetInteger(GL_DRAW_BUFFER);

+        initialReadBuf = glGetInteger(GL_READ_BUFFER);

+

+        // XXX: This has to be GL_BACK for canvas on Mac

+        // Since initialDrawBuf is GL_FRONT for pbuffer, gotta

+        // change this value later on ...

+//        initialDrawBuf = GL_BACK;

+//        initialReadBuf = GL_BACK;

+

+        int spaceIdx = versionStr.indexOf(" ");

+        if (spaceIdx >= 1) {

+            versionStr = versionStr.substring(0, spaceIdx);

+        }

+

+        float version = Float.parseFloat(versionStr);

+        glslVer = (int) (version * 100);

+

+        switch (glslVer) {

+            default:

+                if (glslVer < 400) {

+                    break;

+                }

+

+            // so that future OpenGL revisions wont break jme3

+

+            // fall through intentional

+            case 400:

+            case 330:

+            case 150:

+                caps.add(Caps.GLSL150);

+            case 140:

+                caps.add(Caps.GLSL140);

+            case 130:

+                caps.add(Caps.GLSL130);

+            case 120:

+                caps.add(Caps.GLSL120);

+            case 110:

+                caps.add(Caps.GLSL110);

+            case 100:

+                caps.add(Caps.GLSL100);

+                break;

+        }

+

+        if (!caps.contains(Caps.GLSL100)) {

+            logger.log(Level.WARNING, "Force-adding GLSL100 support, since OpenGL2 is supported.");

+            caps.add(Caps.GLSL100);

+        }

+

+        glGetInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16);

+        vertexTextureUnits = intBuf16.get(0);

+        logger.log(Level.FINER, "VTF Units: {0}", vertexTextureUnits);

+        if (vertexTextureUnits > 0) {

+            caps.add(Caps.VertexTextureFetch);

+        }

+

+        glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16);

+        fragTextureUnits = intBuf16.get(0);

+        logger.log(Level.FINER, "Texture Units: {0}", fragTextureUnits);

+

+        glGetInteger(GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);

+        vertexUniforms = intBuf16.get(0);

+        logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);

+

+        glGetInteger(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16);

+        fragUniforms = intBuf16.get(0);

+        logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms);

+

+        glGetInteger(GL_MAX_VERTEX_ATTRIBS, intBuf16);

+        vertexAttribs = intBuf16.get(0);

+        logger.log(Level.FINER, "Vertex Attributes: {0}", vertexAttribs);

+

+        glGetInteger(GL_SUBPIXEL_BITS, intBuf16);

+        int subpixelBits = intBuf16.get(0);

+        logger.log(Level.FINER, "Subpixel Bits: {0}", subpixelBits);

+

+        glGetInteger(GL_MAX_ELEMENTS_VERTICES, intBuf16);

+        maxVertCount = intBuf16.get(0);

+        logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount);

+

+        glGetInteger(GL_MAX_ELEMENTS_INDICES, intBuf16);

+        maxTriCount = intBuf16.get(0);

+        logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount);

+

+        glGetInteger(GL_MAX_TEXTURE_SIZE, intBuf16);

+        maxTexSize = intBuf16.get(0);

+        logger.log(Level.FINER, "Maximum Texture Resolution: {0}", maxTexSize);

+

+        glGetInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16);

+        maxCubeTexSize = intBuf16.get(0);

+        logger.log(Level.FINER, "Maximum CubeMap Resolution: {0}", maxCubeTexSize);

+

+        if (ctxCaps.GL_ARB_color_buffer_float) {

+            // XXX: Require both 16 and 32 bit float support for FloatColorBuffer.

+            if (ctxCaps.GL_ARB_half_float_pixel) {

+                caps.add(Caps.FloatColorBuffer);

+            }

+        }

+

+        if (ctxCaps.GL_ARB_depth_buffer_float) {

+            caps.add(Caps.FloatDepthBuffer);

+        }

+

+        if (ctxCaps.GL_ARB_draw_instanced) {

+            caps.add(Caps.MeshInstancing);

+        }

+

+        if (ctxCaps.GL_ARB_fragment_program) {

+            caps.add(Caps.ARBprogram);

+        }

+

+        if (ctxCaps.GL_ARB_texture_buffer_object) {

+            caps.add(Caps.TextureBuffer);

+        }

+

+        if (ctxCaps.GL_ARB_texture_float) {

+            if (ctxCaps.GL_ARB_half_float_pixel) {

+                caps.add(Caps.FloatTexture);

+            }

+        }

+

+        if (ctxCaps.GL_ARB_vertex_array_object) {

+            caps.add(Caps.VertexBufferArray);

+        }

+

+        if (ctxCaps.GL_ARB_texture_non_power_of_two) {

+            caps.add(Caps.NonPowerOfTwoTextures);

+        } else {

+            logger.log(Level.WARNING, "Your graphics card does not "

+                    + "support non-power-of-2 textures. "

+                    + "Some features might not work.");

+        }

+

+        boolean latc = ctxCaps.GL_EXT_texture_compression_latc;

+        boolean atdc = ctxCaps.GL_ATI_texture_compression_3dc;

+        if (latc || atdc) {

+            caps.add(Caps.TextureCompressionLATC);

+            if (atdc && !latc) {

+                tdc = true;

+            }

+        }

+

+        if (ctxCaps.GL_EXT_packed_float) {

+            caps.add(Caps.PackedFloatColorBuffer);

+            if (ctxCaps.GL_ARB_half_float_pixel) {

+                // because textures are usually uploaded as RGB16F

+                // need half-float pixel

+                caps.add(Caps.PackedFloatTexture);

+            }

+        }

+

+        if (ctxCaps.GL_EXT_texture_array) {

+            caps.add(Caps.TextureArray);

+        }

+

+        if (ctxCaps.GL_EXT_texture_shared_exponent) {

+            caps.add(Caps.SharedExponentTexture);

+        }

+

+        if (ctxCaps.GL_EXT_framebuffer_object) {

+            caps.add(Caps.FrameBuffer);

+

+            glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16);

+            maxRBSize = intBuf16.get(0);

+            logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize);

+

+            glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16);

+            maxFBOAttachs = intBuf16.get(0);

+            logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs);

+

+            if (ctxCaps.GL_EXT_framebuffer_multisample) {

+                caps.add(Caps.FrameBufferMultisample);

+

+                glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16);

+                maxFBOSamples = intBuf16.get(0);

+                logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples);

+            }

+

+            if (ctxCaps.GL_ARB_texture_multisample) {

+                caps.add(Caps.TextureMultisample);

+

+                glGetInteger(GL_MAX_COLOR_TEXTURE_SAMPLES, intBuf16);

+                maxColorTexSamples = intBuf16.get(0);

+                logger.log(Level.FINER, "Texture Multisample Color Samples: {0}", maxColorTexSamples);

+

+                glGetInteger(GL_MAX_DEPTH_TEXTURE_SAMPLES, intBuf16);

+                maxDepthTexSamples = intBuf16.get(0);

+                logger.log(Level.FINER, "Texture Multisample Depth Samples: {0}", maxDepthTexSamples);

+            }

+

+            if (ctxCaps.GL_ARB_draw_buffers) {

+                caps.add(Caps.FrameBufferMRT);

+                glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16);

+                maxMRTFBOAttachs = intBuf16.get(0);

+                logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs);

+            }

+        }

+

+        if (ctxCaps.GL_ARB_multisample) {

+            glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16);

+            boolean available = intBuf16.get(0) != 0;

+            glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16);

+            int samples = intBuf16.get(0);

+            logger.log(Level.FINER, "Samples: {0}", samples);

+            boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB);

+            if (samples > 0 && available && !enabled) {

+                glEnable(ARBMultisample.GL_MULTISAMPLE_ARB);

+            }

+        }

+

+        logger.log(Level.INFO, "Caps: {0}", caps);

+    }

+

+    public void invalidateState() {

+        context.reset();

+        boundShader = null;

+        lastFb = null;

+

+        initialDrawBuf = glGetInteger(GL_DRAW_BUFFER);

+        initialReadBuf = glGetInteger(GL_READ_BUFFER);

+    }

+

+    public void resetGLObjects() {

+        logger.log(Level.INFO, "Reseting objects and invalidating state");

+        objManager.resetObjects();

+        statistics.clearMemory();

+        invalidateState();

+    }

+

+    public void cleanup() {

+        logger.log(Level.INFO, "Deleting objects and invalidating state");

+        objManager.deleteAllObjects(this);

+        statistics.clearMemory();

+        invalidateState();

+    }

+

+    private void checkCap(Caps cap) {

+        if (!caps.contains(cap)) {

+            throw new UnsupportedOperationException("Required capability missing: " + cap.name());

+        }

+    }

+

+    /*********************************************************************\

+    |* Render State                                                      *|

+    \*********************************************************************/

+    public void setDepthRange(float start, float end) {

+        glDepthRange(start, end);

+    }

+

+    public void clearBuffers(boolean color, boolean depth, boolean stencil) {

+        int bits = 0;

+        if (color) {

+            //See explanations of the depth below, we must enable color write to be able to clear the color buffer

+            if (context.colorWriteEnabled == false) {

+                glColorMask(true, true, true, true);

+                context.colorWriteEnabled = true;

+            }

+            bits = GL_COLOR_BUFFER_BIT;

+        }

+        if (depth) {

+

+            //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false

+            //here s some link on openl board

+            //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223

+            //if depth clear is requested, we enable the depthMask

+            if (context.depthWriteEnabled == false) {

+                glDepthMask(true);

+                context.depthWriteEnabled = true;

+            }

+            bits |= GL_DEPTH_BUFFER_BIT;

+        }

+        if (stencil) {

+            bits |= GL_STENCIL_BUFFER_BIT;

+        }

+        if (bits != 0) {

+            glClear(bits);

+        }

+    }

+

+    public void setBackgroundColor(ColorRGBA color) {

+        glClearColor(color.r, color.g, color.b, color.a);

+    }

+

+    public void setAlphaToCoverage(boolean value) {

+        if (value) {

+            glEnable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);

+        } else {

+            glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);

+        }

+    }

+

+    public void applyRenderState(RenderState state) {

+        if (state.isWireframe() && !context.wireframe) {

+            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

+            context.wireframe = true;

+        } else if (!state.isWireframe() && context.wireframe) {

+            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

+            context.wireframe = false;

+        }

+

+        if (state.isDepthTest() && !context.depthTestEnabled) {

+            glEnable(GL_DEPTH_TEST);

+            glDepthFunc(GL_LEQUAL);

+            context.depthTestEnabled = true;

+        } else if (!state.isDepthTest() && context.depthTestEnabled) {

+            glDisable(GL_DEPTH_TEST);

+            context.depthTestEnabled = false;

+        }

+

+        if (state.isAlphaTest() && !context.alphaTestEnabled) {

+            glEnable(GL_ALPHA_TEST);

+            glAlphaFunc(GL_GREATER, state.getAlphaFallOff());

+            context.alphaTestEnabled = true;

+        } else if (!state.isAlphaTest() && context.alphaTestEnabled) {

+            glDisable(GL_ALPHA_TEST);

+            context.alphaTestEnabled = false;

+        }

+

+        if (state.isDepthWrite() && !context.depthWriteEnabled) {

+            glDepthMask(true);

+            context.depthWriteEnabled = true;

+        } else if (!state.isDepthWrite() && context.depthWriteEnabled) {

+            glDepthMask(false);

+            context.depthWriteEnabled = false;

+        }

+

+        if (state.isColorWrite() && !context.colorWriteEnabled) {

+            glColorMask(true, true, true, true);

+            context.colorWriteEnabled = true;

+        } else if (!state.isColorWrite() && context.colorWriteEnabled) {

+            glColorMask(false, false, false, false);

+            context.colorWriteEnabled = false;

+        }

+

+        if (state.isPointSprite() && !context.pointSprite) {

+            // Only enable/disable sprite

+            if (context.boundTextures[0] != null){

+                if (context.boundTextureUnit != 0){

+                    glActiveTexture(GL_TEXTURE0);

+                    context.boundTextureUnit = 0;

+                }

+                glEnable(GL_POINT_SPRITE);

+                glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);

+            }

+            context.pointSprite = true;

+        } else if (!state.isPointSprite() && context.pointSprite) {

+            if (context.boundTextures[0] != null){

+                if (context.boundTextureUnit != 0){

+                    glActiveTexture(GL_TEXTURE0);

+                    context.boundTextureUnit = 0;

+                }

+                glDisable(GL_POINT_SPRITE);

+                glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);

+                context.pointSprite = false;

+            }

+        }

+

+        if (state.isPolyOffset()) {

+            if (!context.polyOffsetEnabled) {

+                glEnable(GL_POLYGON_OFFSET_FILL);

+                glPolygonOffset(state.getPolyOffsetFactor(),

+                        state.getPolyOffsetUnits());

+                context.polyOffsetEnabled = true;

+                context.polyOffsetFactor = state.getPolyOffsetFactor();

+                context.polyOffsetUnits = state.getPolyOffsetUnits();

+            } else {

+                if (state.getPolyOffsetFactor() != context.polyOffsetFactor

+                        || state.getPolyOffsetUnits() != context.polyOffsetUnits) {

+                    glPolygonOffset(state.getPolyOffsetFactor(),

+                            state.getPolyOffsetUnits());

+                    context.polyOffsetFactor = state.getPolyOffsetFactor();

+                    context.polyOffsetUnits = state.getPolyOffsetUnits();

+                }

+            }

+        } else {

+            if (context.polyOffsetEnabled) {

+                glDisable(GL_POLYGON_OFFSET_FILL);

+                context.polyOffsetEnabled = false;

+                context.polyOffsetFactor = 0;

+                context.polyOffsetUnits = 0;

+            }

+        }

+

+        if (state.getFaceCullMode() != context.cullMode) {

+            if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) {

+                glDisable(GL_CULL_FACE);

+            } else {

+                glEnable(GL_CULL_FACE);

+            }

+

+            switch (state.getFaceCullMode()) {

+                case Off:

+                    break;

+                case Back:

+                    glCullFace(GL_BACK);

+                    break;

+                case Front:

+                    glCullFace(GL_FRONT);

+                    break;

+                case FrontAndBack:

+                    glCullFace(GL_FRONT_AND_BACK);

+                    break;

+                default:

+                    throw new UnsupportedOperationException("Unrecognized face cull mode: "

+                            + state.getFaceCullMode());

+            }

+

+            context.cullMode = state.getFaceCullMode();

+        }

+

+        if (state.getBlendMode() != context.blendMode) {

+            if (state.getBlendMode() == RenderState.BlendMode.Off) {

+                glDisable(GL_BLEND);

+            } else {

+                glEnable(GL_BLEND);

+                switch (state.getBlendMode()) {

+                    case Off:

+                        break;

+                    case Additive:

+                        glBlendFunc(GL_ONE, GL_ONE);

+                        break;

+                    case AlphaAdditive:

+                        glBlendFunc(GL_SRC_ALPHA, GL_ONE);

+                        break;

+                    case Color:

+                        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);

+                        break;

+                    case Alpha:

+                        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

+                        break;

+                    case PremultAlpha:

+                        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

+                        break;

+                    case Modulate:

+                        glBlendFunc(GL_DST_COLOR, GL_ZERO);

+                        break;

+                    case ModulateX2:

+                        glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);

+                        break;

+                    default:

+                        throw new UnsupportedOperationException("Unrecognized blend mode: "

+                                + state.getBlendMode());

+                }

+            }

+

+            context.blendMode = state.getBlendMode();

+        }

+

+        if (context.stencilTest != state.isStencilTest()

+                || context.frontStencilStencilFailOperation != state.getFrontStencilStencilFailOperation()

+                || context.frontStencilDepthFailOperation != state.getFrontStencilDepthFailOperation()

+                || context.frontStencilDepthPassOperation != state.getFrontStencilDepthPassOperation()

+                || context.backStencilStencilFailOperation != state.getBackStencilStencilFailOperation()

+                || context.backStencilDepthFailOperation != state.getBackStencilDepthFailOperation()

+                || context.backStencilDepthPassOperation != state.getBackStencilDepthPassOperation()

+                || context.frontStencilFunction != state.getFrontStencilFunction()

+                || context.backStencilFunction != state.getBackStencilFunction()) {

+

+            context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation();   //terrible looking, I know

+            context.frontStencilDepthFailOperation = state.getFrontStencilDepthFailOperation();

+            context.frontStencilDepthPassOperation = state.getFrontStencilDepthPassOperation();

+            context.backStencilStencilFailOperation = state.getBackStencilStencilFailOperation();

+            context.backStencilDepthFailOperation = state.getBackStencilDepthFailOperation();

+            context.backStencilDepthPassOperation = state.getBackStencilDepthPassOperation();

+            context.frontStencilFunction = state.getFrontStencilFunction();

+            context.backStencilFunction = state.getBackStencilFunction();

+

+            if (state.isStencilTest()) {

+                glEnable(GL_STENCIL_TEST);

+                glStencilOpSeparate(GL_FRONT,

+                        convertStencilOperation(state.getFrontStencilStencilFailOperation()),

+                        convertStencilOperation(state.getFrontStencilDepthFailOperation()),

+                        convertStencilOperation(state.getFrontStencilDepthPassOperation()));

+                glStencilOpSeparate(GL_BACK,

+                        convertStencilOperation(state.getBackStencilStencilFailOperation()),

+                        convertStencilOperation(state.getBackStencilDepthFailOperation()),

+                        convertStencilOperation(state.getBackStencilDepthPassOperation()));

+                glStencilFuncSeparate(GL_FRONT,

+                        convertTestFunction(state.getFrontStencilFunction()),

+                        0, Integer.MAX_VALUE);

+                glStencilFuncSeparate(GL_BACK,

+                        convertTestFunction(state.getBackStencilFunction()),

+                        0, Integer.MAX_VALUE);

+            } else {

+                glDisable(GL_STENCIL_TEST);

+            }

+        }

+    }

+

+    private int convertStencilOperation(StencilOperation stencilOp) {

+        switch (stencilOp) {

+            case Keep:

+                return GL_KEEP;

+            case Zero:

+                return GL_ZERO;

+            case Replace:

+                return GL_REPLACE;

+            case Increment:

+                return GL_INCR;

+            case IncrementWrap:

+                return GL_INCR_WRAP;

+            case Decrement:

+                return GL_DECR;

+            case DecrementWrap:

+                return GL_DECR_WRAP;

+            case Invert:

+                return GL_INVERT;

+            default:

+                throw new UnsupportedOperationException("Unrecognized stencil operation: " + stencilOp);

+        }

+    }

+

+    private int convertTestFunction(TestFunction testFunc) {

+        switch (testFunc) {

+            case Never:

+                return GL_NEVER;

+            case Less:

+                return GL_LESS;

+            case LessOrEqual:

+                return GL_LEQUAL;

+            case Greater:

+                return GL_GREATER;

+            case GreaterOrEqual:

+                return GL_GEQUAL;

+            case Equal:

+                return GL_EQUAL;

+            case NotEqual:

+                return GL_NOTEQUAL;

+            case Always:

+                return GL_ALWAYS;

+            default:

+                throw new UnsupportedOperationException("Unrecognized test function: " + testFunc);

+        }

+    }

+

+    /*********************************************************************\

+    |* Camera and World transforms                                       *|

+    \*********************************************************************/

+    public void setViewPort(int x, int y, int w, int h) {

+        if (x != vpX || vpY != y || vpW != w || vpH != h) {

+            glViewport(x, y, w, h);

+            vpX = x;

+            vpY = y;

+            vpW = w;

+            vpH = h;

+        }

+    }

+

+    public void setClipRect(int x, int y, int width, int height) {

+        if (!context.clipRectEnabled) {

+            glEnable(GL_SCISSOR_TEST);

+            context.clipRectEnabled = true;

+        }

+        if (clipX != x || clipY != y || clipW != width || clipH != height) {

+            glScissor(x, y, width, height);

+            clipX = x;

+            clipY = y;

+            clipW = width;

+            clipH = height;

+        }

+    }

+

+    public void clearClipRect() {

+        if (context.clipRectEnabled) {

+            glDisable(GL_SCISSOR_TEST);

+            context.clipRectEnabled = false;

+

+            clipX = 0;

+            clipY = 0;

+            clipW = 0;

+            clipH = 0;

+        }

+    }

+

+    public void onFrame() {

+        objManager.deleteUnused(this);

+//        statistics.clearFrame();

+    }

+

+    public void setWorldMatrix(Matrix4f worldMatrix) {

+    }

+

+    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {

+    }

+

+    /*********************************************************************\

+    |* Shaders                                                           *|

+    \*********************************************************************/

+    protected void updateUniformLocation(Shader shader, Uniform uniform) {

+        stringBuf.setLength(0);

+        stringBuf.append(uniform.getName()).append('\0');

+        updateNameBuffer();

+        int loc = glGetUniformLocation(shader.getId(), nameBuf);

+        if (loc < 0) {

+            uniform.setLocation(-1);

+            // uniform is not declared in shader

+            logger.log(Level.INFO, "Uniform {0} is not declared in shader {1}.", new Object[]{uniform.getName(), shader.getSources()});

+        } else {

+            uniform.setLocation(loc);

+        }

+    }

+

+    protected void bindProgram(Shader shader){

+        int shaderId = shader.getId();

+        if (context.boundShaderProgram != shaderId) {

+            glUseProgram(shaderId);

+            statistics.onShaderUse(shader, true);

+            boundShader = shader;

+            context.boundShaderProgram = shaderId;

+        } else {

+            statistics.onShaderUse(shader, false);

+        }

+    }

+    

+    protected void updateUniform(Shader shader, Uniform uniform) {

+        int shaderId = shader.getId();

+

+        assert uniform.getName() != null;

+        assert shader.getId() > 0;

+

+        bindProgram(shader);

+

+        int loc = uniform.getLocation();

+        if (loc == -1) {

+            return;

+        }

+

+        if (loc == -2) {

+            // get uniform location

+            updateUniformLocation(shader, uniform);

+            if (uniform.getLocation() == -1) {

+                // not declared, ignore

+                uniform.clearUpdateNeeded();

+                return;

+            }

+            loc = uniform.getLocation();

+        }

+

+        if (uniform.getVarType() == null) {

+            return; // value not set yet..

+        }

+        statistics.onUniformSet();

+

+        uniform.clearUpdateNeeded();

+        FloatBuffer fb;

+        switch (uniform.getVarType()) {

+            case Float:

+                Float f = (Float) uniform.getValue();

+                glUniform1f(loc, f.floatValue());

+                break;

+            case Vector2:

+                Vector2f v2 = (Vector2f) uniform.getValue();

+                glUniform2f(loc, v2.getX(), v2.getY());

+                break;

+            case Vector3:

+                Vector3f v3 = (Vector3f) uniform.getValue();

+                glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ());

+                break;

+            case Vector4:

+                Object val = uniform.getValue();

+                if (val instanceof ColorRGBA) {

+                    ColorRGBA c = (ColorRGBA) val;

+                    glUniform4f(loc, c.r, c.g, c.b, c.a);

+                } else if (val instanceof Vector4f) {

+                    Vector4f c = (Vector4f) val;

+                    glUniform4f(loc, c.x, c.y, c.z, c.w);

+                } else {

+                    Quaternion c = (Quaternion) uniform.getValue();

+                    glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW());

+                }

+                break;

+            case Boolean:

+                Boolean b = (Boolean) uniform.getValue();

+                glUniform1i(loc, b.booleanValue() ? GL_TRUE : GL_FALSE);

+                break;

+            case Matrix3:

+                fb = (FloatBuffer) uniform.getValue();

+                assert fb.remaining() == 9;

+                glUniformMatrix3(loc, false, fb);

+                break;

+            case Matrix4:

+                fb = (FloatBuffer) uniform.getValue();

+                assert fb.remaining() == 16;

+                glUniformMatrix4(loc, false, fb);

+                break;

+            case FloatArray:

+                fb = (FloatBuffer) uniform.getValue();

+                glUniform1(loc, fb);

+                break;

+            case Vector2Array:

+                fb = (FloatBuffer) uniform.getValue();

+                glUniform2(loc, fb);

+                break;

+            case Vector3Array:

+                fb = (FloatBuffer) uniform.getValue();

+                glUniform3(loc, fb);

+                break;

+            case Vector4Array:

+                fb = (FloatBuffer) uniform.getValue();

+                glUniform4(loc, fb);

+                break;

+            case Matrix4Array:

+                fb = (FloatBuffer) uniform.getValue();

+                glUniformMatrix4(loc, false, fb);

+                break;

+            case Int:

+                Integer i = (Integer) uniform.getValue();

+                glUniform1i(loc, i.intValue());

+                break;

+            default:

+                throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType());

+        }

+    }

+

+    protected void updateShaderUniforms(Shader shader) {

+        ListMap<String, Uniform> uniforms = shader.getUniformMap();

+//        for (Uniform uniform : shader.getUniforms()){

+        for (int i = 0; i < uniforms.size(); i++) {

+            Uniform uniform = uniforms.getValue(i);

+            if (uniform.isUpdateNeeded()) {

+                updateUniform(shader, uniform);

+            }

+        }

+    }

+

+    protected void resetUniformLocations(Shader shader) {

+        ListMap<String, Uniform> uniforms = shader.getUniformMap();

+//        for (Uniform uniform : shader.getUniforms()){

+        for (int i = 0; i < uniforms.size(); i++) {

+            Uniform uniform = uniforms.getValue(i);

+            uniform.reset(); // e.g check location again

+        }

+    }

+

+    /*

+     * (Non-javadoc)

+     * Only used for fixed-function. Ignored.

+     */

+    public void setLighting(LightList list) {

+    }

+

+    public int convertShaderType(ShaderType type) {

+        switch (type) {

+            case Fragment:

+                return GL_FRAGMENT_SHADER;

+            case Vertex:

+                return GL_VERTEX_SHADER;

+//            case Geometry:

+//                return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB;

+            default:

+                throw new UnsupportedOperationException("Unrecognized shader type.");

+        }

+    }

+

+    public void updateShaderSourceData(ShaderSource source, String language) {

+        int id = source.getId();

+        if (id == -1) {

+            // create id

+            id = glCreateShader(convertShaderType(source.getType()));

+            if (id <= 0) {

+                throw new RendererException("Invalid ID received when trying to create shader.");

+            }

+

+            source.setId(id);

+        }else{

+            throw new RendererException("Cannot recompile shader source");

+        }

+

+        // upload shader source

+        // merge the defines and source code

+

+        stringBuf.setLength(0);

+        if (language.startsWith("GLSL")) {

+            int version = Integer.parseInt(language.substring(4));

+            if (version > 100) {

+                stringBuf.append("#version ");

+                stringBuf.append(language.substring(4));

+                if (version >= 150) {

+                    stringBuf.append(" core");

+                }

+                stringBuf.append("\n");

+            }

+        }

+        updateNameBuffer();

+

+        byte[] definesCodeData = source.getDefines().getBytes();

+        byte[] sourceCodeData = source.getSource().getBytes();

+        ByteBuffer codeBuf = BufferUtils.createByteBuffer(nameBuf.limit()

+                + definesCodeData.length

+                + sourceCodeData.length);

+        codeBuf.put(nameBuf);

+        codeBuf.put(definesCodeData);

+        codeBuf.put(sourceCodeData);

+        codeBuf.flip();

+

+        glShaderSource(id, codeBuf);

+        glCompileShader(id);

+

+        glGetShader(id, GL_COMPILE_STATUS, intBuf1);

+

+        boolean compiledOK = intBuf1.get(0) == GL_TRUE;

+        String infoLog = null;

+

+        if (VALIDATE_SHADER || !compiledOK) {

+            // even if compile succeeded, check

+            // log for warnings

+            glGetShader(id, GL_INFO_LOG_LENGTH, intBuf1);

+            int length = intBuf1.get(0);

+            if (length > 3) {

+                // get infos

+                ByteBuffer logBuf = BufferUtils.createByteBuffer(length);

+                glGetShaderInfoLog(id, null, logBuf);

+                byte[] logBytes = new byte[length];

+                logBuf.get(logBytes, 0, length);

+                // convert to string, etc

+                infoLog = new String(logBytes);

+            }

+        }

+

+        if (compiledOK) {

+            if (infoLog != null) {

+                logger.log(Level.INFO, "{0} compile success\n{1}",

+                        new Object[]{source.getName(), infoLog});

+            } else {

+                logger.log(Level.FINE, "{0} compile success", source.getName());

+            }

+        } else {

+            logger.log(Level.WARNING, "Bad compile of:\n{0}{1}",

+                    new Object[]{source.getDefines(), source.getSource()});

+            if (infoLog != null) {

+                throw new RendererException("compile error in:" + source + " error:" + infoLog);

+            } else {

+                throw new RendererException("compile error in:" + source + " error: <not provided>");

+            }

+        }

+

+        source.clearUpdateNeeded();

+        // only usable if compiled

+        source.setUsable(compiledOK);

+        if (!compiledOK) {

+            // make sure to dispose id cause all program's

+            // shaders will be cleared later.

+            glDeleteShader(id);

+        } else {

+            // register for cleanup since the ID is usable

+            // NOTE: From now on cleanup is handled

+            // by the parent shader object so no need

+            // to register.

+            //objManager.registerForCleanup(source);

+        }

+    }

+

+    public void updateShaderData(Shader shader) {

+        int id = shader.getId();

+        boolean needRegister = false;

+        if (id == -1) {

+            // create program

+            id = glCreateProgram();

+            if (id == 0) {

+                throw new RendererException("Invalid ID (" + id + ") received when trying to create shader program.");

+            }

+

+            shader.setId(id);

+            needRegister = true;

+        }

+

+        for (ShaderSource source : shader.getSources()) {

+            if (source.isUpdateNeeded()) {

+                updateShaderSourceData(source, shader.getLanguage());

+                // shader has been compiled here

+            }

+

+            if (!source.isUsable()) {

+                // it's useless.. just forget about everything..

+                shader.setUsable(false);

+                shader.clearUpdateNeeded();

+                return;

+            }

+            glAttachShader(id, source.getId());

+        }

+

+        if (caps.contains(Caps.OpenGL30)) {

+            // Check if GLSL version is 1.5 for shader

+            GL30.glBindFragDataLocation(id, 0, "outFragColor");

+        }

+

+        // link shaders to program

+        glLinkProgram(id);

+        glGetProgram(id, GL_LINK_STATUS, intBuf1);

+        boolean linkOK = intBuf1.get(0) == GL_TRUE;

+        String infoLog = null;

+

+        if (VALIDATE_SHADER || !linkOK) {

+            glGetProgram(id, GL_INFO_LOG_LENGTH, intBuf1);

+            int length = intBuf1.get(0);

+            if (length > 3) {

+                // get infos

+                ByteBuffer logBuf = BufferUtils.createByteBuffer(length);

+                glGetProgramInfoLog(id, null, logBuf);

+

+                // convert to string, etc

+                byte[] logBytes = new byte[length];

+                logBuf.get(logBytes, 0, length);

+                infoLog = new String(logBytes);

+            }

+        }

+

+        if (linkOK) {

+            if (infoLog != null) {

+                logger.log(Level.INFO, "shader link success. \n{0}", infoLog);

+            } else {

+                logger.fine("shader link success");

+            }

+        } else {

+            if (infoLog != null) {

+                throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog);

+            } else {

+                throw new RendererException("Shader link failure, shader:" + shader + " info: <not provided>");

+            }

+        }

+

+        shader.clearUpdateNeeded();

+        if (!linkOK) {

+            // failure.. forget about everything

+            shader.resetSources();

+            shader.setUsable(false);

+            deleteShader(shader);

+        } else {

+            shader.setUsable(true);

+            if (needRegister) {

+                objManager.registerForCleanup(shader);

+                statistics.onNewShader();

+            } else {

+                // OpenGL spec: uniform locations may change after re-link

+                resetUniformLocations(shader);

+            }

+        }

+    }

+

+    public void setShader(Shader shader) {

+        if (shader == null) {

+            throw new IllegalArgumentException("shader cannot be null");

+//            if (context.boundShaderProgram > 0) {

+//                glUseProgram(0);

+//                statistics.onShaderUse(null, true);

+//                context.boundShaderProgram = 0;

+//                boundShader = null;

+//            }

+        } else {

+            if (shader.isUpdateNeeded()) {

+                updateShaderData(shader);

+            }

+

+            // NOTE: might want to check if any of the

+            // sources need an update?

+

+            if (!shader.isUsable()) {

+                return;

+            }

+

+            assert shader.getId() > 0;

+

+            updateShaderUniforms(shader);

+            bindProgram(shader);

+        }

+    }

+

+    public void deleteShaderSource(ShaderSource source) {

+        if (source.getId() < 0) {

+            logger.warning("Shader source is not uploaded to GPU, cannot delete.");

+            return;

+        }

+        source.setUsable(false);

+        source.clearUpdateNeeded();

+        glDeleteShader(source.getId());

+        source.resetObject();

+    }

+

+    public void deleteShader(Shader shader) {

+        if (shader.getId() == -1) {

+            logger.warning("Shader is not uploaded to GPU, cannot delete.");

+            return;

+        }

+

+        for (ShaderSource source : shader.getSources()) {

+            if (source.getId() != -1) {

+                glDetachShader(shader.getId(), source.getId());

+                deleteShaderSource(source);

+            }

+        }

+

+        // kill all references so sources can be collected

+        // if needed.

+        shader.resetSources();

+        glDeleteProgram(shader.getId());

+

+        statistics.onDeleteShader();

+    }

+

+    /*********************************************************************\

+    |* Framebuffers                                                      *|

+    \*********************************************************************/

+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {

+        copyFrameBuffer(src, dst, true);

+    }

+

+    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {

+        if (GLContext.getCapabilities().GL_EXT_framebuffer_blit) {

+            int srcW = 0;

+            int srcH = 0;

+            int dstW = 0;

+            int dstH = 0;

+            int prevFBO = context.boundFBO;

+

+            if (src != null && src.isUpdateNeeded()) {

+                updateFrameBuffer(src);

+            }

+

+            if (dst != null && dst.isUpdateNeeded()) {

+                updateFrameBuffer(dst);

+            }

+

+            if (src == null) {

+                glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);

+//                srcW = viewWidth;

+//                srcH = viewHeight;

+            } else {

+                glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId());

+                srcW = src.getWidth();

+                srcH = src.getHeight();

+            }

+            if (dst == null) {

+                glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);

+//                dstW = viewWidth;

+//                dstH = viewHeight;

+            } else {

+                glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId());

+                dstW = dst.getWidth();

+                dstH = dst.getHeight();

+            }

+            int mask = GL_COLOR_BUFFER_BIT;

+            if (copyDepth) {

+                mask |= GL_DEPTH_BUFFER_BIT;

+            }

+            glBlitFramebufferEXT(0, 0, srcW, srcH,

+                    0, 0, dstW, dstH, mask,

+                    GL_NEAREST);

+

+

+            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO);

+            try {

+                checkFrameBufferError();

+            } catch (IllegalStateException ex) {

+                logger.log(Level.SEVERE, "Source FBO:\n{0}", src);

+                logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst);

+                throw ex;

+            }

+        } else {

+            throw new RendererException("EXT_framebuffer_blit required.");

+            // TODO: support non-blit copies?

+        }

+    }

+    

+    private String getTargetBufferName(int buffer){

+        switch (buffer){

+            case GL_NONE: return "NONE";

+            case GL_FRONT: return "GL_FRONT";

+            case GL_BACK: return "GL_BACK";

+            default:

+                if ( buffer >= GL_COLOR_ATTACHMENT0_EXT

+                  && buffer <= GL_COLOR_ATTACHMENT15_EXT){

+                    return "GL_COLOR_ATTACHMENT" + 

+                                (buffer - GL_COLOR_ATTACHMENT0_EXT);

+                }else{

+                    return "UNKNOWN? " + buffer;

+                }

+        }

+    }

+

+    private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name){

+        System.out.println("== Renderbuffer " + name + " ==");

+        System.out.println("RB ID: " + rb.getId());

+        System.out.println("Is proper? " + glIsRenderbufferEXT(rb.getId()));

+        

+        int attachment = convertAttachmentSlot(rb.getSlot());

+        

+        int type = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,

+                                                          attachment,

+                                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT);

+        

+        int rbName = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,

+                                                            attachment,

+                                                            GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT);

+        

+        switch (type){

+            case GL_NONE:

+                System.out.println("Type: None");

+                return; // note: return from method as other queries will be invalid

+            case GL_TEXTURE:

+                System.out.println("Type: Texture");

+                break;

+            case GL_RENDERBUFFER_EXT:

+                System.out.println("Type: Buffer");

+                System.out.println("RB ID: " + rbName);

+                break;

+        }

+        

+        

+        

+    }

+    

+    private void printRealFrameBufferInfo(FrameBuffer fb) {

+        boolean doubleBuffer = glGetBoolean(GL_DOUBLEBUFFER);

+        String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER));

+        String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER));

+        

+        int fbId = fb.getId();

+        int curDrawBinding = glGetInteger(ARBFramebufferObject.GL_DRAW_FRAMEBUFFER_BINDING);

+        int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING);

+    

+        System.out.println("=== OpenGL FBO State ===");

+        System.out.println("Context doublebuffered? " + doubleBuffer);

+        System.out.println("FBO ID: " + fbId);

+        System.out.println("Is proper? " + glIsFramebufferEXT(fbId));

+        System.out.println("Is bound to draw? " + (fbId == curDrawBinding));

+        System.out.println("Is bound to read? " + (fbId == curReadBinding));

+        System.out.println("Draw buffer: " + drawBuf);

+        System.out.println("Read buffer: " + readBuf);

+        

+        if (context.boundFBO != fbId){

+            glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbId);

+            context.boundFBO = fbId;

+        }

+        

+        if (fb.getDepthBuffer() != null){

+            printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth");

+        }

+        for (int i = 0; i < fb.getNumColorBuffers(); i++){

+            printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i);

+        }

+    }

+    

+    private void checkFrameBufferError() {

+        int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

+        switch (status) {

+            case GL_FRAMEBUFFER_COMPLETE_EXT:

+                break;

+            case GL_FRAMEBUFFER_UNSUPPORTED_EXT:

+                //Choose different formats

+                throw new IllegalStateException("Framebuffer object format is "

+                        + "unsupported by the video hardware.");

+            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:

+                throw new IllegalStateException("Framebuffer has erronous attachment.");

+            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:

+                throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached.");

+            case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:

+                throw new IllegalStateException("Framebuffer attachments must have same dimensions.");

+            case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:

+                throw new IllegalStateException("Framebuffer attachments must have same formats.");

+            case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:

+                throw new IllegalStateException("Incomplete draw buffer.");

+            case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:

+                throw new IllegalStateException("Incomplete read buffer.");

+            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:

+                throw new IllegalStateException("Incomplete multisample buffer.");

+            default:

+                //Programming error; will fail on all hardware

+                throw new IllegalStateException("Some video driver error "

+                        + "or programming error occured. "

+                        + "Framebuffer object status is invalid. ");

+        }

+    }

+

+    private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) {

+        int id = rb.getId();

+        if (id == -1) {

+            glGenRenderbuffersEXT(intBuf1);

+            id = intBuf1.get(0);

+            rb.setId(id);

+        }

+

+        if (context.boundRB != id) {

+            glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id);

+            context.boundRB = id;

+        }

+

+        if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) {

+            throw new RendererException("Resolution " + fb.getWidth()

+                    + ":" + fb.getHeight() + " is not supported.");

+        }

+

+        TextureUtil.checkFormatSupported(rb.getFormat());

+

+        if (fb.getSamples() > 1 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample) {

+            int samples = fb.getSamples();

+            if (maxFBOSamples < samples) {

+                samples = maxFBOSamples;

+            }

+            glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT,

+                    samples,

+                    TextureUtil.convertTextureFormat(rb.getFormat()),

+                    fb.getWidth(),

+                    fb.getHeight());

+        } else {

+            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,

+                    TextureUtil.convertTextureFormat(rb.getFormat()),

+                    fb.getWidth(),

+                    fb.getHeight());

+        }

+    }

+

+    private int convertAttachmentSlot(int attachmentSlot) {

+        // can also add support for stencil here

+        if (attachmentSlot == -100) {

+            return GL_DEPTH_ATTACHMENT_EXT;

+        } else if (attachmentSlot < 0 || attachmentSlot >= 16) {

+            throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot);

+        }

+

+        return GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;

+    }

+

+    public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) {

+        Texture tex = rb.getTexture();

+        Image image = tex.getImage();

+        if (image.isUpdateNeeded()) {

+            updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels(), 0);

+

+            // NOTE: For depth textures, sets nearest/no-mips mode

+            // Required to fix "framebuffer unsupported"

+            // for old NVIDIA drivers!

+            setupTextureParams(tex);

+        }

+

+        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,

+                convertAttachmentSlot(rb.getSlot()),

+                convertTextureType(tex.getType(), image.getMultiSamples()),

+                image.getId(),

+                0);

+    }

+

+    public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) {

+        boolean needAttach;

+        if (rb.getTexture() == null) {

+            // if it hasn't been created yet, then attach is required.

+            needAttach = rb.getId() == -1;

+            updateRenderBuffer(fb, rb);

+        } else {

+            needAttach = false;

+            updateRenderTexture(fb, rb);

+        }

+        if (needAttach) {

+            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,

+                    convertAttachmentSlot(rb.getSlot()),

+                    GL_RENDERBUFFER_EXT,

+                    rb.getId());

+        }

+    }

+

+    public void updateFrameBuffer(FrameBuffer fb) {

+        int id = fb.getId();

+        if (id == -1) {

+            // create FBO

+            glGenFramebuffersEXT(intBuf1);

+            id = intBuf1.get(0);

+            fb.setId(id);

+            objManager.registerForCleanup(fb);

+

+            statistics.onNewFrameBuffer();

+        }

+

+        if (context.boundFBO != id) {

+            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);

+            // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0

+            context.boundDrawBuf = 0;

+            context.boundFBO = id;

+        }

+

+        FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();

+        if (depthBuf != null) {

+            updateFrameBufferAttachment(fb, depthBuf);

+        }

+

+        for (int i = 0; i < fb.getNumColorBuffers(); i++) {

+            FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);

+            updateFrameBufferAttachment(fb, colorBuf);

+        }

+

+        fb.clearUpdateNeeded();

+    }

+

+    public Vector2f[] getFrameBufferSamplePositions(FrameBuffer fb) {

+        if (fb.getSamples() <= 1) {

+            throw new IllegalArgumentException("Framebuffer must be multisampled");

+        }

+

+        setFrameBuffer(fb);

+

+        Vector2f[] samplePositions = new Vector2f[fb.getSamples()];

+        FloatBuffer samplePos = BufferUtils.createFloatBuffer(2);

+        for (int i = 0; i < samplePositions.length; i++) {

+            glGetMultisample(GL_SAMPLE_POSITION, i, samplePos);

+            samplePos.clear();

+            samplePositions[i] = new Vector2f(samplePos.get(0) - 0.5f,

+                    samplePos.get(1) - 0.5f);

+        }

+        return samplePositions;

+    }

+    

+    public void setMainFrameBufferOverride(FrameBuffer fb){

+        mainFbOverride = fb;

+    }

+

+    public void setFrameBuffer(FrameBuffer fb) {

+        if (fb == null && mainFbOverride != null){

+            fb = mainFbOverride;

+        }

+        

+        if (lastFb == fb) {

+            if (fb == null || !fb.isUpdateNeeded()){

+                return;

+            }

+        }

+

+        // generate mipmaps for last FB if needed

+        if (lastFb != null) {

+            for (int i = 0; i < lastFb.getNumColorBuffers(); i++) {

+                RenderBuffer rb = lastFb.getColorBuffer(i);

+                Texture tex = rb.getTexture();

+                if (tex != null

+                        && tex.getMinFilter().usesMipMapLevels()) {

+                    setTexture(0, rb.getTexture());

+

+                    int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples());

+                    glEnable(textureType);

+                    glGenerateMipmapEXT(textureType);

+                    glDisable(textureType);

+                }

+            }

+        }

+

+        if (fb == null) {

+            // unbind any fbos

+            if (context.boundFBO != 0) {

+                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

+                statistics.onFrameBufferUse(null, true);

+

+                context.boundFBO = 0;

+            }

+            // select back buffer

+            if (context.boundDrawBuf != -1) {

+                glDrawBuffer(initialDrawBuf);

+                context.boundDrawBuf = -1;

+            }

+            if (context.boundReadBuf != -1) {

+                glReadBuffer(initialReadBuf);

+                context.boundReadBuf = -1;

+            }

+

+            lastFb = null;

+        } else {

+            if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null){

+                throw new IllegalArgumentException("The framebuffer: " + fb + 

+                                                   "\nDoesn't have any color/depth buffers");

+            }

+            

+            if (fb.isUpdateNeeded()) {

+                updateFrameBuffer(fb);

+            }

+

+            if (context.boundFBO != fb.getId()) {

+                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId());

+                statistics.onFrameBufferUse(fb, true);

+

+                // update viewport to reflect framebuffer's resolution

+                setViewPort(0, 0, fb.getWidth(), fb.getHeight());

+

+                context.boundFBO = fb.getId();

+            } else {

+                statistics.onFrameBufferUse(fb, false);

+            }

+            if (fb.getNumColorBuffers() == 0) {

+                // make sure to select NONE as draw buf

+                // no color buffer attached. select NONE

+                if (context.boundDrawBuf != -2) {

+                    glDrawBuffer(GL_NONE);

+                    context.boundDrawBuf = -2;

+                }

+                if (context.boundReadBuf != -2) {

+                    glReadBuffer(GL_NONE);

+                    context.boundReadBuf = -2;

+                }

+            } else {

+                if (fb.isMultiTarget()) {

+                    if (fb.getNumColorBuffers() > maxMRTFBOAttachs) {

+                        throw new RendererException("Framebuffer has more"

+                                + " targets than are supported"

+                                + " on the system!");

+                    }

+

+                    if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) {

+                        intBuf16.clear();

+                        for (int i = 0; i < fb.getNumColorBuffers(); i++) {

+                            intBuf16.put(GL_COLOR_ATTACHMENT0_EXT + i);

+                        }

+

+                        intBuf16.flip();

+                        glDrawBuffers(intBuf16);

+                        context.boundDrawBuf = 100 + fb.getNumColorBuffers();

+                    }

+                } else {

+                    RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex());

+                    // select this draw buffer

+                    if (context.boundDrawBuf != rb.getSlot()) {

+                        glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());

+                        context.boundDrawBuf = rb.getSlot();

+                    }

+                }

+            }

+

+            assert fb.getId() >= 0;

+            assert context.boundFBO == fb.getId();

+            

+            lastFb = fb;

+            

+            try {

+                checkFrameBufferError();

+            } catch (IllegalStateException ex) {

+                logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb);

+                printRealFrameBufferInfo(fb);

+                throw ex;

+            }

+        }

+    }

+

+    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {

+        if (fb != null) {

+            RenderBuffer rb = fb.getColorBuffer();

+            if (rb == null) {

+                throw new IllegalArgumentException("Specified framebuffer"

+                        + " does not have a colorbuffer");

+            }

+

+            setFrameBuffer(fb);

+            if (context.boundReadBuf != rb.getSlot()) {

+                glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());

+                context.boundReadBuf = rb.getSlot();

+            }

+        } else {

+            setFrameBuffer(null);

+        }

+

+        glReadPixels(vpX, vpY, vpW, vpH, /*GL_RGBA*/ GL_BGRA, GL_UNSIGNED_BYTE, byteBuf);

+    }

+

+    private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {

+        intBuf1.put(0, rb.getId());

+        glDeleteRenderbuffersEXT(intBuf1);

+    }

+

+    public void deleteFrameBuffer(FrameBuffer fb) {

+        if (fb.getId() != -1) {

+            if (context.boundFBO == fb.getId()) {

+                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

+                context.boundFBO = 0;

+            }

+

+            if (fb.getDepthBuffer() != null) {

+                deleteRenderBuffer(fb, fb.getDepthBuffer());

+            }

+            if (fb.getColorBuffer() != null) {

+                deleteRenderBuffer(fb, fb.getColorBuffer());

+            }

+

+            intBuf1.put(0, fb.getId());

+            glDeleteFramebuffersEXT(intBuf1);

+            fb.resetObject();

+

+            statistics.onDeleteFrameBuffer();

+        }

+    }

+

+    /*********************************************************************\

+    |* Textures                                                          *|

+    \*********************************************************************/

+    private int convertTextureType(Texture.Type type, int samples) {

+        switch (type) {

+            case TwoDimensional:

+                if (samples > 1) {

+                    return ARBTextureMultisample.GL_TEXTURE_2D_MULTISAMPLE;

+                } else {

+                    return GL_TEXTURE_2D;

+                }

+            case TwoDimensionalArray:

+                if (samples > 1) {

+                    return ARBTextureMultisample.GL_TEXTURE_2D_MULTISAMPLE_ARRAY;

+                } else {

+                    return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT;

+                }

+            case ThreeDimensional:

+                return GL_TEXTURE_3D;

+            case CubeMap:

+                return GL_TEXTURE_CUBE_MAP;

+            default:

+                throw new UnsupportedOperationException("Unknown texture type: " + type);

+        }

+    }

+

+    private int convertMagFilter(Texture.MagFilter filter) {

+        switch (filter) {

+            case Bilinear:

+                return GL_LINEAR;

+            case Nearest:

+                return GL_NEAREST;

+            default:

+                throw new UnsupportedOperationException("Unknown mag filter: " + filter);

+        }

+    }

+

+    private int convertMinFilter(Texture.MinFilter filter) {

+        switch (filter) {

+            case Trilinear:

+                return GL_LINEAR_MIPMAP_LINEAR;

+            case BilinearNearestMipMap:

+                return GL_LINEAR_MIPMAP_NEAREST;

+            case NearestLinearMipMap:

+                return GL_NEAREST_MIPMAP_LINEAR;

+            case NearestNearestMipMap:

+                return GL_NEAREST_MIPMAP_NEAREST;

+            case BilinearNoMipMaps:

+                return GL_LINEAR;

+            case NearestNoMipMaps:

+                return GL_NEAREST;

+            default:

+                throw new UnsupportedOperationException("Unknown min filter: " + filter);

+        }

+    }

+

+    private int convertWrapMode(Texture.WrapMode mode) {

+        switch (mode) {

+            case BorderClamp:

+                return GL_CLAMP_TO_BORDER;

+            case Clamp:

+                return GL_CLAMP;

+            case EdgeClamp:

+                return GL_CLAMP_TO_EDGE;

+            case Repeat:

+                return GL_REPEAT;

+            case MirroredRepeat:

+                return GL_MIRRORED_REPEAT;

+            default:

+                throw new UnsupportedOperationException("Unknown wrap mode: " + mode);

+        }

+    }

+

+    @SuppressWarnings("fallthrough")

+    private void setupTextureParams(Texture tex) {

+        Image image = tex.getImage();

+        int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1);

+

+        // filter things

+        int minFilter = convertMinFilter(tex.getMinFilter());

+        int magFilter = convertMagFilter(tex.getMagFilter());

+        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter);

+        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter);

+

+        if (tex.getAnisotropicFilter() > 1) {

+            if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic) {

+                glTexParameterf(target,

+                        EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT,

+                        tex.getAnisotropicFilter());

+            }

+        }

+

+        if (context.pointSprite) {

+            return; // Attempt to fix glTexParameter crash for some ATI GPUs

+        }

+        // repeat modes

+        switch (tex.getType()) {

+            case ThreeDimensional:

+            case CubeMap: // cubemaps use 3D coords

+                glTexParameteri(target, GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R)));

+            case TwoDimensional:

+            case TwoDimensionalArray:

+                glTexParameteri(target, GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T)));

+                // fall down here is intentional..

+//            case OneDimensional:

+                glTexParameteri(target, GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S)));

+                break;

+            default:

+                throw new UnsupportedOperationException("Unknown texture type: " + tex.getType());

+        }

+

+        // R to Texture compare mode

+        if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) {

+            glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

+            glTexParameteri(target, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);

+            if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) {

+                glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC, GL_GEQUAL);

+            } else {

+                glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

+            }

+        }

+    }

+

+    public void updateTexImageData(Image img, Texture.Type type, boolean mips, int unit) {

+        int texId = img.getId();

+        if (texId == -1) {

+            // create texture

+            glGenTextures(intBuf1);

+            texId = intBuf1.get(0);

+            img.setId(texId);

+            objManager.registerForCleanup(img);

+

+            statistics.onNewTexture();

+        }

+

+        // bind texture

+        int target = convertTextureType(type, img.getMultiSamples());

+        if (context.boundTextureUnit != unit) {

+            glActiveTexture(GL_TEXTURE0 + unit);

+            context.boundTextureUnit = unit;

+        }

+        if (context.boundTextures[unit] != img) {

+            glBindTexture(target, texId);

+            context.boundTextures[unit] = img;

+

+            statistics.onTextureUse(img, true);

+        }

+

+        if (!img.hasMipmaps() && mips) {

+            // No pregenerated mips available,

+            // generate from base level if required

+            if (!GLContext.getCapabilities().OpenGL30) {

+                glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE);

+            }

+        } else {

+//          glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 );

+            if (img.getMipMapSizes() != null) {

+                glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length);

+            }

+        }

+

+        int imageSamples = img.getMultiSamples();

+        if (imageSamples > 1) {

+            if (img.getFormat().isDepthFormat()) {

+                img.setMultiSamples(Math.min(maxDepthTexSamples, imageSamples));

+            } else {

+                img.setMultiSamples(Math.min(maxColorTexSamples, imageSamples));

+            }

+        }

+

+        // Yes, some OpenGL2 cards (GeForce 5) still dont support NPOT.

+        if (!GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {

+            if (img.getWidth() != 0 && img.getHeight() != 0) {

+                if (!FastMath.isPowerOfTwo(img.getWidth())

+                        || !FastMath.isPowerOfTwo(img.getHeight())) {

+                    if (img.getData(0) == null) {

+                        throw new RendererException("non-power-of-2 framebuffer textures are not supported by the video hardware");

+                    } else {

+                        MipMapGenerator.resizeToPowerOf2(img);

+                    }

+                }

+            }

+        }

+

+        // Check if graphics card doesn't support multisample textures

+        if (!GLContext.getCapabilities().GL_ARB_texture_multisample) {

+            if (img.getMultiSamples() > 1) {

+                throw new RendererException("Multisample textures not supported by graphics hardware");

+            }

+        }

+

+        if (target == GL_TEXTURE_CUBE_MAP) {

+            List<ByteBuffer> data = img.getData();

+            if (data.size() != 6) {

+                logger.log(Level.WARNING, "Invalid texture: {0}\n"

+                        + "Cubemap textures must contain 6 data units.", img);

+                return;

+            }

+            for (int i = 0; i < 6; i++) {

+                TextureUtil.uploadTexture(img, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc);

+            }

+        } else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT) {

+            List<ByteBuffer> data = img.getData();

+            // -1 index specifies prepare data for 2D Array

+            TextureUtil.uploadTexture(img, target, -1, 0, tdc);

+            for (int i = 0; i < data.size(); i++) {

+                // upload each slice of 2D array in turn

+                // this time with the appropriate index

+                TextureUtil.uploadTexture(img, target, i, 0, tdc);

+            }

+        } else {

+            TextureUtil.uploadTexture(img, target, 0, 0, tdc);

+        }

+

+        if (img.getMultiSamples() != imageSamples) {

+            img.setMultiSamples(imageSamples);

+        }

+

+        if (GLContext.getCapabilities().OpenGL30) {

+            if (!img.hasMipmaps() && mips && img.getData() != null) {

+                // XXX: Required for ATI

+                glEnable(target);

+                glGenerateMipmapEXT(target);

+                glDisable(target);

+            }

+        }

+

+        img.clearUpdateNeeded();

+    }

+

+    public void setTexture(int unit, Texture tex) {

+        Image image = tex.getImage();

+        if (image.isUpdateNeeded()) {

+            updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels(), unit);

+        }

+

+        int texId = image.getId();

+        assert texId != -1;

+

+        Image[] textures = context.boundTextures;

+

+        int type = convertTextureType(tex.getType(), image.getMultiSamples());

+//        if (!context.textureIndexList.moveToNew(unit)) {

+//             if (context.boundTextureUnit != unit){

+//                glActiveTexture(GL_TEXTURE0 + unit);

+//                context.boundTextureUnit = unit;

+//             }

+//             glEnable(type);

+//        }

+

+        if (context.boundTextureUnit != unit) {

+            glActiveTexture(GL_TEXTURE0 + unit);

+            context.boundTextureUnit = unit;

+        }

+        if (textures[unit] != image) {

+            glBindTexture(type, texId);

+            textures[unit] = image;

+

+            statistics.onTextureUse(image, true);

+        } else {

+            statistics.onTextureUse(image, false);

+        }

+

+        setupTextureParams(tex);

+    }

+

+    public void clearTextureUnits() {

+//        IDList textureList = context.textureIndexList;

+//        Image[] textures = context.boundTextures;

+//        for (int i = 0; i < textureList.oldLen; i++) {

+//            int idx = textureList.oldList[i];

+//            if (context.boundTextureUnit != idx){

+//                glActiveTexture(GL_TEXTURE0 + idx);

+//                context.boundTextureUnit = idx;

+//            }

+//            glDisable(convertTextureType(textures[idx].getType()));

+//            textures[idx] = null;

+//        }

+//        context.textureIndexList.copyNewToOld();

+    }

+

+    public void deleteImage(Image image) {

+        int texId = image.getId();

+        if (texId != -1) {

+            intBuf1.put(0, texId);

+            intBuf1.position(0).limit(1);

+            glDeleteTextures(intBuf1);

+            image.resetObject();

+

+            statistics.onDeleteTexture();

+        }

+    }

+

+    /*********************************************************************\

+    |* Vertex Buffers and Attributes                                     *|

+    \*********************************************************************/

+    private int convertUsage(Usage usage) {

+        switch (usage) {

+            case Static:

+                return GL_STATIC_DRAW;

+            case Dynamic:

+                return GL_DYNAMIC_DRAW;

+            case Stream:

+                return GL_STREAM_DRAW;

+            default:

+                throw new UnsupportedOperationException("Unknown usage type.");

+        }

+    }

+

+    private int convertFormat(Format format) {

+        switch (format) {

+            case Byte:

+                return GL_BYTE;

+            case UnsignedByte:

+                return GL_UNSIGNED_BYTE;

+            case Short:

+                return GL_SHORT;

+            case UnsignedShort:

+                return GL_UNSIGNED_SHORT;

+            case Int:

+                return GL_INT;

+            case UnsignedInt:

+                return GL_UNSIGNED_INT;

+            case Half:

+                return NVHalfFloat.GL_HALF_FLOAT_NV;

+//                return ARBHalfFloatVertex.GL_HALF_FLOAT;

+            case Float:

+                return GL_FLOAT;

+            case Double:

+                return GL_DOUBLE;

+            default:

+                throw new UnsupportedOperationException("Unknown buffer format.");

+

+        }

+    }

+

+    public void updateBufferData(VertexBuffer vb) {

+        int bufId = vb.getId();

+        boolean created = false;

+        if (bufId == -1) {

+            // create buffer

+            glGenBuffers(intBuf1);

+            bufId = intBuf1.get(0);

+            vb.setId(bufId);

+            objManager.registerForCleanup(vb);

+

+            //statistics.onNewVertexBuffer();

+            

+            created = true;

+        }

+

+        // bind buffer

+        int target;

+        if (vb.getBufferType() == VertexBuffer.Type.Index) {

+            target = GL_ELEMENT_ARRAY_BUFFER;

+            if (context.boundElementArrayVBO != bufId) {

+                glBindBuffer(target, bufId);

+                context.boundElementArrayVBO = bufId;

+                //statistics.onVertexBufferUse(vb, true);

+            }else{

+                //statistics.onVertexBufferUse(vb, false);

+            }

+        } else {

+            target = GL_ARRAY_BUFFER;

+            if (context.boundArrayVBO != bufId) {

+                glBindBuffer(target, bufId);

+                context.boundArrayVBO = bufId;

+                //statistics.onVertexBufferUse(vb, true);

+            }else{

+                //statistics.onVertexBufferUse(vb, false);

+            }

+        }

+

+        int usage = convertUsage(vb.getUsage());

+        vb.getData().rewind();

+

+        if (created || vb.hasDataSizeChanged()) {

+            // upload data based on format

+            switch (vb.getFormat()) {

+                case Byte:

+                case UnsignedByte:

+                    glBufferData(target, (ByteBuffer) vb.getData(), usage);

+                    break;

+                //            case Half:

+                case Short:

+                case UnsignedShort:

+                    glBufferData(target, (ShortBuffer) vb.getData(), usage);

+                    break;

+                case Int:

+                case UnsignedInt:

+                    glBufferData(target, (IntBuffer) vb.getData(), usage);

+                    break;

+                case Float:

+                    glBufferData(target, (FloatBuffer) vb.getData(), usage);

+                    break;

+                case Double:

+                    glBufferData(target, (DoubleBuffer) vb.getData(), usage);

+                    break;

+                default:

+                    throw new UnsupportedOperationException("Unknown buffer format.");

+            }

+        } else {

+            switch (vb.getFormat()) {

+                case Byte:

+                case UnsignedByte:

+                    glBufferSubData(target, 0, (ByteBuffer) vb.getData());

+                    break;

+                case Short:

+                case UnsignedShort:

+                    glBufferSubData(target, 0, (ShortBuffer) vb.getData());

+                    break;

+                case Int:

+                case UnsignedInt:

+                    glBufferSubData(target, 0, (IntBuffer) vb.getData());

+                    break;

+                case Float:

+                    glBufferSubData(target, 0, (FloatBuffer) vb.getData());

+                    break;

+                case Double:

+                    glBufferSubData(target, 0, (DoubleBuffer) vb.getData());

+                    break;

+                default:

+                    throw new UnsupportedOperationException("Unknown buffer format.");

+            }

+        }

+//        }else{

+//            if (created || vb.hasDataSizeChanged()){

+//                glBufferData(target, vb.getData().capacity() * vb.getFormat().getComponentSize(), usage);

+//            }

+//

+//            ByteBuffer buf = glMapBuffer(target,

+//                                         GL_WRITE_ONLY,

+//                                         vb.getMappedData());

+//

+//            if (buf != vb.getMappedData()){

+//                buf = buf.order(ByteOrder.nativeOrder());

+//                vb.setMappedData(buf);

+//            }

+//

+//            buf.clear();

+//

+//            switch (vb.getFormat()){

+//                case Byte:

+//                case UnsignedByte:

+//                    buf.put( (ByteBuffer) vb.getData() );

+//                    break;

+//                case Short:

+//                case UnsignedShort:

+//                    buf.asShortBuffer().put( (ShortBuffer) vb.getData() );

+//                    break;

+//                case Int:

+//                case UnsignedInt:

+//                    buf.asIntBuffer().put( (IntBuffer) vb.getData() );

+//                    break;

+//                case Float:

+//                    buf.asFloatBuffer().put( (FloatBuffer) vb.getData() );

+//                    break;

+//                case Double:

+//                    break;

+//                default:

+//                    throw new RuntimeException("Unknown buffer format.");

+//            }

+//

+//            glUnmapBuffer(target);

+//        }

+

+        vb.clearUpdateNeeded();

+    }

+

+    public void deleteBuffer(VertexBuffer vb) {

+        int bufId = vb.getId();

+        if (bufId != -1) {

+            // delete buffer

+            intBuf1.put(0, bufId);

+            intBuf1.position(0).limit(1);

+            glDeleteBuffers(intBuf1);

+            vb.resetObject();

+            

+            //statistics.onDeleteVertexBuffer();

+        }

+    }

+

+    public void clearVertexAttribs() {

+        IDList attribList = context.attribIndexList;

+        for (int i = 0; i < attribList.oldLen; i++) {

+            int idx = attribList.oldList[i];

+            glDisableVertexAttribArray(idx);

+            context.boundAttribs[idx] = null;

+        }

+        context.attribIndexList.copyNewToOld();

+    }

+

+    public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {

+        if (vb.getBufferType() == VertexBuffer.Type.Index) {

+            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");

+        }

+

+        int programId = context.boundShaderProgram;

+        if (programId > 0) {

+            Attribute attrib = boundShader.getAttribute(vb.getBufferType());

+            int loc = attrib.getLocation();

+            if (loc == -1) {

+                return; // not defined

+            }

+            if (loc == -2) {

+                stringBuf.setLength(0);

+                stringBuf.append("in").append(vb.getBufferType().name()).append('\0');

+                updateNameBuffer();

+                loc = glGetAttribLocation(programId, nameBuf);

+

+                // not really the name of it in the shader (inPosition\0) but

+                // the internal name of the enum (Position).

+                if (loc < 0) {

+                    attrib.setLocation(-1);

+                    return; // not available in shader.

+                } else {

+                    attrib.setLocation(loc);

+                }

+            }

+            

+            if (vb.isUpdateNeeded() && idb == null) {

+                updateBufferData(vb);

+            }

+            

+            VertexBuffer[] attribs = context.boundAttribs;

+            if (!context.attribIndexList.moveToNew(loc)) {

+                glEnableVertexAttribArray(loc);

+                //System.out.println("Enabled ATTRIB IDX: "+loc);

+            }

+            if (attribs[loc] != vb) {

+                // NOTE: Use id from interleaved buffer if specified

+                int bufId = idb != null ? idb.getId() : vb.getId();

+                assert bufId != -1;

+                if (context.boundArrayVBO != bufId) {

+                    glBindBuffer(GL_ARRAY_BUFFER, bufId);

+                    context.boundArrayVBO = bufId;

+                    //statistics.onVertexBufferUse(vb, true);

+                }else{

+                    //statistics.onVertexBufferUse(vb, false);

+                }

+

+                glVertexAttribPointer(loc,

+                        vb.getNumComponents(),

+                        convertFormat(vb.getFormat()),

+                        vb.isNormalized(),

+                        vb.getStride(),

+                        vb.getOffset());

+

+                attribs[loc] = vb;

+            }

+        } else {

+            throw new IllegalStateException("Cannot render mesh without shader bound");

+        }

+    }

+

+    public void setVertexAttrib(VertexBuffer vb) {

+        setVertexAttrib(vb, null);

+    }

+

+    public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {

+        if (count > 1) {

+            ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0,

+                    vertCount, count);

+        } else {

+            glDrawArrays(convertElementMode(mode), 0, vertCount);

+        }

+    }

+

+    public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) {

+        if (indexBuf.getBufferType() != VertexBuffer.Type.Index) {

+            throw new IllegalArgumentException("Only index buffers are allowed as triangle lists.");

+        }

+

+        if (indexBuf.isUpdateNeeded()) {

+            updateBufferData(indexBuf);

+        }

+

+        int bufId = indexBuf.getId();

+        assert bufId != -1;

+

+        if (context.boundElementArrayVBO != bufId) {

+            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId);

+            context.boundElementArrayVBO = bufId;

+            //statistics.onVertexBufferUse(indexBuf, true);

+        }else{

+            //statistics.onVertexBufferUse(indexBuf, true);

+        }

+

+        int vertCount = mesh.getVertexCount();

+        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);

+

+        if (mesh.getMode() == Mode.Hybrid) {

+            int[] modeStart = mesh.getModeStart();

+            int[] elementLengths = mesh.getElementLengths();

+

+            int elMode = convertElementMode(Mode.Triangles);

+            int fmt = convertFormat(indexBuf.getFormat());

+            int elSize = indexBuf.getFormat().getComponentSize();

+            int listStart = modeStart[0];

+            int stripStart = modeStart[1];

+            int fanStart = modeStart[2];

+            int curOffset = 0;

+            for (int i = 0; i < elementLengths.length; i++) {

+                if (i == stripStart) {

+                    elMode = convertElementMode(Mode.TriangleStrip);

+                } else if (i == fanStart) {

+                    elMode = convertElementMode(Mode.TriangleStrip);

+                }

+                int elementLength = elementLengths[i];

+

+                if (useInstancing) {

+                    ARBDrawInstanced.glDrawElementsInstancedARB(elMode,

+                            elementLength,

+                            fmt,

+                            curOffset,

+                            count);

+                } else {

+                    glDrawRangeElements(elMode,

+                            0,

+                            vertCount,

+                            elementLength,

+                            fmt,

+                            curOffset);

+                }

+

+                curOffset += elementLength * elSize;

+            }

+        } else {

+            if (useInstancing) {

+                ARBDrawInstanced.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),

+                        indexBuf.getData().limit(),

+                        convertFormat(indexBuf.getFormat()),

+                        0,

+                        count);

+            } else {

+                glDrawRangeElements(convertElementMode(mesh.getMode()),

+                        0,

+                        vertCount,

+                        indexBuf.getData().limit(),

+                        convertFormat(indexBuf.getFormat()),

+                        0);

+            }

+        }

+    }

+

+    /*********************************************************************\

+    |* Render Calls                                                      *|

+    \*********************************************************************/

+    public int convertElementMode(Mesh.Mode mode) {

+        switch (mode) {

+            case Points:

+                return GL_POINTS;

+            case Lines:

+                return GL_LINES;

+            case LineLoop:

+                return GL_LINE_LOOP;

+            case LineStrip:

+                return GL_LINE_STRIP;

+            case Triangles:

+                return GL_TRIANGLES;

+            case TriangleFan:

+                return GL_TRIANGLE_FAN;

+            case TriangleStrip:

+                return GL_TRIANGLE_STRIP;

+            default:

+                throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode);

+        }

+    }

+

+    public void updateVertexArray(Mesh mesh) {

+        int id = mesh.getId();

+        if (id == -1) {

+            IntBuffer temp = intBuf1;

+            ARBVertexArrayObject.glGenVertexArrays(temp);

+            id = temp.get(0);

+            mesh.setId(id);

+        }

+

+        if (context.boundVertexArray != id) {

+            ARBVertexArrayObject.glBindVertexArray(id);

+            context.boundVertexArray = id;

+        }

+

+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);

+        if (interleavedData != null && interleavedData.isUpdateNeeded()) {

+            updateBufferData(interleavedData);

+        }

+

+        IntMap<VertexBuffer> buffers = mesh.getBuffers();

+        for (Entry<VertexBuffer> entry : buffers) {

+            VertexBuffer vb = entry.getValue();

+

+            if (vb.getBufferType() == Type.InterleavedData

+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers

+                    || vb.getBufferType() == Type.Index) {

+                continue;

+            }

+

+            if (vb.getStride() == 0) {

+                // not interleaved

+                setVertexAttrib(vb);

+            } else {

+                // interleaved

+                setVertexAttrib(vb, interleavedData);

+            }

+        }

+    }

+

+    private void renderMeshVertexArray(Mesh mesh, int lod, int count) {

+        if (mesh.getId() == -1){

+            updateVertexArray(mesh);

+        }else{

+            // TODO: Check if it was updated

+        }

+

+        if (context.boundVertexArray != mesh.getId()) {

+            ARBVertexArrayObject.glBindVertexArray(mesh.getId());

+            context.boundVertexArray = mesh.getId();

+        }

+

+//        IntMap<VertexBuffer> buffers = mesh.getBuffers();

+        VertexBuffer indices = null;

+        if (mesh.getNumLodLevels() > 0) {

+            indices = mesh.getLodLevel(lod);

+        } else {

+            indices = mesh.getBuffer(Type.Index);

+        }

+        if (indices != null) {

+            drawTriangleList(indices, mesh, count);

+        } else {

+            drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());

+        }

+        clearVertexAttribs();

+        clearTextureUnits();

+    }

+

+    private void renderMeshDefault(Mesh mesh, int lod, int count) {

+        VertexBuffer indices = null;

+

+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);

+        if (interleavedData != null && interleavedData.isUpdateNeeded()) {

+            updateBufferData(interleavedData);

+        }

+

+//        IntMap<VertexBuffer> buffers = mesh.getBuffers();

+        SafeArrayList<VertexBuffer> buffersList = mesh.getBufferList();

+

+        if (mesh.getNumLodLevels() > 0) {

+            indices = mesh.getLodLevel(lod);

+        } else {

+            indices = mesh.getBuffer(Type.Index);

+        }

+        

+//        for (Entry<VertexBuffer> entry : buffers) {

+//             VertexBuffer vb = entry.getValue();

+        for (VertexBuffer vb : mesh.getBufferList().getArray()){

+            if (vb.getBufferType() == Type.InterleavedData

+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers

+                    || vb.getBufferType() == Type.Index) {

+                continue;

+            }

+

+            if (vb.getStride() == 0) {

+                // not interleaved

+                setVertexAttrib(vb);

+            } else {

+                // interleaved

+                setVertexAttrib(vb, interleavedData);

+            }

+        }

+

+        if (indices != null) {

+            drawTriangleList(indices, mesh, count);

+        } else {

+            drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());

+        }

+        clearVertexAttribs();

+        clearTextureUnits();

+    }

+

+    public void renderMesh(Mesh mesh, int lod, int count) {

+        if (mesh.getVertexCount() == 0) {

+            return;

+        }

+

+        if (context.pointSprite && mesh.getMode() != Mode.Points){

+            // XXX: Hack, disable point sprite mode if mesh not in point mode

+            if (context.boundTextures[0] != null){

+                if (context.boundTextureUnit != 0){

+                    glActiveTexture(GL_TEXTURE0);

+                    context.boundTextureUnit = 0;

+                }

+                glDisable(GL_POINT_SPRITE);

+                glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);

+                context.pointSprite = false;

+            }

+        }

+

+        if (context.pointSize != mesh.getPointSize()) {

+            glPointSize(mesh.getPointSize());

+            context.pointSize = mesh.getPointSize();

+        }

+        if (context.lineWidth != mesh.getLineWidth()) {

+            glLineWidth(mesh.getLineWidth());

+            context.lineWidth = mesh.getLineWidth();

+        }

+

+        statistics.onMeshDrawn(mesh, lod);

+//        if (GLContext.getCapabilities().GL_ARB_vertex_array_object){

+//            renderMeshVertexArray(mesh, lod, count);

+//        }else{

+            renderMeshDefault(mesh, lod, count);

+//        }

+    }

+}

diff --git a/engine/src/lwjgl/com/jme3/renderer/lwjgl/TextureUtil.java b/engine/src/lwjgl/com/jme3/renderer/lwjgl/TextureUtil.java
new file mode 100644
index 0000000..2edeea4
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/renderer/lwjgl/TextureUtil.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.nio.ByteBuffer;
+import static org.lwjgl.opengl.ATITextureCompression3DC.GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI;
+import static org.lwjgl.opengl.EXTTextureCompressionLATC.GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+import static org.lwjgl.opengl.EXTTextureCompressionLATC.GL_COMPRESSED_LUMINANCE_LATC1_EXT;
+import static org.lwjgl.opengl.EXTTextureCompressionS3TC.*;
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL12.*;
+import static org.lwjgl.opengl.GL13.glCompressedTexImage2D;
+import static org.lwjgl.opengl.GL13.glCompressedTexImage3D;
+import static org.lwjgl.opengl.GL14.*;
+import org.lwjgl.opengl.*;
+
+public class TextureUtil {
+
+    private static boolean isFormatSupported(Format fmt, ContextCapabilities caps){
+        switch (fmt){
+            case ARGB4444:
+                return false;
+            case BGR8:
+                return caps.OpenGL12 || caps.GL_EXT_bgra;
+            case DXT1:
+            case DXT1A:
+            case DXT3:
+            case DXT5:
+                return caps.GL_EXT_texture_compression_s3tc;
+            case Depth:
+            case Depth16:
+            case Depth24:
+            case Depth32:
+                return caps.OpenGL14 || caps.GL_ARB_depth_texture;
+            case Depth32F:
+            case Luminance16F:
+            case Luminance16FAlpha16F:
+            case Luminance32F:
+            case RGBA16F:
+            case RGBA32F:
+                return caps.OpenGL30 || caps.GL_ARB_texture_float;
+            case LATC:
+            case LTC:
+                return caps.GL_EXT_texture_compression_latc;
+            case RGB9E5:
+            case RGB16F_to_RGB9E5:
+                return caps.OpenGL30 || caps.GL_EXT_texture_shared_exponent;
+            case RGB111110F:
+            case RGB16F_to_RGB111110F:
+                return caps.OpenGL30 || caps.GL_EXT_packed_float;
+            default:
+                return true;
+        }
+    }
+
+    public static void checkFormatSupported(Format fmt) {
+        if (!isFormatSupported(fmt, GLContext.getCapabilities())) {
+            throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware.");
+        }
+    }
+
+    public static int convertTextureFormat(Format fmt){
+        switch (fmt){
+            case Alpha16:
+                return GL_ALPHA16;
+            case Alpha8:
+                return GL_ALPHA8;
+            case DXT1:
+                return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+            case DXT1A:
+                return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+            case DXT3:
+                return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+            case DXT5:
+                return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+            case LATC:
+                return GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+            case Depth:
+                return GL_DEPTH_COMPONENT;
+            case Depth16:
+                return GL_DEPTH_COMPONENT16;
+            case Depth24:
+                return GL_DEPTH_COMPONENT24;
+            case Depth32:
+                return GL_DEPTH_COMPONENT32;
+            case Depth32F:
+                return ARBDepthBufferFloat.GL_DEPTH_COMPONENT32F;
+            case Luminance8Alpha8:
+                return GL_LUMINANCE8_ALPHA8;
+            case Luminance16Alpha16:
+                return GL_LUMINANCE16_ALPHA16;
+            case Luminance16FAlpha16F:
+                return ARBTextureFloat.GL_LUMINANCE_ALPHA16F_ARB;
+            case Intensity8:
+                return GL_INTENSITY8;
+            case Intensity16:
+                return GL_INTENSITY16;
+            case Luminance8:
+                return GL_LUMINANCE8;
+            case Luminance16:
+                return GL_LUMINANCE16;
+            case Luminance16F:
+                return ARBTextureFloat.GL_LUMINANCE16F_ARB;
+             case Luminance32F:
+                return ARBTextureFloat.GL_LUMINANCE32F_ARB;
+            case RGB10:
+                return GL_RGB10;
+            case RGB16:
+                return GL_RGB16;
+            case RGB111110F:
+                return EXTPackedFloat.GL_R11F_G11F_B10F_EXT;
+            case RGB9E5:
+                return EXTTextureSharedExponent.GL_RGB9_E5_EXT;
+            case RGB16F:
+                return ARBTextureFloat.GL_RGB16F_ARB;
+            case RGBA16F:
+                return ARBTextureFloat.GL_RGBA16F_ARB;
+            case RGB32F:
+                return ARBTextureFloat.GL_RGB32F_ARB;
+            case RGB5A1:
+                return GL_RGB5_A1;
+            case BGR8:
+                return GL_RGB8;
+            case RGB8:
+                return GL_RGB8;
+            case RGBA16:
+                return GL_RGBA16;
+            case RGBA8:
+                return GL_RGBA8;
+            default:
+                throw new UnsupportedOperationException("Unrecognized format: "+fmt);
+        }
+    }
+
+    public static void uploadTexture(Image img,
+                                     int target,
+                                     int index,
+                                     int border,
+                                     boolean tdc){
+        Image.Format fmt = img.getFormat();
+
+        checkFormatSupported(fmt);
+
+        ByteBuffer data;
+        if (index >= 0 && img.getData() != null && img.getData().size() > 0){
+            data = img.getData(index);
+        }else{
+            data = null;
+        }
+
+        int width = img.getWidth();
+        int height = img.getHeight();
+        int depth = img.getDepth();
+
+        boolean compress = false;
+        int internalFormat = -1;
+        int format = -1;
+        int dataType = -1;
+
+        switch (fmt){
+            case Alpha16:
+                internalFormat = GL_ALPHA16;
+                format = GL_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Alpha8:
+                internalFormat = GL_ALPHA8;
+                format = GL_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case DXT1:
+                compress = true;
+                internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+                format = GL_RGB;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case DXT1A:
+                compress = true;
+                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case DXT3:
+                compress = true;
+                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case DXT5:
+                compress = true;
+                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case LATC:
+                compress = true;
+                if (tdc){
+                    internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI;
+                }else{
+                    internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+                }
+                format = GL_LUMINANCE_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case LTC:
+                compress = true;
+                internalFormat = GL_COMPRESSED_LUMINANCE_LATC1_EXT;
+                format = GL_LUMINANCE_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Depth:
+                internalFormat = GL_DEPTH_COMPONENT;
+                format = GL_DEPTH_COMPONENT;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Depth16:
+                internalFormat = GL_DEPTH_COMPONENT16;
+                format = GL_DEPTH_COMPONENT;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Depth24:
+                internalFormat = GL_DEPTH_COMPONENT24;
+                format = GL_DEPTH_COMPONENT;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Depth32:
+                internalFormat = GL_DEPTH_COMPONENT32;
+                format = GL_DEPTH_COMPONENT;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Depth32F:
+                internalFormat = NVDepthBufferFloat.GL_DEPTH_COMPONENT32F_NV;
+                format = GL_DEPTH_COMPONENT;
+                dataType = GL_FLOAT;
+                break;
+            case Luminance16FAlpha16F:
+                internalFormat = ARBTextureFloat.GL_LUMINANCE_ALPHA16F_ARB;
+                format = GL_LUMINANCE_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Intensity8:
+                internalFormat = GL_INTENSITY8;
+                format = GL_INTENSITY;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Intensity16:
+                internalFormat = GL_INTENSITY16;
+                format = GL_INTENSITY;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Luminance8:
+                internalFormat = GL_LUMINANCE8;
+                format = GL_LUMINANCE;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Luminance8Alpha8:
+                internalFormat = GL_LUMINANCE8_ALPHA8;
+                format = GL_LUMINANCE_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Luminance16Alpha16:
+                internalFormat = GL_LUMINANCE16_ALPHA16;
+                format = GL_LUMINANCE_ALPHA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Luminance16:
+                internalFormat = GL_LUMINANCE16;
+                format = GL_LUMINANCE;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case Luminance16F:
+                internalFormat = ARBTextureFloat.GL_LUMINANCE16F_ARB;
+                format = GL_LUMINANCE;
+                dataType = ARBHalfFloatPixel.GL_HALF_FLOAT_ARB;
+                break;
+            case Luminance32F:
+                internalFormat = ARBTextureFloat.GL_LUMINANCE32F_ARB;
+                format = GL_LUMINANCE;
+                dataType = GL_FLOAT;
+                break;
+            case RGB10:
+                internalFormat = GL_RGB10;
+                format = GL_RGB;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case RGB16:
+                internalFormat = GL_RGB16;
+                format = GL_RGB;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case RGB111110F:
+                internalFormat = EXTPackedFloat.GL_R11F_G11F_B10F_EXT;
+                format = GL_RGB;
+                dataType = EXTPackedFloat.GL_UNSIGNED_INT_10F_11F_11F_REV_EXT;
+                break;
+            case RGB16F_to_RGB111110F:
+                internalFormat = EXTPackedFloat.GL_R11F_G11F_B10F_EXT;
+                format = GL_RGB;
+                dataType = ARBHalfFloatPixel.GL_HALF_FLOAT_ARB;
+                break;
+            case RGB16F_to_RGB9E5:
+                internalFormat = EXTTextureSharedExponent.GL_RGB9_E5_EXT;
+                format = GL_RGB;
+                dataType = ARBHalfFloatPixel.GL_HALF_FLOAT_ARB;
+                break;
+            case RGB9E5:
+                internalFormat = EXTTextureSharedExponent.GL_RGB9_E5_EXT;
+                format = GL_RGB;
+                dataType = EXTTextureSharedExponent.GL_UNSIGNED_INT_5_9_9_9_REV_EXT;
+                break;
+            case RGB16F:
+                internalFormat = ARBTextureFloat.GL_RGB16F_ARB;
+                format = GL_RGB;
+                dataType = ARBHalfFloatPixel.GL_HALF_FLOAT_ARB;
+                break;
+            case RGBA16F:
+                internalFormat = ARBTextureFloat.GL_RGBA16F_ARB;
+                format = GL_RGBA;
+                dataType = ARBHalfFloatPixel.GL_HALF_FLOAT_ARB;
+                break;
+            case RGB32F:
+                internalFormat = ARBTextureFloat.GL_RGB32F_ARB;
+                format = GL_RGB;
+                dataType = GL_FLOAT;
+                break;
+            case RGBA32F:
+                internalFormat = ARBTextureFloat.GL_RGBA32F_ARB;
+                format = GL_RGBA;
+                dataType = GL_FLOAT;
+                break;
+            case RGB5A1:
+                internalFormat = GL_RGB5_A1;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case RGB8:
+                internalFormat = GL_RGB8;
+                format = GL_RGB;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case BGR8:
+                internalFormat = GL_RGB8;
+                format = GL_BGR;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case RGBA16:
+                internalFormat = GL_RGBA16;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case RGBA8:
+                internalFormat = GL_RGBA8;
+                format = GL_RGBA;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            case ABGR8:
+                internalFormat = GL_RGBA8;
+                format = EXTAbgr.GL_ABGR_EXT;
+                dataType = GL_UNSIGNED_BYTE;
+                break;
+            default:
+                throw new UnsupportedOperationException("Unrecognized format: "+fmt);
+        }
+
+        if (data != null)
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        int[] mipSizes = img.getMipMapSizes();
+        int pos = 0;
+        // TODO: Remove unneccessary allocation
+        if (mipSizes == null){
+            if (data != null)
+                mipSizes = new int[]{ data.capacity() };
+            else
+                mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
+        }
+
+        boolean subtex = false;
+        int samples = img.getMultiSamples();
+
+        for (int i = 0; i < mipSizes.length; i++){
+            int mipWidth =  Math.max(1, width  >> i);
+            int mipHeight = Math.max(1, height >> i);
+            int mipDepth =  Math.max(1, depth  >> i);
+
+            if (data != null){
+                data.position(pos);
+                data.limit(pos + mipSizes[i]);
+            }
+            
+            if (compress && data != null){
+                if (target == GL_TEXTURE_3D){
+                    glCompressedTexImage3D(target,
+                                           i,
+                                           internalFormat,
+                                           mipWidth,
+                                           mipHeight,
+                                           mipDepth,
+                                           border,
+                                           data);
+                }else{
+                    //all other targets use 2D: array, cubemap, 2d
+                    glCompressedTexImage2D(target,
+                                           i,
+                                           internalFormat,
+                                           mipWidth,
+                                           mipHeight,
+                                           border,
+                                           data);
+                }
+            }else{
+                if (target == GL_TEXTURE_3D){
+                    glTexImage3D(target,
+                                 i,
+                                 internalFormat,
+                                 mipWidth,
+                                 mipHeight,
+                                 mipDepth,
+                                 border,
+                                 format,
+                                 dataType,
+                                 data);
+                }else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT){
+                    // prepare data for 2D array
+                    // or upload slice
+                    if (index == -1){
+                        glTexImage3D(target,
+                                     0,
+                                     internalFormat,
+                                     mipWidth,
+                                     mipHeight,
+                                     img.getData().size(), //# of slices
+                                     border,
+                                     format,
+                                     dataType,
+                                     data);
+                    }else{
+                        glTexSubImage3D(target,
+                                        i, // level
+                                        0, // xoffset
+                                        0, // yoffset
+                                        index, // zoffset
+                                        width, // width
+                                        height, // height
+                                        1, // depth
+                                        format,
+                                        dataType,
+                                        data);
+                    }
+                }else{
+                    if (subtex){
+                        if (samples > 1)
+                            throw new IllegalStateException("Cannot update multisample textures");
+
+                        glTexSubImage2D(target,
+                                        i,
+                                        0, 0,
+                                        mipWidth, mipHeight,
+                                        format,
+                                        dataType,
+                                        data);
+                    }else{
+                        if (samples > 1){
+                            ARBTextureMultisample.glTexImage2DMultisample(target,
+                                                                          samples,
+                                                                          internalFormat,
+                                                                          mipWidth,
+                                                                          mipHeight,
+                                                                          true);
+                        }else{
+                            glTexImage2D(target,
+                                         i,
+                                         internalFormat,
+                                         mipWidth,
+                                         mipHeight,
+                                         border,
+                                         format,
+                                         dataType,
+                                         data);
+                        }
+                    }
+                }
+            }
+            
+            pos += mipSizes[i];
+        }
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java
new file mode 100644
index 0000000..b1171cd
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.lwjgl.JInputJoyInput;
+import com.jme3.input.lwjgl.LwjglKeyInput;
+import com.jme3.input.lwjgl.LwjglMouseInput;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.JmeSystem;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.Sys;
+import org.lwjgl.opengl.Display;
+import org.lwjgl.opengl.OpenGLException;
+import org.lwjgl.opengl.Util;
+
+public abstract class LwjglAbstractDisplay extends LwjglContext implements Runnable {
+
+    private static final Logger logger = Logger.getLogger(LwjglAbstractDisplay.class.getName());
+    
+    protected AtomicBoolean needClose = new AtomicBoolean(false);
+    protected boolean wasActive = false;
+    protected int frameRate = 0;
+    protected boolean autoFlush = true;
+
+    /**
+     * @return Type.Display or Type.Canvas
+     */
+    public abstract Type getType();
+
+    /**
+     * Set the title if its a windowed display
+     * @param title
+     */
+    public abstract void setTitle(String title);
+
+    /**
+     * Restart if its a windowed or full-screen display.
+     */
+    public abstract void restart();
+
+    /**
+     * Apply the settings, changing resolution, etc.
+     * @param settings
+     */
+    protected abstract void createContext(AppSettings settings) throws LWJGLException;
+
+    /**
+     * Destroy the context.
+     */
+    protected abstract void destroyContext();
+
+    /**
+     * Does LWJGL display initialization in the OpenGL thread
+     */
+    protected void initInThread(){
+        try{
+            if (!JmeSystem.isLowPermissions()){
+                // Enable uncaught exception handler only for current thread
+                Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                    public void uncaughtException(Thread thread, Throwable thrown) {
+                        listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);
+                        if (needClose.get()){
+                            // listener.handleError() has requested the 
+                            // context to close. Satisfy request.
+                            deinitInThread();
+                        }
+                    }
+                });
+            }
+
+            // For canvas, this will create a pbuffer,
+            // allowing us to query information.
+            // When the canvas context becomes available, it will
+            // be replaced seamlessly.
+            createContext(settings);
+            printContextInitInfo();
+
+            created.set(true);
+        } catch (Exception ex){
+            try {
+                if (Display.isCreated())
+                    Display.destroy();
+            } catch (Exception ex2){
+                logger.log(Level.WARNING, null, ex2);
+            }
+
+            listener.handleError("Failed to create display", ex);
+            return; // if we failed to create display, do not continue
+        }
+        super.internalCreate();
+        listener.initialize();
+    }
+
+    protected boolean checkGLError(){
+        try {
+            Util.checkGLError();
+        } catch (OpenGLException ex){
+            listener.handleError("An OpenGL error has occured!", ex);
+        }
+        // NOTE: Always return true since this is used in an "assert" statement
+        return true;
+    }
+
+    /**
+     * execute one iteration of the render loop in the OpenGL thread
+     */
+    protected void runLoop(){
+        if (!created.get())
+            throw new IllegalStateException();
+
+        listener.update();
+       
+        // All this does is call swap buffers
+        // If the canvas is not active, there's no need to waste time
+        // doing that ..
+        if (renderable.get()){
+            assert checkGLError();
+
+            // calls swap buffers, etc.
+            try {
+                if (autoFlush){
+                    Display.update(false);
+                }else{
+                    Display.processMessages();
+                    Thread.sleep(50);
+                    // add a small wait
+                    // to reduce CPU usage
+                }
+            } catch (Throwable ex){
+                listener.handleError("Error while swapping buffers", ex);
+            }
+        }
+
+        if (frameRate > 0)
+            Display.sync(frameRate);
+
+        if (renderable.get()){
+            if (autoFlush){
+                // check input after we synchronize with framerate.
+                // this reduces input lag.
+                Display.processMessages();
+            }
+        }
+
+        // Subclasses just call GLObjectManager clean up objects here
+        // it is safe .. for now.
+        renderer.onFrame();
+    }
+
+    /**
+     * De-initialize in the OpenGL thread.
+     */
+    protected void deinitInThread(){
+        destroyContext();
+
+        listener.destroy();
+        logger.info("Display destroyed.");
+        super.internalDestroy();
+    }
+
+    public void run(){
+        if (listener == null)
+            throw new IllegalStateException("SystemListener is not set on context!"
+                                          + "Must set with JmeContext.setSystemListner().");
+
+        logger.log(Level.INFO, "Using LWJGL {0}", Sys.getVersion());
+        initInThread();
+        while (true){
+            if (renderable.get()){
+                if (Display.isCloseRequested())
+                    listener.requestClose(false);
+
+                if (wasActive != Display.isActive()) {
+                    if (!wasActive) {
+                        listener.gainFocus();
+                        timer.reset();
+                        wasActive = true;
+                    } else {
+                        listener.loseFocus();
+                        wasActive = false;
+                    }
+                }
+            }
+
+            runLoop();
+
+            if (needClose.get())
+                break;
+        }
+        deinitInThread();
+    }
+
+    public JoyInput getJoyInput() {
+        if (joyInput == null){
+            joyInput = new JInputJoyInput();
+        }
+        return joyInput;
+    }
+
+    public MouseInput getMouseInput() {
+        if (mouseInput == null){
+            mouseInput = new LwjglMouseInput(this);
+        }
+        return mouseInput;
+    }
+
+    public KeyInput getKeyInput() {
+        if (keyInput == null){
+            keyInput = new LwjglKeyInput(this);
+        }
+        return keyInput;
+    }
+    
+    public TouchInput getTouchInput() {
+        return null;
+    }
+    
+    public void setAutoFlushFrames(boolean enabled){
+        this.autoFlush = enabled;
+    }
+
+    public void destroy(boolean waitFor){
+        needClose.set(true);
+        if (waitFor)
+            waitFor(false);
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglCanvas.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglCanvas.java
new file mode 100644
index 0000000..bb18543
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglCanvas.java
@@ -0,0 +1,482 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.system.lwjgl;

+

+import com.jme3.system.AppSettings;

+import com.jme3.system.JmeCanvasContext;

+import com.jme3.system.JmeContext.Type;

+import com.jme3.system.JmeSystem;

+import com.jme3.system.Platform;

+import java.awt.Canvas;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import javax.swing.SwingUtilities;

+import org.lwjgl.LWJGLException;

+import org.lwjgl.input.Keyboard;

+import org.lwjgl.input.Mouse;

+import org.lwjgl.opengl.Display;

+import org.lwjgl.opengl.Pbuffer;

+import org.lwjgl.opengl.PixelFormat;

+

+public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {

+

+    protected static final int TASK_NOTHING = 0,

+                               TASK_DESTROY_DISPLAY = 1,

+                               TASK_CREATE_DISPLAY = 2,

+                               TASK_COMPLETE = 3;

+    

+//    protected static final boolean USE_SHARED_CONTEXT =

+//                Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true"));

+    

+    protected static final boolean USE_SHARED_CONTEXT = false;

+    

+    private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());

+    private Canvas canvas;

+    private int width;

+    private int height;

+

+    private final Object taskLock = new Object();

+    private int desiredTask = TASK_NOTHING;

+

+    private Thread renderThread;

+    private boolean runningFirstTime = true;

+    private boolean mouseWasGrabbed = false;

+    

+    private boolean mouseWasCreated = false;

+    private boolean keyboardWasCreated = false;

+

+    private Pbuffer pbuffer;

+    private PixelFormat pbufferFormat;

+    private PixelFormat canvasFormat;

+

+    private class GLCanvas extends Canvas {

+        @Override

+        public void addNotify(){

+            super.addNotify();

+

+            if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED)

+                return; // already destroyed.

+

+            if (renderThread == null){

+                logger.log(Level.INFO, "EDT: Creating OGL thread.");

+

+                // Also set some settings on the canvas here.

+                // So we don't do it outside the AWT thread.

+                canvas.setFocusable(true);

+                canvas.setIgnoreRepaint(true);

+

+                renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");

+                renderThread.start();

+            }else if (needClose.get()){

+                return;

+            }

+

+            logger.log(Level.INFO, "EDT: Telling OGL to create display ..");

+            synchronized (taskLock){

+                desiredTask = TASK_CREATE_DISPLAY;

+//                while (desiredTask != TASK_COMPLETE){

+//                    try {

+//                        taskLock.wait();

+//                    } catch (InterruptedException ex) {

+//                        return;

+//                    }

+//                }

+//                desiredTask = TASK_NOTHING;

+            }

+//            logger.log(Level.INFO, "EDT: OGL has created the display");

+        }

+

+        @Override

+        public void removeNotify(){

+            if (needClose.get()){

+                logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas.");

+                super.removeNotify();

+                return;

+            }

+

+            // We must tell GL context to shutdown and wait for it to

+            // shutdown, otherwise, issues will occur.

+            logger.log(Level.INFO, "EDT: Telling OGL to destroy display ..");

+            synchronized (taskLock){

+                desiredTask = TASK_DESTROY_DISPLAY;

+                while (desiredTask != TASK_COMPLETE){

+                    try {

+                        taskLock.wait();

+                    } catch (InterruptedException ex){

+                        super.removeNotify();

+                        return;

+                    }

+                }

+                desiredTask = TASK_NOTHING;

+            }

+            

+            logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");

+            // GL context is dead at this point

+

+            super.removeNotify();

+        }

+    }

+

+    public LwjglCanvas(){

+        super();

+        canvas = new GLCanvas();

+    }

+

+    @Override

+    public Type getType() {

+        return Type.Canvas;

+    }

+

+    public void create(boolean waitFor){

+        if (renderThread == null){

+            logger.log(Level.INFO, "MAIN: Creating OGL thread.");

+

+            renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");

+            renderThread.start();

+        }

+        // do not do anything.

+        // superclass's create() will be called at initInThread()

+        if (waitFor)

+            waitFor(true);

+    }

+

+    @Override

+    public void setTitle(String title) {

+    }

+

+    @Override

+    public void restart() {

+        frameRate = settings.getFrameRate();

+        // TODO: Handle other cases, like change of pixel format, etc.

+    }

+

+    public Canvas getCanvas(){

+        return canvas;

+    }

+    

+    @Override

+    protected void runLoop(){

+        if (desiredTask != TASK_NOTHING){

+            synchronized (taskLock){

+                switch (desiredTask){

+                    case TASK_CREATE_DISPLAY:

+                        logger.log(Level.INFO, "OGL: Creating display ..");

+                        restoreCanvas();

+                        listener.gainFocus();

+                        desiredTask = TASK_NOTHING;

+                        break;

+                    case TASK_DESTROY_DISPLAY:

+                        logger.log(Level.INFO, "OGL: Destroying display ..");

+                        listener.loseFocus();

+                        pauseCanvas();

+                        break;

+                }

+                desiredTask = TASK_COMPLETE;

+                taskLock.notifyAll();

+            }

+        }

+        

+        if (renderable.get()){

+            int newWidth = Math.max(canvas.getWidth(), 1);

+            int newHeight = Math.max(canvas.getHeight(), 1);

+            if (width != newWidth || height != newHeight){

+                width = newWidth;

+                height = newHeight;

+                if (listener != null){

+                    listener.reshape(width, height);

+                }

+            }

+        }else{

+            if (frameRate <= 0){

+                // NOTE: MUST be done otherwise 

+                // Windows OS will freeze

+                Display.sync(30);

+            }

+        }

+        

+        super.runLoop();

+    }

+

+    private void pauseCanvas(){

+        if (Mouse.isCreated()){

+            if (Mouse.isGrabbed()){

+                Mouse.setGrabbed(false);

+                mouseWasGrabbed = true;

+            }

+            mouseWasCreated = true;

+            Mouse.destroy();

+        }

+        if (Keyboard.isCreated()){

+            keyboardWasCreated = true;

+            Keyboard.destroy();

+        }

+

+        renderable.set(false);

+        destroyContext();

+    }

+

+    /**

+     * Called to restore the canvas.

+     */

+    private void restoreCanvas(){

+        logger.log(Level.INFO, "OGL: Waiting for canvas to become displayable..");

+        while (!canvas.isDisplayable()){

+            try {

+                Thread.sleep(10);

+            } catch (InterruptedException ex) {

+                logger.log(Level.SEVERE, "OGL: Interrupted! ", ex);

+            }

+        }

+        

+        logger.log(Level.INFO, "OGL: Creating display context ..");

+

+        // Set renderable to true, since canvas is now displayable.

+        renderable.set(true);

+        createContext(settings);

+

+        logger.log(Level.INFO, "OGL: Display is active!");

+

+        try {

+            if (mouseWasCreated){

+                Mouse.create();

+                if (mouseWasGrabbed){

+                    Mouse.setGrabbed(true);

+                    mouseWasGrabbed = false;

+                }

+            }

+            if (keyboardWasCreated){

+                Keyboard.create();

+                keyboardWasCreated = false;

+            }

+        } catch (LWJGLException ex){

+            logger.log(Level.SEVERE, "Encountered exception when restoring input", ex);

+        }

+

+        SwingUtilities.invokeLater(new Runnable(){

+            public void run(){

+                canvas.requestFocus();

+            }

+        });

+    }

+    

+    /**

+     * It seems it is best to use one pixel format for all shared contexts.

+     * @see <a href="http://developer.apple.com/library/mac/#qa/qa1248/_index.html">http://developer.apple.com/library/mac/#qa/qa1248/_index.html</a>

+     */

+    protected PixelFormat acquirePixelFormat(boolean forPbuffer){

+        if (forPbuffer){

+            // Use 0 samples for pbuffer format, prevents

+            // crashes on bad drivers

+            if (pbufferFormat == null){

+                pbufferFormat = new PixelFormat(settings.getBitsPerPixel(),

+                                                0,

+                                                settings.getDepthBits(),

+                                                settings.getStencilBits(),

+                                                0);

+            }

+            return pbufferFormat;

+        }else{

+            if (canvasFormat == null){

+			int samples = 0;

+		      if (settings.getSamples() > 1){

+                    samples = settings.getSamples();

+                }

+                canvasFormat = new PixelFormat(settings.getBitsPerPixel(),

+                                               0,

+                                               settings.getDepthBits(),

+                                               settings.getStencilBits(),

+                                               samples);

+            }

+            return canvasFormat;

+        }

+    }

+

+    /**

+     * Makes sure the pbuffer is available and ready for use

+     */

+    protected void makePbufferAvailable() throws LWJGLException{

+        if (pbuffer != null && pbuffer.isBufferLost()){

+            logger.log(Level.WARNING, "PBuffer was lost!");

+            pbuffer.destroy();

+            pbuffer = null;

+        }

+        

+        if (pbuffer == null) {

+            pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);

+            pbuffer.makeCurrent();

+            logger.log(Level.INFO, "OGL: Pbuffer has been created");

+            

+            // Any created objects are no longer valid

+            if (!runningFirstTime){

+                renderer.resetGLObjects();

+            }

+        }

+        

+        pbuffer.makeCurrent();

+        if (!pbuffer.isCurrent()){

+            throw new LWJGLException("Pbuffer cannot be made current");

+        }

+    }

+    

+    protected void destroyPbuffer(){

+        if (pbuffer != null){

+            if (!pbuffer.isBufferLost()){

+                pbuffer.destroy();

+            }

+            pbuffer = null;

+        }

+    }

+    

+    /**

+     * This is called:

+     * 1) When the context thread ends

+     * 2) Any time the canvas becomes non-displayable

+     */

+    protected void destroyContext(){

+        try {

+            // invalidate the state so renderer can resume operation

+            if (!USE_SHARED_CONTEXT){

+                renderer.cleanup();

+            }

+            

+            if (Display.isCreated()){

+                /* FIXES:

+                 * org.lwjgl.LWJGLException: X Error

+                 * BadWindow (invalid Window parameter) request_code: 2 minor_code: 0

+                 * 

+                 * Destroying keyboard early prevents the error above, triggered

+                 * by destroying keyboard in by Display.destroy() or Display.setParent(null).

+                 * Therefore Keyboard.destroy() should precede any of these calls.

+                 */

+                if (Keyboard.isCreated()){

+                    // Should only happen if called in 

+                    // LwjglAbstractDisplay.deinitInThread().

+                    Keyboard.destroy();

+                }

+

+                //try {

+                    // NOTE: On Windows XP, not calling setParent(null)

+                    // freezes the application.

+                    // On Mac it freezes the application.

+                    // On Linux it fixes a crash with X Window System.

+                    if (JmeSystem.getPlatform() == Platform.Windows32

+                     || JmeSystem.getPlatform() == Platform.Windows64){

+                        //Display.setParent(null);

+                    }

+                //} catch (LWJGLException ex) {

+                //    logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);

+                //}

+

+                Display.destroy();

+            }

+            

+            // The canvas is no longer visible,

+            // but the context thread is still running.

+            if (!needClose.get()){

+                // MUST make sure there's still a context current here ..

+                // Display is dead, make pbuffer available to the system

+                makePbufferAvailable();

+                

+                renderer.invalidateState();

+            }else{

+                // The context thread is no longer running.

+                // Destroy pbuffer.

+                destroyPbuffer();

+            }

+        } catch (LWJGLException ex) {

+            listener.handleError("Failed make pbuffer available", ex);

+        }

+    }

+

+    /**

+     * This is called:

+     * 1) When the context thread starts

+     * 2) Any time the canvas becomes displayable again.

+     */

+    @Override

+    protected void createContext(AppSettings settings) {

+        // In case canvas is not visible, we still take framerate

+        // from settings to prevent "100% CPU usage"

+        frameRate = settings.getFrameRate();

+        

+        try {

+            if (renderable.get()){

+                if (!runningFirstTime){

+                    // because the display is a different opengl context

+                    // must reset the context state.

+                    if (!USE_SHARED_CONTEXT){

+                        renderer.cleanup();

+                    }

+                }

+                

+                // if the pbuffer is currently active, 

+                // make sure to deactivate it

+                destroyPbuffer();

+                

+                if (Keyboard.isCreated()){

+                    Keyboard.destroy();

+                }

+                

+                try {

+                    Thread.sleep(1000);

+                } catch (InterruptedException ex) {

+                }

+                

+                Display.setVSyncEnabled(settings.isVSync());

+                Display.setParent(canvas);

+                

+                if (USE_SHARED_CONTEXT){

+                    Display.create(acquirePixelFormat(false), pbuffer);

+                }else{

+                    Display.create(acquirePixelFormat(false));

+                }

+                

+                renderer.invalidateState();

+            }else{

+                // First create the pbuffer, if it is needed.

+                makePbufferAvailable();

+            }

+

+            // At this point, the OpenGL context is active.

+            if (runningFirstTime){

+                // THIS is the part that creates the renderer.

+                // It must always be called, now that we have the pbuffer workaround.

+                initContextFirstTime();

+                runningFirstTime = false;

+            }

+        } catch (LWJGLException ex) {

+            listener.handleError("Failed to initialize OpenGL context", ex);

+            // TODO: Fix deadlock that happens after the error (throw runtime exception?)

+        }

+    }

+}

diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglContext.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglContext.java
new file mode 100644
index 0000000..91b3694
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglContext.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.input.lwjgl.JInputJoyInput;
+import com.jme3.input.lwjgl.LwjglKeyInput;
+import com.jme3.input.lwjgl.LwjglMouseInput;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.lwjgl.LwjglGL1Renderer;
+import com.jme3.renderer.lwjgl.LwjglRenderer;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+import com.jme3.system.SystemListener;
+import com.jme3.system.Timer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.opengl.*;
+
+/**
+ * A LWJGL implementation of a graphics context.
+ */
+public abstract class LwjglContext implements JmeContext {
+
+    private static final Logger logger = Logger.getLogger(LwjglContext.class.getName());
+
+    protected AtomicBoolean created = new AtomicBoolean(false);
+    protected AtomicBoolean renderable = new AtomicBoolean(false);
+    protected final Object createdLock = new Object();
+
+    protected AppSettings settings = new AppSettings(true);
+    protected Renderer renderer;
+    protected LwjglKeyInput keyInput;
+    protected LwjglMouseInput mouseInput;
+    protected JInputJoyInput joyInput;
+    protected Timer timer;
+    protected SystemListener listener;
+
+    public void setSystemListener(SystemListener listener){
+        this.listener = listener;
+    }
+
+    protected void printContextInitInfo(){
+        logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
+
+        logger.log(Level.INFO, "Adapter: {0}", Display.getAdapter());
+        logger.log(Level.INFO, "Driver Version: {0}", Display.getVersion());
+
+        String vendor = GL11.glGetString(GL11.GL_VENDOR);
+        logger.log(Level.INFO, "Vendor: {0}", vendor);
+
+        String version = GL11.glGetString(GL11.GL_VERSION);
+        logger.log(Level.INFO, "OpenGL Version: {0}", version);
+
+        String renderGl = GL11.glGetString(GL11.GL_RENDERER);
+        logger.log(Level.INFO, "Renderer: {0}", renderGl);
+
+        if (GLContext.getCapabilities().OpenGL20){
+            String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION);
+            logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang);
+        }
+    }
+
+    protected ContextAttribs createContextAttribs(){
+        if (settings.getBoolean("GraphicsDebug") || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){
+            ContextAttribs attr;
+            if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){
+                attr = new ContextAttribs(3, 3);
+                attr = attr.withProfileCore(true).withForwardCompatible(true).withProfileCompatibility(false);
+            }else{
+                attr = new ContextAttribs();
+            }
+            if (settings.getBoolean("GraphicsDebug")){
+                attr = attr.withDebug(true);
+            }
+            return attr;
+        }else{
+            return null;
+        }
+    }
+
+    protected void initContextFirstTime(){
+        if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
+         || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){
+            renderer = new LwjglRenderer();
+        }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL1)){
+            renderer = new LwjglGL1Renderer();
+        }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL_ANY)){
+            // Choose an appropriate renderer based on capabilities
+            if (GLContext.getCapabilities().OpenGL20){
+                renderer = new LwjglRenderer();
+            }else{
+                renderer = new LwjglGL1Renderer();
+            }
+        }else{
+            throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
+        }
+        
+        // Init renderer
+        if (renderer instanceof LwjglRenderer){
+            ((LwjglRenderer)renderer).initialize();
+        }else if (renderer instanceof LwjglGL1Renderer){
+            ((LwjglGL1Renderer)renderer).initialize();
+        }else{
+            assert false;
+        }
+
+        // Init input
+        if (keyInput != null)
+            keyInput.initialize();
+
+        if (mouseInput != null)
+            mouseInput.initialize();
+
+        if (joyInput != null)
+            joyInput.initialize();
+    }
+
+    public void internalDestroy(){
+        renderer = null;
+        timer = null;
+        renderable.set(false);
+        synchronized (createdLock){
+            created.set(false);
+            createdLock.notifyAll();
+        }
+    }
+    
+    public void internalCreate(){
+        timer = new LwjglTimer();
+        
+        synchronized (createdLock){
+            created.set(true);
+            createdLock.notifyAll();
+        }
+        
+        if (renderable.get()){
+            initContextFirstTime();
+        }else{
+            assert getType() == Type.Canvas;
+        }
+    }
+
+    public void create(){
+        create(false);
+    }
+
+    public void destroy(){
+        destroy(false);
+    }
+
+    protected void waitFor(boolean createdVal){
+        synchronized (createdLock){
+            while (created.get() != createdVal){
+                try {
+                    createdLock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+    }
+
+    public boolean isCreated(){
+        return created.get();
+    }
+    
+    public boolean isRenderable(){
+        return renderable.get();
+    }
+
+    public void setSettings(AppSettings settings) {
+        this.settings.copyFrom(settings);
+    }
+
+    public AppSettings getSettings(){
+        return settings;
+    }
+
+    public Renderer getRenderer() {
+        return renderer;
+    }
+
+    public Timer getTimer() {
+        return timer;
+    }
+
+}
diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglDisplay.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglDisplay.java
new file mode 100644
index 0000000..db106e7
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglDisplay.java
@@ -0,0 +1,240 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.system.lwjgl;

+

+import com.jme3.system.AppSettings;

+import com.jme3.system.JmeContext.Type;

+import java.awt.Graphics2D;

+import java.awt.image.BufferedImage;

+import java.nio.ByteBuffer;

+import java.util.concurrent.atomic.AtomicBoolean;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.lwjgl.LWJGLException;

+import org.lwjgl.opengl.*;

+

+public class LwjglDisplay extends LwjglAbstractDisplay {

+

+    private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());

+

+    private final AtomicBoolean needRestart = new AtomicBoolean(false);

+    private PixelFormat pixelFormat;

+

+    protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, int freq){

+        try {

+            DisplayMode[] modes = Display.getAvailableDisplayModes();

+            for (DisplayMode mode : modes){

+                if (mode.getWidth() == width

+                 && mode.getHeight() == height

+                 && (mode.getBitsPerPixel() == bpp || (bpp==24&&mode.getBitsPerPixel()==32))

+                 && mode.getFrequency() == freq){

+                    return mode;

+                }

+            }

+        } catch (LWJGLException ex) {

+            listener.handleError("Failed to acquire fullscreen display mode!", ex);

+        }

+        return null;

+    }

+

+    protected void createContext(AppSettings settings) throws LWJGLException{

+        DisplayMode displayMode = null;

+        if (settings.getWidth() <= 0 || settings.getHeight() <= 0){

+            displayMode = Display.getDesktopDisplayMode();

+            settings.setResolution(displayMode.getWidth(), displayMode.getHeight());

+        }else if (settings.isFullscreen()){

+            displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(),

+                                                   settings.getBitsPerPixel(), settings.getFrequency());

+            if (displayMode == null)

+                throw new RuntimeException("Unable to find fullscreen display mode matching settings");

+        }else{

+            displayMode = new DisplayMode(settings.getWidth(), settings.getHeight());

+        }

+

+	   int samples = 0;

+        if (settings.getSamples() > 1){

+            samples = settings.getSamples();

+        }

+        PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(),

+                                         0,

+                                         settings.getDepthBits(),

+                                         settings.getStencilBits(),

+                                         samples);

+

+        frameRate = settings.getFrameRate();

+        logger.log(Level.INFO, "Selected display mode: {0}", displayMode);

+

+        boolean pixelFormatChanged = false;

+        if (created.get() && (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel()

+                            ||pixelFormat.getDepthBits() != pf.getDepthBits()

+                            ||pixelFormat.getStencilBits() != pf.getStencilBits()

+                            ||pixelFormat.getSamples() != pf.getSamples())){

+            renderer.resetGLObjects();

+            Display.destroy();

+            pixelFormatChanged = true;

+        }

+        pixelFormat = pf;

+        

+        Display.setTitle(settings.getTitle());

+        if (displayMode != null){

+            if (settings.isFullscreen()){

+                Display.setDisplayModeAndFullscreen(displayMode);

+            }else{

+                Display.setFullscreen(false);

+                Display.setDisplayMode(displayMode);

+            }

+        }else{

+            Display.setFullscreen(settings.isFullscreen());

+        }

+

+        if (settings.getIcons() != null) {

+            Display.setIcon(imagesToByteBuffers(settings.getIcons()));

+        }

+        

+        Display.setVSyncEnabled(settings.isVSync());

+        

+        if (created.get() && !pixelFormatChanged){

+            Display.releaseContext();

+            Display.makeCurrent();

+            Display.update();

+        }

+

+        if (!created.get() || pixelFormatChanged){

+            ContextAttribs attr = createContextAttribs();

+            if (attr != null){

+                Display.create(pixelFormat, attr);

+            }else{

+                Display.create(pixelFormat);

+            }

+            renderable.set(true);

+            

+            if (pixelFormatChanged && pixelFormat.getSamples() > 1

+             && GLContext.getCapabilities().GL_ARB_multisample){

+                GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB);

+            }

+        }

+    }

+    

+    protected void destroyContext(){

+        try {

+            renderer.cleanup();

+            Display.releaseContext();

+            Display.destroy();

+        } catch (LWJGLException ex) {

+            listener.handleError("Failed to destroy context", ex);

+        }

+    }

+

+    public void create(boolean waitFor){

+        if (created.get()){

+            logger.warning("create() called when display is already created!");

+            return;

+        }

+

+        new Thread(this, "LWJGL Renderer Thread").start();

+        if (waitFor)

+            waitFor(true);

+    }

+

+    @Override

+    public void runLoop(){

+        // This method is overriden to do restart

+        if (needRestart.getAndSet(false)){

+            try{

+                createContext(settings);

+            }catch (LWJGLException ex){

+                logger.log(Level.SEVERE, "Failed to set display settings!", ex);

+            }

+            listener.reshape(settings.getWidth(), settings.getHeight());

+            logger.info("Display restarted.");

+        }

+

+        super.runLoop();

+    }

+

+    @Override

+    public void restart() {

+        if (created.get()){

+            needRestart.set(true);

+        }else{

+            logger.warning("Display is not created, cannot restart window.");

+        }

+    }

+

+    public Type getType() {

+        return Type.Display;

+    }

+

+    public void setTitle(String title){

+        if (created.get())

+            Display.setTitle(title);

+    }

+    

+    private ByteBuffer[] imagesToByteBuffers(Object[] images) {

+        ByteBuffer[] out = new ByteBuffer[images.length];

+        for (int i = 0; i < images.length; i++) {

+            BufferedImage image = (BufferedImage) images[i];

+            out[i] = imageToByteBuffer(image);

+        }

+        return out;

+    }

+

+    private ByteBuffer imageToByteBuffer(BufferedImage image) {

+        if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) {

+            BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);

+            Graphics2D g = convertedImage.createGraphics();

+            double width = image.getWidth() * (double) 1;

+            double height = image.getHeight() * (double) 1;

+            g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2),

+                    (int) ((convertedImage.getHeight() - height) / 2),

+                    (int) (width), (int) (height), null);

+            g.dispose();

+            image = convertedImage;

+        }

+

+        byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4];

+        int counter = 0;

+        for (int i = 0; i < image.getHeight(); i++) {

+            for (int j = 0; j < image.getWidth(); j++) {

+                int colorSpace = image.getRGB(j, i);

+                imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24);

+                imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24);

+                imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24);

+                imageBuffer[counter + 3] = (byte) (colorSpace >> 24);

+                counter += 4;

+            }

+        }

+        return ByteBuffer.wrap(imageBuffer);

+    }

+

+}

diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java
new file mode 100644
index 0000000..9124531
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java
@@ -0,0 +1,198 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.system.lwjgl;

+

+import com.jme3.input.JoyInput;

+import com.jme3.input.KeyInput;

+import com.jme3.input.MouseInput;

+import com.jme3.input.TouchInput;

+import com.jme3.input.dummy.DummyKeyInput;

+import com.jme3.input.dummy.DummyMouseInput;

+import java.util.concurrent.atomic.AtomicBoolean;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import org.lwjgl.LWJGLException;

+import org.lwjgl.Sys;

+import org.lwjgl.opengl.*;

+

+public class LwjglOffscreenBuffer extends LwjglContext implements Runnable {

+

+    private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName());

+    private Pbuffer pbuffer;

+    protected AtomicBoolean needClose = new AtomicBoolean(false);

+    private int width;

+    private int height;

+    private PixelFormat pixelFormat;

+

+    protected void initInThread(){

+        if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0){

+            logger.severe("Offscreen surfaces are not supported.");

+            return;

+        }

+

+	   int samples = 0;

+         if (settings.getSamples() > 1){

+              samples = settings.getSamples();

+        }

+        pixelFormat = new PixelFormat(settings.getBitsPerPixel(),

+                                      0,

+                                      settings.getDepthBits(),

+                                      settings.getStencilBits(),

+                                      settings.getSamples());

+        

+        width = settings.getWidth();

+        height = settings.getHeight();

+        try{

+            Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

+                public void uncaughtException(Thread thread, Throwable thrown) {

+                    listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);

+                }

+            });

+

+            pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs());

+            pbuffer.makeCurrent();

+

+            renderable.set(true);

+

+            logger.info("Offscreen buffer created.");

+            printContextInitInfo();

+        } catch (LWJGLException ex){

+            listener.handleError("Failed to create display", ex);

+        } finally {

+            // TODO: It is possible to avoid "Failed to find pixel format"

+            // error here by creating a default display.

+        }

+        super.internalCreate();

+        listener.initialize();

+    }

+

+    protected boolean checkGLError(){

+        try {

+            Util.checkGLError();

+        } catch (OpenGLException ex){

+            listener.handleError("An OpenGL error has occured!", ex);

+        }

+        // NOTE: Always return true since this is used in an "assert" statement

+        return true;

+    }

+

+    protected void runLoop(){

+        if (!created.get())

+            throw new IllegalStateException();

+

+        if (pbuffer.isBufferLost()){

+            pbuffer.destroy();

+            try{

+                pbuffer = new Pbuffer(width, height, pixelFormat, null);

+                pbuffer.makeCurrent();

+            }catch (LWJGLException ex){

+                listener.handleError("Failed to restore pbuffer content", ex);

+            }

+        }

+

+        listener.update();

+        assert checkGLError();

+        

+        renderer.onFrame();

+        

+        int frameRate = settings.getFrameRate();

+        if (frameRate >= 1){

+            Display.sync(frameRate);

+        }

+    }

+

+    protected void deinitInThread(){

+        renderable.set(false);

+        

+        listener.destroy();

+        renderer.cleanup();

+        pbuffer.destroy();

+        logger.info("Offscreen buffer destroyed.");

+    }

+

+    public void run(){

+        logger.log(Level.INFO, "Using LWJGL {0}", Sys.getVersion());

+        initInThread();

+        while (!needClose.get()){

+            runLoop();

+        }

+        deinitInThread();

+    }

+

+    public void destroy(boolean waitFor){

+        needClose.set(true);

+        if (waitFor)

+            waitFor(false);

+    }

+

+    public void create(boolean waitFor){

+        if (created.get()){

+            logger.warning("create() called when pbuffer is already created!");

+            return;

+        }

+

+        new Thread(this, "LWJGL Renderer Thread").start();

+        if (waitFor)

+            waitFor(true);

+    }

+

+    public void restart() {

+    }

+

+    public void setAutoFlushFrames(boolean enabled){

+    }

+

+    public Type getType() {

+        return Type.OffscreenSurface;

+    }

+

+    public MouseInput getMouseInput() {

+        return new DummyMouseInput();

+    }

+

+    public KeyInput getKeyInput() {

+        return new DummyKeyInput();

+    }

+

+    public JoyInput getJoyInput() {

+        return null;

+    }

+    

+    public TouchInput getTouchInput() {

+        return null;

+    }

+

+    public void setTitle(String title) {

+    }

+

+}

diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglSmoothingTimer.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglSmoothingTimer.java
new file mode 100644
index 0000000..2c9b80a
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglSmoothingTimer.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.math.FastMath;
+import com.jme3.system.Timer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.Sys;
+
+/**
+ * <code>Timer</code> handles the system's time related functionality. This
+ * allows the calculation of the framerate. To keep the framerate calculation
+ * accurate, a call to update each frame is required. <code>Timer</code> is a
+ * singleton object and must be created via the <code>getTimer</code> method.
+ *
+ * @author Mark Powell
+ * @version $Id: LWJGLTimer.java,v 1.21 2007/09/22 16:46:35 irrisor Exp $
+ */
+public class LwjglSmoothingTimer extends Timer {
+    private static final Logger logger = Logger.getLogger(LwjglSmoothingTimer.class
+            .getName());
+
+    private long lastFrameDiff;
+
+    //frame rate parameters.
+    private long oldTime;
+
+    private float lastTPF, lastFPS;
+
+    public static int TIMER_SMOOTHNESS = 32;
+
+    private long[] tpf;
+
+    private int smoothIndex;
+
+    private final static long LWJGL_TIMER_RES = Sys.getTimerResolution();
+    private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES );
+    private static float invTimerRezSmooth;
+
+    public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES);
+
+    private long startTime;
+
+    private boolean allSmooth = false;
+
+    /**
+     * Constructor builds a <code>Timer</code> object. All values will be
+     * initialized to it's default values.
+     */
+    public LwjglSmoothingTimer() {
+        reset();
+
+        //print timer resolution info
+        logger.log(Level.INFO, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES);
+    }
+
+    public void reset() {
+        lastFrameDiff = 0;
+        lastFPS = 0;
+        lastTPF = 0;
+
+        // init to -1 to indicate this is a new timer.
+        oldTime = -1;
+        //reset time
+        startTime = Sys.getTime();
+
+        tpf = new long[TIMER_SMOOTHNESS];
+        smoothIndex = TIMER_SMOOTHNESS - 1;
+        invTimerRezSmooth = ( 1f / (LWJGL_TIMER_RES * TIMER_SMOOTHNESS));
+
+        // set tpf... -1 values will not be used for calculating the average in update()
+        for ( int i = tpf.length; --i >= 0; ) {
+            tpf[i] = -1;
+        }
+    }
+
+    /**
+     * @see com.jme.util.Timer#getTime()
+     */
+    public long getTime() {
+        return Sys.getTime() - startTime;
+    }
+
+    /**
+     * @see com.jme.util.Timer#getResolution()
+     */
+    public long getResolution() {
+        return LWJGL_TIMER_RES;
+    }
+
+    /**
+     * <code>getFrameRate</code> returns the current frame rate since the last
+     * call to <code>update</code>.
+     *
+     * @return the current frame rate.
+     */
+    public float getFrameRate() {
+        return lastFPS;
+    }
+
+    public float getTimePerFrame() {
+        return lastTPF;
+    }
+
+    /**
+     * <code>update</code> recalulates the frame rate based on the previous
+     * call to update. It is assumed that update is called each frame.
+     */
+    public void update() {
+        long newTime = Sys.getTime();
+        long oldTime = this.oldTime;
+        this.oldTime = newTime;
+        if ( oldTime == -1 ) {
+            // For the first frame use 60 fps. This value will not be counted in further averages.
+            // This is done so initialization code between creating the timer and the first
+            // frame is not counted as a single frame on it's own.
+            lastTPF = 1 / 60f;
+            lastFPS = 1f / lastTPF;
+            return;
+        }
+
+        long frameDiff = newTime - oldTime;
+        long lastFrameDiff = this.lastFrameDiff;
+        if ( lastFrameDiff > 0 && frameDiff > lastFrameDiff *100 ) {
+            frameDiff = lastFrameDiff *100;
+        }
+        this.lastFrameDiff = frameDiff;
+        tpf[smoothIndex] = frameDiff;
+        smoothIndex--;
+        if ( smoothIndex < 0 ) {
+            smoothIndex = tpf.length - 1;
+        }
+
+        lastTPF = 0.0f;
+        if (!allSmooth) {
+            int smoothCount = 0;
+            for ( int i = tpf.length; --i >= 0; ) {
+                if ( tpf[i] != -1 ) {
+                    lastTPF += tpf[i];
+                    smoothCount++;
+                }
+            }
+            if (smoothCount == tpf.length)
+                allSmooth  = true;
+            lastTPF *= ( INV_LWJGL_TIMER_RES / smoothCount );
+        } else {
+            for ( int i = tpf.length; --i >= 0; ) {
+                if ( tpf[i] != -1 ) {
+                    lastTPF += tpf[i];
+                }
+            }
+            lastTPF *= invTimerRezSmooth;
+        }
+        if ( lastTPF < FastMath.FLT_EPSILON ) {
+            lastTPF = FastMath.FLT_EPSILON;
+        }
+
+        lastFPS = 1f / lastTPF;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this timer
+     * in the format: <br>
+     * <br>
+     * jme.utility.Timer@1db699b <br>
+     * Time: {LONG} <br>
+     * FPS: {LONG} <br>
+     *
+     * @return the string representation of this object.
+     */
+    @Override
+    public String toString() {
+        String string = super.toString();
+        string += "\nTime: " + oldTime;
+        string += "\nFPS: " + getFrameRate();
+        return string;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglTimer.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglTimer.java
new file mode 100644
index 0000000..681f9de
--- /dev/null
+++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglTimer.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.system.Timer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.lwjgl.Sys;
+
+/**
+ * <code>Timer</code> handles the system's time related functionality. This
+ * allows the calculation of the framerate. To keep the framerate calculation
+ * accurate, a call to update each frame is required. <code>Timer</code> is a
+ * singleton object and must be created via the <code>getTimer</code> method.
+ *
+ * @author Mark Powell
+ * @version $Id: LWJGLTimer.java,v 1.21 2007/09/22 16:46:35 irrisor Exp $
+ */
+public class LwjglTimer extends Timer {
+    private static final Logger logger = Logger.getLogger(LwjglTimer.class
+            .getName());
+
+    //frame rate parameters.
+    private long oldTime;
+    private long startTime;
+
+    private float lastTPF, lastFPS;
+
+    private final static long LWJGL_TIMER_RES = Sys.getTimerResolution();
+    private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES );
+
+    public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES);
+
+    /**
+     * Constructor builds a <code>Timer</code> object. All values will be
+     * initialized to it's default values.
+     */
+    public LwjglTimer() {
+        reset();
+        logger.log(Level.INFO, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES);
+    }
+
+    public void reset() {
+        startTime = Sys.getTime();
+        oldTime = getTime();
+    }
+
+    @Override
+    public float getTimeInSeconds() {
+        return getTime() * INV_LWJGL_TIMER_RES;
+    }
+
+    /**
+     * @see com.jme.util.Timer#getTime()
+     */
+    public long getTime() {
+        return Sys.getTime() - startTime;
+    }
+
+    /**
+     * @see com.jme.util.Timer#getResolution()
+     */
+    public long getResolution() {
+        return LWJGL_TIMER_RES;
+    }
+
+    /**
+     * <code>getFrameRate</code> returns the current frame rate since the last
+     * call to <code>update</code>.
+     *
+     * @return the current frame rate.
+     */
+    public float getFrameRate() {
+        return lastFPS;
+    }
+
+    public float getTimePerFrame() {
+        return lastTPF;
+    }
+
+    /**
+     * <code>update</code> recalulates the frame rate based on the previous
+     * call to update. It is assumed that update is called each frame.
+     */
+    public void update() {
+        long curTime = getTime();
+        lastTPF = (curTime - oldTime) * (1.0f / LWJGL_TIMER_RES);
+        lastFPS = 1.0f / lastTPF;
+        oldTime = curTime;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this timer
+     * in the format: <br>
+     * <br>
+     * jme.utility.Timer@1db699b <br>
+     * Time: {LONG} <br>
+     * FPS: {LONG} <br>
+     *
+     * @return the string representation of this object.
+     */
+    @Override
+    public String toString() {
+        String string = super.toString();
+        string += "\nTime: " + oldTime;
+        string += "\nFPS: " + getFrameRate();
+        return string;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/networking/com/jme3/network/AbstractMessage.java b/engine/src/networking/com/jme3/network/AbstractMessage.java
new file mode 100644
index 0000000..7b04fa3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/AbstractMessage.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.serializing.Serializable;
+
+/**
+ *  Interface implemented by all network messages.
+ *
+ *  @version   $Revision: 7070 $
+ *  @author    Paul Speed
+ */
+@Serializable()
+public abstract class AbstractMessage implements Message
+{
+    private transient boolean reliable = true;
+
+    protected AbstractMessage()
+    {
+    }
+
+    protected AbstractMessage( boolean reliable )
+    {
+        this.reliable = reliable; 
+    }
+    
+    /**
+     *  Sets this message to 'reliable' or not and returns this
+     *  message.
+     */
+    public Message setReliable(boolean f)
+    {
+        this.reliable = f;
+        return this;
+    }
+    
+    /**
+     *  Indicates which way an outgoing message should be sent
+     *  or which way an incoming message was sent.
+     */
+    public boolean isReliable()
+    {
+        return reliable;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/Client.java b/engine/src/networking/com/jme3/network/Client.java
new file mode 100644
index 0000000..86ee8c6
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Client.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Represents a remote connection to a server that can be used
+ *  for sending and receiving messages.
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public interface Client extends MessageConnection
+{
+    /**
+     *  Starts the client allowing it to begin processing incoming
+     *  messages and delivering them to listeners.
+     */
+    public void start();
+
+    /**
+     *  Returns true if this client is fully connected to the
+     *  host.
+     */
+    public boolean isConnected();     
+
+    /**
+     *  Returns a unique ID for this client within the remote
+     *  server or -1 if this client isn't fully connected to the
+     *  server.
+     */
+    public int getId();     
+ 
+    /**
+     *  Returns the 'game name' for servers to which this client should be able
+     *  to connect.  This should match the 'game name' set on the server or this
+     *  client will be turned away.
+     */
+    public String getGameName();
+ 
+    /**
+     *  Returns the game-specific version of the server this client should
+     *  be able to connect to.
+     */   
+    public int getVersion();
+ 
+    /**
+     *  Sends a message to the server.
+     */   
+    public void send( Message message );
+ 
+    /**
+     *  Sends a message to the other end of the connection using
+     *  the specified alternate channel.
+     */   
+    public void send( int channel, Message message );
+ 
+    /**
+     *  Closes this connection to the server.
+     */
+    public void close();         
+
+    /**
+     *  Adds a listener that will be notified about connection
+     *  state changes.
+     */
+    public void addClientStateListener( ClientStateListener listener ); 
+
+    /**
+     *  Removes a previously registered connection listener.
+     */
+    public void removeClientStateListener( ClientStateListener listener ); 
+
+    /**
+     *  Adds a listener that will be notified when any message or object
+     *  is received from the server.
+     */
+    public void addMessageListener( MessageListener<? super Client> listener ); 
+
+    /**
+     *  Adds a listener that will be notified when messages of the specified
+     *  types are received.
+     */
+    public void addMessageListener( MessageListener<? super Client> listener, Class... classes ); 
+
+    /**
+     *  Removes a previously registered wildcard listener.  This does
+     *  not remove this listener from any type-specific registrations.
+     */
+    public void removeMessageListener( MessageListener<? super Client> listener ); 
+
+    /**
+     *  Removes a previously registered type-specific listener from
+     *  the specified types.
+     */
+    public void removeMessageListener( MessageListener<? super Client> listener, Class... classes ); 
+    
+    /**
+     *  Adds a listener that will be notified when any connection errors
+     *  occur.  If a client has no error listeners then the default behavior
+     *  is to close the connection and provide an appropriate DisconnectInfo
+     *  to any ClientStateListeners.  If the application adds its own error
+     *  listeners then it must take care of closing the connection itself.
+     */
+    public void addErrorListener( ErrorListener<? super Client> listener ); 
+
+    /**
+     *  Removes a previously registered error listener.
+     */
+    public void removeErrorListener( ErrorListener<? super Client> listener ); 
+}
+
+
diff --git a/engine/src/networking/com/jme3/network/ClientStateListener.java b/engine/src/networking/com/jme3/network/ClientStateListener.java
new file mode 100644
index 0000000..65ebe11
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/ClientStateListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Listener that is notified about the connection state of
+ *  a Client.
+ *
+ *  @version   $Revision: 7451 $
+ *  @author    Paul Speed
+ */
+public interface ClientStateListener
+{
+    /**
+     *  Called when the specified client is fully connected to
+     *  the remote server.
+     */
+    public void clientConnected( Client c );
+ 
+    /**
+     *  Called when the client has disconnected from the remote
+     *  server.  If info is null then the client shut down the
+     *  connection normally, otherwise the info object contains
+     *  additional information about the disconnect.
+     */   
+    public void clientDisconnected( Client c, DisconnectInfo info );
+ 
+    /**
+     *  Provided with the clientDisconnected() notification to
+     *  include additional information about the disconnect.
+     */   
+    public class DisconnectInfo
+    {
+        public String reason;
+        public Throwable error;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/ConnectionListener.java b/engine/src/networking/com/jme3/network/ConnectionListener.java
new file mode 100644
index 0000000..bcaf1e3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/ConnectionListener.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Listener that is notified about connection arrivals and
+ *  removals within a server.
+ *
+ *  @version   $Revision: 7010 $
+ *  @author    Paul Speed
+ */
+public interface ConnectionListener
+{
+    /**
+     *  Called when a connection has been added to the specified server and
+     *  is fully setup.
+     */
+    public void connectionAdded( Server server, HostedConnection conn );
+    
+    /**
+     *  Called when a connection has been removed from the specified
+     *  server. 
+     */
+    public void connectionRemoved( Server server, HostedConnection conn );
+}
diff --git a/engine/src/networking/com/jme3/network/ErrorListener.java b/engine/src/networking/com/jme3/network/ErrorListener.java
new file mode 100644
index 0000000..af4beae
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/ErrorListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Notified when errors happen on a connection.
+ *
+ *  @version   $Revision: 7451 $
+ *  @author    Paul Speed
+ */
+public interface ErrorListener<S>
+{
+    public void handleError( S source, Throwable t );
+}
diff --git a/engine/src/networking/com/jme3/network/Filter.java b/engine/src/networking/com/jme3/network/Filter.java
new file mode 100644
index 0000000..dff3e94
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Filter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Determines a true or false value for a given input. 
+ *
+ *  @version   $Revision: 7029 $
+ *  @author    Paul Speed
+ */
+public interface Filter<T>
+{
+    /**
+     *  Returns true if the specified input is accepted by this
+     *  filter. 
+     */
+    public boolean apply( T input ); 
+}
+
+
diff --git a/engine/src/networking/com/jme3/network/Filters.java b/engine/src/networking/com/jme3/network/Filters.java
new file mode 100644
index 0000000..c255201
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Filters.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ *  Static utility methods pertaining to Filter instances.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class Filters 
+{
+    /**
+     *  Creates a filter that returns true for any value in the specified
+     *  list of values and false for all other cases.
+     */
+    public static <T> Filter<T> in( T... values )
+    {
+        return in( new HashSet<T>(Arrays.asList(values)) );
+    }
+    
+    /**
+     *  Creates a filter that returns true for any value in the specified
+     *  collection and false for all other cases.
+     */
+    public static <T> Filter<T> in( Collection<? extends T> collection )
+    {
+        return new InFilter<T>(collection);
+    }
+
+    /**
+     *  Creates a filter that returns true for any value NOT in the specified
+     *  list of values and false for all other cases.  This is the equivalent
+     *  of calling not(in(values)).
+     */
+    public static <T> Filter<T> notIn( T... values )
+    {
+        return not( in( values ) );
+    }
+    
+    /**
+     *  Creates a filter that returns true for any value NOT in the specified
+     *  collection and false for all other cases.  This is the equivalent
+     *  of calling not(in(collection)).
+     */
+    public static <T> Filter<T> notIn( Collection<? extends T> collection )
+    {
+        return not( in( collection ) );
+    }
+    
+    /**
+     *  Creates a filter that returns true for inputs that are .equals()
+     *  equivalent to the specified value.
+     */
+    public static <T> Filter<T> equalTo( T value )
+    {
+        return new EqualToFilter<T>(value); 
+    }     
+
+    /**
+     *  Creates a filter that returns true for inputs that are NOT .equals()
+     *  equivalent to the specified value.  This is the equivalent of calling
+     *  not(equalTo(value)).
+     */
+    public static <T> Filter<T> notEqualTo( T value )
+    {
+        return not(equalTo(value));
+    }     
+
+    /**
+     *  Creates a filter that returns true when the specified delegate filter
+     *  returns false, and vice versa.
+     */
+    public static <T> Filter<T> not( Filter<T> f )
+    {
+        return new NotFilter<T>(f);
+    }
+ 
+    private static class EqualToFilter<T> implements Filter<T>
+    {
+        private T value;
+        
+        public EqualToFilter( T value )
+        {
+            this.value = value;
+        }
+        
+        public boolean apply( T input )
+        {
+            return value == input || (value != null && value.equals(input));
+        }
+    }
+    
+    private static class InFilter<T> implements Filter<T>
+    {
+        private Collection<? extends T> collection;
+        
+        public InFilter( Collection<? extends T> collection )
+        {
+            this.collection = collection;
+        }
+        
+        public boolean apply( T input )
+        {
+            return collection.contains(input);
+        } 
+    }
+    
+    private static class NotFilter<T> implements Filter<T>
+    {
+        private Filter<T> delegate;
+        
+        public NotFilter( Filter<T> delegate )
+        {
+            this.delegate = delegate;
+        }
+        
+        public boolean apply( T input )
+        {
+            return !delegate.apply(input);
+        }
+    } 
+}
+
+
diff --git a/engine/src/networking/com/jme3/network/HostedConnection.java b/engine/src/networking/com/jme3/network/HostedConnection.java
new file mode 100644
index 0000000..b6f06cd
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/HostedConnection.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import java.util.Set;
+
+/**
+ *  This is the connection back to a client that is being
+ *  hosted in a server instance.  
+ *
+ *  @version   $Revision: 7485 $
+ *  @author    Paul Speed
+ */
+public interface HostedConnection extends MessageConnection
+{
+    /**
+     *  Returns the Server instance that is hosting this connection.
+     */
+    public Server getServer();     
+
+    /**
+     *  Returns the server-unique ID for this client.
+     */
+    public int getId();
+
+    /**
+     *  Returns the transport specific remote address of this connection
+     *  as a string.  This may or may not be unique per connection depending
+     *  on the type of transport.  It is provided for information and filtering
+     *  purposes. 
+     */
+    public String getAddress();
+   
+    /**
+     *  Closes and removes this connection from the server
+     *  sending the optional reason to the remote client.
+     */
+    public void close( String reason );
+    
+    /**
+     *  Sets a session attribute specific to this connection.  If the value
+     *  is set to null then the attribute is removed.
+     *
+     *  @return The previous session value for this key or null
+     *          if there was no previous value.
+     */
+    public Object setAttribute( String name, Object value );
+    
+    /**
+     *  Retrieves a previosly stored session attribute or
+     *  null if no such attribute exists.
+     */
+    public <T> T getAttribute( String name );
+    
+    /**
+     *  Returns a read-only set of attribute names currently stored
+     *  for this client session.
+     */
+    public Set<String> attributeNames();     
+}
diff --git a/engine/src/networking/com/jme3/network/Message.java b/engine/src/networking/com/jme3/network/Message.java
new file mode 100644
index 0000000..4c3734f
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Message.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.serializing.Serializable;
+
+/**
+ *  Interface implemented by all network messages.
+ *
+ *  @version   $Revision: 7068 $
+ *  @author    Paul Speed
+ */
+@Serializable()
+public interface Message
+{
+    /**
+     *  Sets this message to 'reliable' or not and returns this
+     *  message.
+     */
+    public Message setReliable(boolean f);
+    
+    /**
+     *  Indicates which way an outgoing message should be sent
+     *  or which way an incoming message was sent.
+     */
+    public boolean isReliable();
+}
diff --git a/engine/src/networking/com/jme3/network/MessageConnection.java b/engine/src/networking/com/jme3/network/MessageConnection.java
new file mode 100644
index 0000000..3a25f77
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/MessageConnection.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  The source of a received message and the common abstract interface
+ *  of client->server and server->client objects. 
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public interface MessageConnection
+{
+    /**
+     *  Sends a message to the other end of the connection.
+     */   
+    public void send( Message message );
+    
+    /**
+     *  Sends a message to the other end of the connection using
+     *  the specified alternate channel.
+     */   
+    public void send( int channel, Message message );
+}    
+
diff --git a/engine/src/networking/com/jme3/network/MessageListener.java b/engine/src/networking/com/jme3/network/MessageListener.java
new file mode 100644
index 0000000..36c6c59
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/MessageListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+
+/**
+ *  Listener notified about new messages.
+ *
+ *  <p>Note about multithreading: on the server, these messages may
+ *  be delivered by more than one thread depending on the server
+ *  implementation used.  Listener implementations should treat their
+ *  shared data structures accordingly and set them up for multithreaded 
+ *  access.  The only threading guarantee is that for a single
+ *  HostedConnection, there will only ever be one thread at a time
+ *  and the messages will always be delivered to that connection in the 
+ *  order that they were delivered.</p>   
+ *
+ *  @version   $Revision: 7046 $
+ *  @author    Paul Speed
+ */
+public interface MessageListener<S>
+{
+    public void messageReceived( S source, Message m );
+}
diff --git a/engine/src/networking/com/jme3/network/Network.java b/engine/src/networking/com/jme3/network/Network.java
new file mode 100644
index 0000000..08d90d5
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Network.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.base.DefaultClient;
+import com.jme3.network.base.DefaultServer;
+import com.jme3.network.base.TcpConnectorFactory;
+import com.jme3.network.kernel.tcp.SelectorKernel;
+import com.jme3.network.kernel.tcp.SocketConnector;
+import com.jme3.network.kernel.udp.UdpConnector;
+import com.jme3.network.kernel.udp.UdpKernel;
+import java.io.IOException;
+import java.net.InetAddress;
+
+/**
+ *  The main service provider for conveniently creating
+ *  server and client instances.
+ *
+ *  @version   $Revision: 8979 $
+ *  @author    Paul Speed
+ */
+public class Network
+{
+    public static final String DEFAULT_GAME_NAME = "Unnamed jME3 Game";
+    public static final int DEFAULT_VERSION = 42;
+
+    /**
+     *  Creates a Server that will utilize both reliable and fast
+     *  transports to communicate with clients.  The specified port
+     *  will be used for both TCP and UDP communication.
+     */
+    public static Server createServer( int port ) throws IOException
+    {   
+        return createServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, port, port );
+    }
+
+    /**
+     *  Creates a Server that will utilize both reliable and fast
+     *  transports to communicate with clients.  The specified port
+     *  will be used for both TCP and UDP communication.
+     */
+    public static Server createServer( int tcpPort, int udpPort ) throws IOException
+    {
+        return createServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, tcpPort, udpPort );
+    }
+
+    /**
+     *  Creates a named and versioned Server that will utilize both reliable and fast
+     *  transports to communicate with clients.  The specified port
+     *  will be used for both TCP and UDP communication.
+     *
+     *  @param gameName This is the name that identifies the game.  Connecting clients
+     *                  must use this name or be turned away.
+     *  @param version  This is a game-specific verison that helps detect when out-of-date
+     *                  clients have connected to an incompatible server.
+     *  @param tcpPort  The port upon which the TCP hosting will listen for new connections.
+     *  @param udpPort  The port upon which the UDP hosting will listen for new 'fast' UDP 
+     *                  messages.  Set to -1 if 'fast' traffic should go over TCP.  This will
+     *                  completely disable UDP traffic for this server.
+     */
+    public static Server createServer( String gameName, int version, int tcpPort, int udpPort ) throws IOException
+    {
+        UdpKernel fast = udpPort == -1 ? null : new UdpKernel(udpPort);
+        SelectorKernel reliable = new SelectorKernel(tcpPort);
+ 
+        return new DefaultServer( gameName, version, reliable, fast );       
+    }
+    
+    /**
+     *  Creates a client that can be connected at a later time.
+     */
+    public static NetworkClient createClient()
+    {
+        return createClient( DEFAULT_GAME_NAME, DEFAULT_VERSION );
+    }     
+
+    /**
+     *  Creates a client that can be connected at a later time.  The specified
+     *  game name and version must match the server or the client will be turned
+     *  away.
+     */
+    public static NetworkClient createClient( String gameName, int version )
+    {
+        return new NetworkClientImpl(gameName, version);
+    }     
+    
+    /**
+     *  Creates a Client that communicates with the specified host and port
+     *  using both reliable and fast transports. 
+     */   
+    public static Client connectToServer( String host, int hostPort ) throws IOException
+    {
+        return connectToServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, host, hostPort, hostPort );   
+    }
+
+    /**
+     *  Creates a Client that communicates with the specified host and separate TCP and UDP ports
+     *  using both reliable and fast transports.
+     */   
+    public static Client connectToServer( String host, int hostPort, int remoteUdpPort ) throws IOException
+    {
+        return connectToServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, host, hostPort, remoteUdpPort );
+    }
+
+    /**
+     *  Creates a Client that communicates with the specified host and port
+     *  using both reliable and fast transports.  
+     */   
+    public static Client connectToServer( String gameName, int version, 
+                                          String host, int hostPort ) throws IOException
+    {
+        return connectToServer( gameName, version, host, hostPort, hostPort );   
+    }
+
+    /**
+     *  Creates a Client that communicates with the specified host and and separate TCP and UDP ports
+     *  using both reliable and fast transports.  
+     *  
+     *  @param gameName This is the name that identifies the game.  This must match
+     *                  the target server's name or this client will be turned away.
+     *  @param version  This is a game-specific verison that helps detect when out-of-date
+     *                  clients have connected to an incompatible server.  This must match
+     *                  the server's version of this client will be turned away.
+     *  @param hostPort  The remote TCP port on the server to which this client should
+     *                  send reliable messages. 
+     *  @param remoteUdpPort  The remote UDP port on the server to which this client should
+     *                  send 'fast'/unreliable messages.   Set to -1 if 'fast' traffic should 
+     *                  go over TCP.  This will completely disable UDP traffic for this
+     *                  client.
+     */   
+    public static Client connectToServer( String gameName, int version, 
+                                          String host, int hostPort, int remoteUdpPort ) throws IOException
+    {
+        InetAddress remoteAddress = InetAddress.getByName(host);   
+        UdpConnector fast = remoteUdpPort == -1 ? null : new UdpConnector( remoteAddress, remoteUdpPort ); 
+        SocketConnector reliable = new SocketConnector( remoteAddress, hostPort );        
+       
+        return new DefaultClient( gameName, version, reliable, fast, new TcpConnectorFactory(remoteAddress) );
+    }
+ 
+ 
+    protected static class NetworkClientImpl extends DefaultClient implements NetworkClient
+    {
+        public NetworkClientImpl(String gameName, int version)
+        {
+            super( gameName, version );
+        }
+        
+        public void connectToServer( String host, int port, int remoteUdpPort ) throws IOException
+        {
+            connectToServer( InetAddress.getByName(host), port, remoteUdpPort );
+        }                                     
+                                 
+        public void connectToServer( InetAddress address, int port, int remoteUdpPort ) throws IOException
+        {
+            UdpConnector fast = new UdpConnector( address, remoteUdpPort ); 
+            SocketConnector reliable = new SocketConnector( address, port );        
+            
+            setPrimaryConnectors( reliable, fast, new TcpConnectorFactory(address) );
+        }                                             
+    }   
+}
diff --git a/engine/src/networking/com/jme3/network/NetworkClient.java b/engine/src/networking/com/jme3/network/NetworkClient.java
new file mode 100644
index 0000000..2cd2cd7
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/NetworkClient.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+/**
+ *  A Client whose network connection information can 
+ *  be provided post-creation.  The actual connection stack
+ *  will be setup the same as if Network.connectToServer
+ *  had been called.  
+ *
+ *  @version   $Revision: 8979 $
+ *  @author    Paul Speed
+ */
+public interface NetworkClient extends Client
+{
+    /**
+     *  Connects this client to the specified remote server and ports.
+     */
+    public void connectToServer( String host, int port, int remoteUdpPort ) throws IOException;
+ 
+    /**
+     *  Connects this client to the specified remote server and ports.
+     *  
+     *  @param address  The hosts internet address.
+     *  @param port  The remote TCP port on the server to which this client should
+     *                  send reliable messages. 
+     *  @param remoteUdpPort  The remote UDP port on the server to which this client should
+     *                  send 'fast'/unreliable messages.   Set to -1 if 'fast' traffic should 
+     *                  go over TCP.  This will completely disable UDP traffic for this
+     *                  client.
+     */                               
+    public void connectToServer( InetAddress address, int port, int remoteUdpPort ) throws IOException;
+    
+}
diff --git a/engine/src/networking/com/jme3/network/Server.java b/engine/src/networking/com/jme3/network/Server.java
new file mode 100644
index 0000000..850cf29
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/Server.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import java.util.Collection;
+
+/**
+ *  Represents a host that can send and receive messages to
+ *  a set of remote client connections.
+ *  
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public interface Server
+{
+    /**
+     *  Returns the 'game name' for this server.  This should match the
+     *  'game name' set on connecting clients or they will be turned away.
+     */
+    public String getGameName();
+ 
+    /**
+     *  Returns the game-specific version of this server used for detecting
+     *  mismatched clients.
+     */   
+    public int getVersion();
+
+    /**
+     *  Sends the specified message to all connected clients.
+     */ 
+    public void broadcast( Message message );
+
+    /**
+     *  Sends the specified message to all connected clients that match
+     *  the filter.  If no filter is specified then this is the same as
+     *  calling broadcast(message) and the message will be delivered to
+     *  all connections.
+     *  <p>Examples:</p>
+     *  <pre>
+     *    // Broadcast to connections: conn1, conn2, and conn3
+     *    server.broadcast( Filters.in( conn1, conn2, conn3 ), message );
+     *
+     *    // Broadcast to all connections exception source
+     *    server.broadcast( Filters.notEqualTo( source ), message );
+     *  </pre>
+     */ 
+    public void broadcast( Filter<? super HostedConnection> filter, Message message );
+
+    /**
+     *  Sends the specified message over the specified alternate channel to all connected 
+     *  clients that match the filter.  If no filter is specified then this is the same as
+     *  calling broadcast(message) and the message will be delivered to
+     *  all connections.
+     *  <p>Examples:</p>
+     *  <pre>
+     *    // Broadcast to connections: conn1, conn2, and conn3
+     *    server.broadcast( Filters.in( conn1, conn2, conn3 ), message );
+     *
+     *    // Broadcast to all connections exception source
+     *    server.broadcast( Filters.notEqualTo( source ), message );
+     *  </pre>
+     */ 
+    public void broadcast( int channel, Filter<? super HostedConnection> filter, Message message );
+
+    /**
+     *  Start the server so that it will began accepting new connections
+     *  and processing messages.
+     */
+    public void start();
+
+    /**
+     *  Adds an alternate channel to the server, using the specified port.  This is an 
+     *  entirely separate connection where messages are sent and received in parallel 
+     *  to the two primary default channels.  All channels must be added before the connection
+     *  is started.
+     *  Returns the ID of the created channel for use when specifying the channel in send or 
+     *  broadcast calls.  The ID is returned entirely out of convenience since the IDs
+     *  are predictably incremented.  The first channel is 0, second is 1, and so on.
+     */
+    public int addChannel( int port ); 
+
+    /**
+     *  Returns true if the server has been started.
+     */
+    public boolean isRunning();     
+ 
+    /**
+     *  Closes all client connections, stops and running processing threads, and
+     *  closes the host connection.
+     */   
+    public void close();
+ 
+    /**
+     *  Retrieves a hosted connection by ID.
+     */
+    public HostedConnection getConnection( int id );     
+ 
+    /**
+     *  Retrieves a read-only collection of all currently connected connections.
+     */
+    public Collection<HostedConnection> getConnections(); 
+ 
+    /**
+     *  Returns true if the server has active connections at the time of this
+     *  call.
+     */
+    public boolean hasConnections();
+    
+    /**
+     *  Adds a listener that will be notified when new hosted connections
+     *  arrive.
+     */
+    public void addConnectionListener( ConnectionListener listener );
+ 
+    /**
+     *  Removes a previously registered connection listener.
+     */   
+    public void removeConnectionListener( ConnectionListener listener );     
+    
+    /**
+     *  Adds a listener that will be notified when any message or object
+     *  is received from one of the clients.
+     *
+     *  <p>Note about MessageListener multithreading: on the server, message events may
+     *  be delivered by more than one thread depending on the server
+     *  implementation used.  Listener implementations should treat their
+     *  shared data structures accordingly and set them up for multithreaded 
+     *  access.  The only threading guarantee is that for a single
+     *  HostedConnection, there will only ever be one thread at a time
+     *  and the messages will always be delivered to that connection in the 
+     *  order that they were delivered.  This is the only restriction placed
+     *  upon server message dispatch pool implementations.</p>   
+     */
+    public void addMessageListener( MessageListener<? super HostedConnection> listener ); 
+
+    /**
+     *  Adds a listener that will be notified when messages of the specified
+     *  types are received from one of the clients.
+     */
+    public void addMessageListener( MessageListener<? super HostedConnection> listener, Class... classes ); 
+
+    /**
+     *  Removes a previously registered wildcard listener.  This does
+     *  not remove this listener from any type-specific registrations.
+     */
+    public void removeMessageListener( MessageListener<? super HostedConnection> listener ); 
+
+    /**
+     *  Removes a previously registered type-specific listener from
+     *  the specified types.
+     */
+    public void removeMessageListener( MessageListener<? super HostedConnection> listener, Class... classes ); 
+    
+     
+}
+
diff --git a/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java b/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java
new file mode 100644
index 0000000..3827786
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/ConnectorAdapter.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.ErrorListener;
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.kernel.ConnectorException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ *  Wraps a single Connector and forwards new messages
+ *  to the supplied message dispatcher.  This is used
+ *  by DefaultClient to manage its connector objects.
+ *  This is only responsible for message reading and provides
+ *  no support for buffering writes.
+ *
+ *  <p>This adapter assumes a simple protocol where two
+ *  bytes define a (short) object size with the object data
+ *  to follow.  Note: this limits the size of serialized
+ *  objects to 32676 bytes... even though, for example,
+ *  datagram packets can hold twice that. :P</p>  
+ *
+ *  @version   $Revision: 8944 $
+ *  @author    Paul Speed
+ */
+public class ConnectorAdapter extends Thread
+{
+    private static final int OUTBOUND_BACKLOG = 16000;
+
+    private Connector connector;
+    private MessageListener<Object> dispatcher;
+    private ErrorListener<Object> errorHandler;
+    private AtomicBoolean go = new AtomicBoolean(true);
+
+    private BlockingQueue<ByteBuffer> outbound;
+     
+    // Writes messages out on a background thread
+    private WriterThread writer;
+   
+    // Marks the messages as reliable or not if they came
+    // through this connector.
+    private boolean reliable;
+ 
+    public ConnectorAdapter( Connector connector, MessageListener<Object> dispatcher, 
+                             ErrorListener<Object> errorHandler, boolean reliable )
+    {
+        super( String.valueOf(connector) );
+        this.connector = connector;        
+        this.dispatcher = dispatcher;
+        this.errorHandler = errorHandler;
+        this.reliable = reliable;
+        setDaemon(true);
+ 
+        // The backlog makes sure that the outbound channel blocks once
+        // a certain backlog level is reached.  It is set high so that it
+        // is only reached in the worst cases... which are usually things like
+        // raw throughput tests.  Technically, a saturated TCP channel could
+        // back up quite a bit if the buffers are full and the socket has
+        // stalled but 16,000 messages is still a big backlog.       
+        outbound = new ArrayBlockingQueue<ByteBuffer>(OUTBOUND_BACKLOG); 
+ 
+        // Note: this technically adds a potential deadlock case
+        // with the above code where there wasn't one before.  For example,
+        // if a TCP outbound queue fills to capacity and a client sends
+        // in such a way that they block TCP message handling then if the HostedConnection
+        // on the server is similarly blocked then the TCP network buffers may
+        // all get full and no outbound messages move and we forever block
+        // on the queue.
+        // However, in practice this can't really happen... or at least it's
+        // the sign of other really bad things.
+        // First, currently the server-side outbound queues are all unbounded and
+        // so won't ever block the handling of messages if the outbound channel is full.
+        // Second, there would have to be a huge amount of data backlog for this
+        // to ever occur anyway.
+        // Third, it's a sign of a really poor architecture if 16,000 messages
+        // can go out in a way that blocks reads. 
+        
+        writer = new WriterThread();
+        writer.start();                                           
+    }
+ 
+    public void close()
+    {
+        go.set(false);
+
+        // Kill the writer service
+        writer.shutdown();
+ 
+        if( connector.isConnected() )
+            {       
+            // Kill the connector
+            connector.close();
+            }
+    }
+ 
+    protected void dispatch( Message m )
+    {
+        dispatcher.messageReceived( null, m );                        
+    }
+ 
+    public void write( ByteBuffer data )
+    {
+        try {
+            outbound.put( data );
+        } catch( InterruptedException e ) {
+            throw new RuntimeException( "Interrupted while waiting for queue to drain", e );
+        }
+    }
+ 
+    protected void handleError( Exception e )
+    {
+        if( !go.get() )
+            return;
+        
+        errorHandler.handleError( this, e );
+    }
+ 
+    public void run()
+    {
+        MessageProtocol protocol = new MessageProtocol();
+ 
+        try {                  
+            while( go.get() ) {
+                ByteBuffer buffer = connector.read();
+                if( buffer == null ) {
+                    if( go.get() ) {
+                        throw new ConnectorException( "Connector closed." ); 
+                    } else {
+                        // Just dump out because a null buffer is expected
+                        // from a closed/closing connector
+                        break;
+                    }
+                }
+                
+                protocol.addBuffer( buffer );
+                
+                Message m = null;
+                while( (m = protocol.getMessage()) != null ) {
+                    m.setReliable( reliable );
+                    dispatch( m );
+                }
+            }
+        } catch( Exception e ) {
+            handleError( e );
+        }            
+    }
+ 
+    protected class WriterThread extends Thread
+    {
+        public WriterThread()
+        {
+            super( String.valueOf(connector) + "-writer" );
+        }
+
+        public void shutdown()
+        {
+            interrupt();
+        }
+ 
+        private void write( ByteBuffer data )
+        {
+            try {                                        
+                connector.write(data);
+            } catch( Exception e ) {
+                handleError( e );
+            }
+        }
+        
+        public void run()
+        {
+            while( go.get() ) {
+                try {           
+                    ByteBuffer data = outbound.take();
+                    write(data);                                       
+                } catch( InterruptedException e ) {
+                    if( !go.get() )
+                        return;
+                    throw new RuntimeException( "Interrupted waiting for data", e );
+                } 
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/base/ConnectorFactory.java b/engine/src/networking/com/jme3/network/base/ConnectorFactory.java
new file mode 100644
index 0000000..d9bb700
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/ConnectorFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.kernel.Connector;
+import java.io.IOException;
+
+
+/**
+ *  Creates Connectors for a specific host.
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public interface ConnectorFactory
+{
+    public Connector createConnector( int channel, int port ) throws IOException;
+}
diff --git a/engine/src/networking/com/jme3/network/base/DefaultClient.java b/engine/src/networking/com/jme3/network/base/DefaultClient.java
new file mode 100644
index 0000000..e03c3e4
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/DefaultClient.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.ClientStateListener.DisconnectInfo;
+import com.jme3.network.*;
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.message.ChannelInfoMessage;
+import com.jme3.network.message.ClientRegistrationMessage;
+import com.jme3.network.message.DisconnectMessage;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  A default implementation of the Client interface that delegates
+ *  its network connectivity to a kernel.Connector.
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public class DefaultClient implements Client
+{
+    static Logger log = Logger.getLogger(DefaultClient.class.getName());
+    
+    // First two channels are reserved for reliable and
+    // unreliable.  Note: channels are endpoint specific so these
+    // constants and the handling need not have anything to do with
+    // the same constants in DefaultServer... which is why they are
+    // separate.
+    private static final int CH_RELIABLE = 0;
+    private static final int CH_UNRELIABLE = 1;
+    private static final int CH_FIRST = 2;
+        
+    private ThreadLocal<ByteBuffer> dataBuffer = new ThreadLocal<ByteBuffer>();
+    
+    private int id = -1;
+    private boolean isRunning = false;
+    private CountDownLatch connecting = new CountDownLatch(1);
+    private String gameName;
+    private int version;
+    private MessageListenerRegistry<Client> messageListeners = new MessageListenerRegistry<Client>();
+    private List<ClientStateListener> stateListeners = new CopyOnWriteArrayList<ClientStateListener>();
+    private List<ErrorListener<? super Client>> errorListeners = new CopyOnWriteArrayList<ErrorListener<? super Client>>();
+    private Redispatch dispatcher = new Redispatch();
+    private List<ConnectorAdapter> channels = new ArrayList<ConnectorAdapter>();    
+ 
+    private ConnectorFactory connectorFactory;
+    
+    public DefaultClient( String gameName, int version )
+    {
+        this.gameName = gameName;
+        this.version = version;
+    }
+    
+    public DefaultClient( String gameName, int version, Connector reliable, Connector fast,
+                          ConnectorFactory connectorFactory )
+    {
+        this( gameName, version );
+        setPrimaryConnectors( reliable, fast, connectorFactory );
+    }
+
+    protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory )
+    {
+        if( reliable == null )
+            throw new IllegalArgumentException( "The reliable connector cannot be null." );            
+        if( isRunning )
+            throw new IllegalStateException( "Client is already started." );
+        if( !channels.isEmpty() )
+            throw new IllegalStateException( "Channels already exist." );
+            
+        this.connectorFactory = connectorFactory;
+        channels.add(new ConnectorAdapter(reliable, dispatcher, dispatcher, true));
+        if( fast != null ) {
+            channels.add(new ConnectorAdapter(fast, dispatcher, dispatcher, false));
+        } else {
+            // Add the null adapter to keep the indexes right
+            channels.add(null);
+        }
+    }  
+
+    protected void checkRunning()
+    {
+        if( !isRunning )
+            throw new IllegalStateException( "Client is not started." );
+    }
+ 
+    public void start()
+    {
+        if( isRunning )
+            throw new IllegalStateException( "Client is already started." );
+            
+        // Start up the threads and stuff for the
+        // connectors that we have
+        for( ConnectorAdapter ca : channels ) {
+            if( ca == null )
+                continue;
+            ca.start();
+        }
+
+        // Send our connection message with a generated ID until
+        // we get one back from the server.  We'll hash time in
+        // millis and time in nanos.
+        // This is used to match the TCP and UDP endpoints up on the
+        // other end since they may take different routes to get there.
+        // Behind NAT, many game clients may be coming over the same
+        // IP address from the server's perspective and they may have
+        // their UDP ports mapped all over the place.
+        //
+        // Since currentTimeMillis() is absolute time and nano time
+        // is roughtly related to system start time, adding these two
+        // together should be plenty unique for our purposes.  It wouldn't
+        // hurt to reconcile with IP on the server side, though.
+        long tempId = System.currentTimeMillis() + System.nanoTime();
+
+        // Set it true here so we can send some messages.
+        isRunning = true;        
+                
+        ClientRegistrationMessage reg;
+        reg = new ClientRegistrationMessage();
+        reg.setId(tempId);
+        reg.setGameName(getGameName());
+        reg.setVersion(getVersion());
+        reg.setReliable(true);
+        send(CH_RELIABLE, reg, false);
+        
+        // Send registration messages to any other configured
+        // connectors
+        reg = new ClientRegistrationMessage();
+        reg.setId(tempId);
+        reg.setReliable(false);
+        for( int ch = CH_UNRELIABLE; ch < channels.size(); ch++ ) {
+            if( channels.get(ch) == null )
+                continue;
+            send(ch, reg, false);
+        }
+    }
+
+    protected void waitForConnected()
+    {
+        if( isConnected() )
+            return;
+            
+        try {
+            connecting.await();
+        } catch( InterruptedException e ) {
+            throw new RuntimeException( "Interrupted waiting for connect", e );
+        }
+    }
+
+    public boolean isConnected()
+    {
+        return id != -1 && isRunning; 
+    }     
+
+    public int getId()
+    {   
+        return id;
+    }     
+ 
+    public String getGameName()
+    {
+        return gameName;
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+   
+    public void send( Message message )
+    {
+        if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) {
+            send(CH_RELIABLE, message, true);
+        } else {
+            send(CH_UNRELIABLE, message, true);
+        }
+    }
+ 
+    public void send( int channel, Message message )
+    {
+        if( channel < 0 || channel + CH_FIRST >= channels.size() )
+            throw new IllegalArgumentException( "Channel is undefined:" + channel );
+        send( channel + CH_FIRST, message, true );
+    }
+    
+    protected void send( int channel, Message message, boolean waitForConnected )
+    {
+        checkRunning();
+ 
+        if( waitForConnected ) {       
+            // Make sure we aren't still connecting
+            waitForConnected();
+        }
+        
+        ByteBuffer buffer = dataBuffer.get();
+        if( buffer == null ) {
+            buffer = ByteBuffer.allocate( 65536 + 2 );
+            dataBuffer.set(buffer);
+        }
+        buffer.clear();        
+ 
+        // Convert the message to bytes
+        buffer = MessageProtocol.messageToBuffer(message, buffer);
+                
+        // Since we share the buffer between invocations, we will need to 
+        // copy this message's part out of it.  This is because we actually
+        // do the send on a background thread.       
+        byte[] temp = new byte[buffer.remaining()];
+        System.arraycopy(buffer.array(), buffer.position(), temp, 0, buffer.remaining());
+        buffer = ByteBuffer.wrap(temp);
+        
+        channels.get(channel).write(buffer);
+    }
+ 
+    public void close()
+    {
+        checkRunning();
+ 
+        closeConnections( null );            
+    }         
+
+    protected void closeConnections( DisconnectInfo info )
+    {
+        if( !isRunning )
+            return;
+
+        // Send a close message
+    
+        // Tell the thread it's ok to die
+        for( ConnectorAdapter ca : channels ) {
+            if( ca == null )
+                continue;
+            ca.close();
+        }
+        
+        // Wait for the threads?
+
+        // Just in case we never fully connected
+        connecting.countDown();
+        
+        fireDisconnected(info);
+        
+        isRunning = false;
+    }         
+
+    public void addClientStateListener( ClientStateListener listener )
+    {
+        stateListeners.add( listener );
+    } 
+
+    public void removeClientStateListener( ClientStateListener listener )
+    {
+        stateListeners.remove( listener );
+    } 
+
+    public void addMessageListener( MessageListener<? super Client> listener )
+    {
+        messageListeners.addMessageListener( listener );
+    } 
+
+    public void addMessageListener( MessageListener<? super Client> listener, Class... classes )
+    {
+        messageListeners.addMessageListener( listener, classes );
+    } 
+
+    public void removeMessageListener( MessageListener<? super Client> listener )
+    {
+        messageListeners.removeMessageListener( listener );
+    } 
+
+    public void removeMessageListener( MessageListener<? super Client> listener, Class... classes )
+    {
+        messageListeners.removeMessageListener( listener, classes );
+    } 
+
+    public void addErrorListener( ErrorListener<? super Client> listener )
+    {
+        errorListeners.add( listener );
+    } 
+
+    public void removeErrorListener( ErrorListener<? super Client> listener )
+    {
+        errorListeners.remove( listener );
+    } 
+ 
+    protected void fireConnected()
+    {
+        for( ClientStateListener l : stateListeners ) {
+            l.clientConnected( this );
+        }            
+    }
+    
+    protected void fireDisconnected( DisconnectInfo info )
+    {
+        for( ClientStateListener l : stateListeners ) {
+            l.clientDisconnected( this, info );
+        }            
+    }
+ 
+    /**
+     *  Either calls the ErrorListener or closes the connection
+     *  if there are no listeners.  
+     */ 
+    protected void handleError( Throwable t )
+    {
+        // If there are no listeners then close the connection with
+        // a reason
+        if( errorListeners.isEmpty() ) {
+            log.log( Level.SEVERE, "Termining connection due to unhandled error", t );
+            DisconnectInfo info = new DisconnectInfo();
+            info.reason = "Connection Error";
+            info.error = t;
+            closeConnections(info);
+            return;
+        }
+    
+        for( ErrorListener l : errorListeners ) {
+            l.handleError( this, t );
+        } 
+    }
+ 
+    protected void configureChannels( long tempId, int[] ports ) {
+
+        try {               
+            for( int i = 0; i < ports.length; i++ ) {
+                Connector c = connectorFactory.createConnector( i, ports[i] );
+                ConnectorAdapter ca = new ConnectorAdapter(c, dispatcher, dispatcher, true);
+                int ch = channels.size(); 
+                channels.add( ca );
+                
+                // Need to send the connection its hook-up registration
+                // and start it.
+                ca.start(); 
+                ClientRegistrationMessage reg;
+                reg = new ClientRegistrationMessage();
+                reg.setId(tempId);
+                reg.setReliable(true);
+                send( ch, reg, false );
+            }
+        } catch( IOException e ) {
+            throw new RuntimeException( "Error configuring channels", e );
+        }
+    }
+ 
+    protected void dispatch( Message m )
+    {
+        // Pull off the connection management messages we're
+        // interested in and then pass on the rest.
+        if( m instanceof ClientRegistrationMessage ) {
+            // Then we've gotten our real id
+            this.id = (int)((ClientRegistrationMessage)m).getId();
+            log.log( Level.INFO, "Connection established, id:{0}.", this.id );
+            connecting.countDown();
+            fireConnected();
+            return;
+        } else if( m instanceof ChannelInfoMessage ) {
+            // This is an interum step in the connection process and
+            // now we need to add a bunch of connections
+            configureChannels( ((ChannelInfoMessage)m).getId(), ((ChannelInfoMessage)m).getPorts() );
+            return; 
+        } else if( m instanceof DisconnectMessage ) {
+            // Can't do too much else yet
+            String reason = ((DisconnectMessage)m).getReason();
+            log.log( Level.SEVERE, "Connection terminated, reason:{0}.", reason );
+            DisconnectInfo info = new DisconnectInfo();
+            info.reason = reason;
+            closeConnections(info);
+        }
+    
+        // Make sure client MessageListeners are called single-threaded
+        // since it could receive messages from the TCP and UDP
+        // thread simultaneously.
+        synchronized( this ) {
+            messageListeners.messageReceived( this, m );
+        }
+    }
+ 
+    protected class Redispatch implements MessageListener<Object>, ErrorListener<Object>
+    {
+        public void messageReceived( Object source, Message m )
+        {
+            dispatch( m );
+        }
+        
+        public void handleError( Object source, Throwable t )
+        {
+            // Only doing the DefaultClient.this to make the code
+            // checker happy... it compiles fine without it but I
+            // don't like red lines in my editor. :P
+            DefaultClient.this.handleError( t );   
+        }
+    }    
+}
diff --git a/engine/src/networking/com/jme3/network/base/DefaultServer.java b/engine/src/networking/com/jme3/network/base/DefaultServer.java
new file mode 100644
index 0000000..28c4c80
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/DefaultServer.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.*;
+import com.jme3.network.kernel.Endpoint;
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.message.ChannelInfoMessage;
+import com.jme3.network.message.ClientRegistrationMessage;
+import com.jme3.network.message.DisconnectMessage;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  A default implementation of the Server interface that delegates
+ *  its network connectivity to kernel.Kernel.
+ *
+ *  @version   $Revision: 9114 $
+ *  @author    Paul Speed
+ */
+public class DefaultServer implements Server
+{
+    static Logger log = Logger.getLogger(DefaultServer.class.getName());
+
+    // First two channels are reserved for reliable and
+    // unreliable
+    private static final int CH_RELIABLE = 0;
+    private static final int CH_UNRELIABLE = 1;
+    private static final int CH_FIRST = 2;
+    
+    private boolean isRunning = false;
+    private AtomicInteger nextId = new AtomicInteger(0);
+    private String gameName;
+    private int version;
+    private KernelFactory kernelFactory = KernelFactory.DEFAULT;
+    private KernelAdapter reliableAdapter;
+    private KernelAdapter fastAdapter;
+    private List<KernelAdapter> channels = new ArrayList<KernelAdapter>();
+    private List<Integer> alternatePorts = new ArrayList<Integer>();
+    private Redispatch dispatcher = new Redispatch();
+    private Map<Integer,HostedConnection> connections = new ConcurrentHashMap<Integer,HostedConnection>();
+    private Map<Endpoint,HostedConnection> endpointConnections 
+                            = new ConcurrentHashMap<Endpoint,HostedConnection>();
+    
+    // Keeps track of clients for whom we've only received the UDP
+    // registration message
+    private Map<Long,Connection> connecting = new ConcurrentHashMap<Long,Connection>();
+    
+    private MessageListenerRegistry<HostedConnection> messageListeners 
+                            = new MessageListenerRegistry<HostedConnection>();                        
+    private List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>();
+    
+    public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast )
+    {
+        if( reliable == null )
+            throw new IllegalArgumentException( "Default server reqiures a reliable kernel instance." );
+            
+        this.gameName = gameName;
+        this.version = version;
+        
+        reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true );
+        channels.add( reliableAdapter );
+        if( fast != null ) {
+            fastAdapter = new KernelAdapter( this, fast, dispatcher, false );
+            channels.add( fastAdapter );
+        }
+    }   
+
+    public String getGameName()
+    {
+        return gameName;
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    public int addChannel( int port )
+    {
+        if( isRunning )
+            throw new IllegalStateException( "Channels cannot be added once server is started." );
+ 
+        // Note: it does bug me that channels aren't 100% universal and
+        // setup externally but it requires a more invasive set of changes
+        // for "connection types" and some kind of registry of kernel and
+        // connector factories.  This really would be the best approach and
+        // would allow all kinds of channel customization maybe... but for
+        // now, we hard-code the standard connections and treat the +2 extras
+        // differently.
+            
+        // Check for consistency with the channels list
+        if( channels.size() - CH_FIRST != alternatePorts.size() )
+            throw new IllegalStateException( "Channel and port lists do not match." ); 
+            
+        try {                                
+            int result = alternatePorts.size(); 
+            alternatePorts.add(port);
+            
+            Kernel kernel = kernelFactory.createKernel(result, port); 
+            channels.add( new KernelAdapter(this, kernel, dispatcher, true) );
+            
+            return result;
+        } catch( IOException e ) {
+            throw new RuntimeException( "Error adding channel for port:" + port, e );
+        } 
+    } 
+
+    protected void checkChannel( int channel )
+    {
+        if( channel < 0 || channel >= alternatePorts.size() )
+            throw new IllegalArgumentException( "Channel is undefined:" + channel );              
+    }
+
+    public void start()
+    {
+        if( isRunning )
+            throw new IllegalStateException( "Server is already started." );
+            
+        // Initialize the kernels
+        for( KernelAdapter ka : channels ) {
+            ka.initialize();
+        }
+ 
+        // Start em up
+        for( KernelAdapter ka : channels ) {
+            ka.start();
+        }
+        
+        isRunning = true;             
+    }
+
+    public boolean isRunning()
+    {
+        return isRunning;
+    }     
+ 
+    public void close() 
+    {
+        if( !isRunning )
+            throw new IllegalStateException( "Server is not started." );
+ 
+        try {
+            // Kill the adpaters, they will kill the kernels
+            for( KernelAdapter ka : channels ) {
+                ka.close();
+            }
+            
+            isRunning = false;            
+        } catch( InterruptedException e ) {
+            throw new RuntimeException( "Interrupted while closing", e );
+        }                               
+    }
+ 
+    public void broadcast( Message message )
+    {
+        broadcast( null, message );
+    }
+
+    public void broadcast( Filter<? super HostedConnection> filter, Message message )
+    {
+        if( connections.isEmpty() )
+            return;
+            
+        ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null);
+ 
+        FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter);
+               
+        if( message.isReliable() || fastAdapter == null ) {
+            // Don't need to copy the data because message protocol is already
+            // giving us a fresh buffer
+            reliableAdapter.broadcast( adapter, buffer, true, false );
+        } else {
+            fastAdapter.broadcast( adapter, buffer, false, false );
+        }               
+    }
+
+    public void broadcast( int channel, Filter<? super HostedConnection> filter, Message message )
+    {
+        if( connections.isEmpty() )
+            return;
+
+        checkChannel(channel);
+        
+        ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null);
+ 
+        FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter);
+
+        channels.get(channel+CH_FIRST).broadcast( adapter, buffer, true, false );               
+    }
+
+    public HostedConnection getConnection( int id )
+    {
+        return connections.get(id);
+    }     
+ 
+    public boolean hasConnections()
+    {
+        return !connections.isEmpty();
+    }
+ 
+    public Collection<HostedConnection> getConnections()
+    {
+        return Collections.unmodifiableCollection((Collection<HostedConnection>)connections.values());
+    } 
+ 
+    public void addConnectionListener( ConnectionListener listener )
+    {
+        connectionListeners.add(listener);   
+    }
+ 
+    public void removeConnectionListener( ConnectionListener listener )
+    {
+        connectionListeners.remove(listener);   
+    }     
+    
+    public void addMessageListener( MessageListener<? super HostedConnection> listener )
+    {
+        messageListeners.addMessageListener( listener );
+    } 
+
+    public void addMessageListener( MessageListener<? super HostedConnection> listener, Class... classes )
+    {
+        messageListeners.addMessageListener( listener, classes );
+    } 
+
+    public void removeMessageListener( MessageListener<? super HostedConnection> listener )
+    {
+        messageListeners.removeMessageListener( listener );
+    } 
+
+    public void removeMessageListener( MessageListener<? super HostedConnection> listener, Class... classes )
+    {
+        messageListeners.removeMessageListener( listener, classes );
+    }
+ 
+    protected void dispatch( HostedConnection source, Message m )
+    {
+        if( source == null ) {
+            messageListeners.messageReceived( source, m );
+        } else {
+        
+            // A semi-heavy handed way to make sure the listener
+            // doesn't get called at the same time from two different
+            // threads for the same hosted connection.
+            synchronized( source ) {
+                messageListeners.messageReceived( source, m );
+            }
+        }
+    }
+
+    protected void fireConnectionAdded( HostedConnection conn )
+    {
+        for( ConnectionListener l : connectionListeners ) {
+            l.connectionAdded( this, conn );
+        }
+    }            
+
+    protected void fireConnectionRemoved( HostedConnection conn )
+    {
+        for( ConnectionListener l : connectionListeners ) {
+            l.connectionRemoved( this, conn );
+        }
+    }            
+
+    protected int getChannel( KernelAdapter ka )
+    {
+        return channels.indexOf(ka);
+    }
+
+    protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationMessage m )
+    {
+        Connection addedConnection = null;
+
+        // generally this will only be called by one thread but it's        
+        // important enough I won't take chances
+        synchronized( this ) {       
+            // Grab the random ID that the client created when creating
+            // its two registration messages
+            long tempId = m.getId();
+ 
+            // See if we already have one
+            Connection c = connecting.remove(tempId);
+            if( c == null ) {
+                c = new Connection(channels.size());
+                log.log( Level.FINE, "Registering client for endpoint, pass 1:{0}.", p );
+            } else {
+                log.log( Level.FINE, "Refining client registration for endpoint:{0}.", p );
+            } 
+          
+            // Fill in what we now know
+            int channel = getChannel(ka); 
+            c.setChannel(channel, p);            
+            log.log( Level.FINE, "Setting up channel:{0}", channel );
+ 
+            // If it's channel 0 then this is the initial connection
+            // and we will send the connection information
+            if( channel == CH_RELIABLE ) {
+                // Validate the name and version which is only sent
+                // over the reliable connection at this point.
+                if( !getGameName().equals(m.getGameName()) 
+                    || getVersion() != m.getVersion() ) {
+ 
+                    log.log( Level.INFO, "Kicking client due to name/version mismatch:{0}.", c );
+            
+                    // Need to kick them off... I may regret doing this from within
+                    // the sync block but the alternative is more code
+                    c.close( "Server client mismatch, server:" + getGameName() + " v" + getVersion()
+                             + "  client:" + m.getGameName() + " v" + m.getVersion() );
+                    return;                        
+                }
+                
+                // Else send the extra channel information to the client
+                if( !alternatePorts.isEmpty() ) {
+                    ChannelInfoMessage cim = new ChannelInfoMessage( m.getId(), alternatePorts );
+                    c.send(cim);
+                }
+            }
+
+            if( c.isComplete() ) {             
+                // Then we are fully connected
+                if( connections.put( c.getId(), c ) == null ) {
+                
+                    for( Endpoint cp : c.channels ) {
+                        if( cp == null )
+                            continue;
+                        endpointConnections.put( cp, c );
+                    } 
+ 
+                    addedConnection = c;               
+                }
+            } else {
+                // Need to keep getting channels so we'll keep it in
+                // the map
+                connecting.put(tempId, c);
+            } 
+        }
+ 
+        // Best to do this outside of the synch block to avoid
+        // over synchronizing which is the path to deadlocks
+        if( addedConnection != null ) {       
+            log.log( Level.INFO, "Client registered:{0}.", addedConnection );
+            
+            // Send the ID back to the client letting it know it's
+            // fully connected.
+            m = new ClientRegistrationMessage();
+            m.setId( addedConnection.getId() );
+            m.setReliable(true);
+            addedConnection.send(m);
+            
+            // Now we can notify the listeners about the
+            // new connection.
+            fireConnectionAdded( addedConnection );                                                   
+        }            
+    }
+
+    protected HostedConnection getConnection( Endpoint endpoint )
+    {
+        return endpointConnections.get(endpoint);       
+    } 
+
+    protected void connectionClosed( Endpoint p )
+    {
+        if( p.isConnected() ) {
+            log.log( Level.INFO, "Connection closed:{0}.", p );
+        } else {
+            log.log( Level.FINE, "Connection closed:{0}.", p );
+        }
+        
+        // Try to find the endpoint in all ways that it might
+        // exist.  Note: by this point the raw network channel is 
+        // closed already.
+    
+        // Also note: this method will be called multiple times per
+        // HostedConnection if it has multiple endpoints.
+ 
+        Connection removed = null;
+        synchronized( this ) {             
+            // Just in case the endpoint was still connecting
+            connecting.values().remove(p);
+
+            // And the regular management
+            removed = (Connection)endpointConnections.remove(p);
+            if( removed != null ) {
+                connections.remove( removed.getId() );                
+            }
+            
+            log.log( Level.FINE, "Connections size:{0}", connections.size() );
+            log.log( Level.FINE, "Endpoint mappings size:{0}", endpointConnections.size() );
+        }
+        
+        // Better not to fire events while we hold a lock
+        // so always do this outside the synch block.
+        // Note: checking removed.closed just to avoid spurious log messages
+        //       since in general we are called back for every endpoint closing.
+        if( removed != null && !removed.closed ) {
+        
+            log.log( Level.INFO, "Client closed:{0}.", removed );
+            
+            removed.closeConnection();
+        }
+    }
+
+    protected class Connection implements HostedConnection
+    {
+        private int id;
+        private boolean closed;
+        private Endpoint[] channels;
+        private int setChannelCount = 0; 
+       
+        private Map<String,Object> sessionData = new ConcurrentHashMap<String,Object>();       
+        
+        public Connection( int channelCount )
+        {
+            id = nextId.getAndIncrement();
+            channels = new Endpoint[channelCount];
+        }
+ 
+        void setChannel( int channel, Endpoint p )
+        {
+            if( channels[channel] != null && channels[channel] != p ) {
+                throw new RuntimeException( "Channel has already been set:" + channel 
+                                            + " = " + channels[channel] + ", cannot be set to:" + p );
+            }
+            channels[channel] = p;
+            if( p != null )
+                setChannelCount++;
+        }
+        
+        boolean isComplete()
+        {
+            return setChannelCount == channels.length;
+        }
+ 
+        public Server getServer()
+        {   
+            return DefaultServer.this;
+        }     
+       
+        public int getId()
+        {
+            return id;
+        }
+ 
+        public String getAddress()
+        {            
+            return channels[CH_RELIABLE] == null ? null : channels[CH_RELIABLE].getAddress();
+        }
+       
+        public void send( Message message )
+        {
+            ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null);
+            if( message.isReliable() || channels[CH_UNRELIABLE] == null ) {
+                channels[CH_RELIABLE].send( buffer );
+            } else {
+                channels[CH_UNRELIABLE].send( buffer );
+            }
+        }
+
+        public void send( int channel, Message message )
+        {
+            checkChannel(channel);
+            ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null);
+            channels[channel+CH_FIRST].send(buffer);
+        }
+ 
+        protected void closeConnection()
+        {
+            if( closed ) 
+                return;
+            closed = true;
+            
+            // Make sure all endpoints are closed.  Note: reliable
+            // should always already be closed through all paths that I
+            // can conceive... but it doesn't hurt to be sure. 
+            for( Endpoint p : channels ) {
+                if( p == null ) 
+                    continue;
+                p.close();
+            }
+        
+            fireConnectionRemoved( this );
+        }
+        
+        public void close( String reason )
+        {
+            // Send a reason
+            DisconnectMessage m = new DisconnectMessage();
+            m.setType( DisconnectMessage.KICK );
+            m.setReason( reason );
+            m.setReliable( true );
+            send( m );
+            
+            // Just close the reliable endpoint
+            // fast will be cleaned up as a side-effect
+            // when closeConnection() is called by the
+            // connectionClosed() endpoint callback.
+            if( channels[CH_RELIABLE] != null ) {
+                // Close with flush so we make sure our
+                // message gets out
+                channels[CH_RELIABLE].close(true);
+            }
+        }
+        
+        public Object setAttribute( String name, Object value )
+        {
+            if( value == null )
+                return sessionData.remove(name);
+            return sessionData.put(name, value);
+        }
+    
+        @SuppressWarnings("unchecked")
+        public <T> T getAttribute( String name )
+        {
+            return (T)sessionData.get(name);
+        }             
+
+        public Set<String> attributeNames()
+        {
+            return Collections.unmodifiableSet(sessionData.keySet());
+        }           
+        
+        public String toString()
+        {
+            return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE] 
+                                     + ", fast=" + channels[CH_UNRELIABLE] + " ]"; 
+        }  
+    } 
+
+    protected class Redispatch implements MessageListener<HostedConnection>
+    {
+        public void messageReceived( HostedConnection source, Message m )
+        {
+            dispatch( source, m );
+        }
+    }                                          
+     
+    protected class FilterAdapter implements Filter<Endpoint>
+    {
+        private Filter<? super HostedConnection> delegate;
+        
+        public FilterAdapter( Filter<? super HostedConnection> delegate )
+        {
+            this.delegate = delegate;
+        }
+        
+        public boolean apply( Endpoint input )
+        {
+            HostedConnection conn = getConnection( input );
+            if( conn == null )
+                return false;
+            return delegate.apply(conn);
+        } 
+    }     
+}
diff --git a/engine/src/networking/com/jme3/network/base/KernelAdapter.java b/engine/src/networking/com/jme3/network/base/KernelAdapter.java
new file mode 100644
index 0000000..8477aad
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/KernelAdapter.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.Filter;
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import com.jme3.network.kernel.Endpoint;
+import com.jme3.network.kernel.EndpointEvent;
+import com.jme3.network.kernel.Envelope;
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.message.ClientRegistrationMessage;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  Wraps a single Kernel and forwards new messages
+ *  to the supplied message dispatcher and new endpoint
+ *  events to the connection dispatcher.  This is used
+ *  by DefaultServer to manage its kernel objects.
+ *
+ *  <p>This adapter assumes a simple protocol where two
+ *  bytes define a (short) object size with the object data
+ *  to follow.  Note: this limits the size of serialized
+ *  objects to 32676 bytes... even though, for example,
+ *  datagram packets can hold twice that. :P</p>  
+ *
+ *  @version   $Revision: 8944 $
+ *  @author    Paul Speed
+ */
+public class KernelAdapter extends Thread
+{
+    static Logger log = Logger.getLogger(KernelAdapter.class.getName());
+    
+    private DefaultServer server; // this is unfortunate
+    private Kernel kernel;
+    private MessageListener<HostedConnection> messageDispatcher;
+    private AtomicBoolean go = new AtomicBoolean(true);
+ 
+    // Keeps track of the in-progress messages that are received
+    // on reliable connections
+    private Map<Endpoint, MessageProtocol> messageBuffers = new ConcurrentHashMap<Endpoint,MessageProtocol>();
+    
+    // Marks the messages as reliable or not if they came
+    // through this connector.
+    private boolean reliable;
+    
+    public KernelAdapter( DefaultServer server, Kernel kernel, MessageListener<HostedConnection> messageDispatcher,
+                          boolean reliable )
+    {
+        super( String.valueOf(kernel) );
+        this.server = server;
+        this.kernel = kernel;
+        this.messageDispatcher = messageDispatcher;
+        this.reliable = reliable;
+        setDaemon(true);
+    }
+
+    public Kernel getKernel()
+    {
+        return kernel;
+    }
+
+    public void initialize()
+    {
+        kernel.initialize();
+    }
+ 
+    public void broadcast( Filter<? super Endpoint> filter, ByteBuffer data, boolean reliable, 
+                           boolean copy )
+    {
+        kernel.broadcast( filter, data, reliable, copy );
+    }                           
+ 
+    public void close() throws InterruptedException
+    {
+        go.set(false);
+        
+        // Kill the kernel
+        kernel.terminate();
+    }
+
+    protected void reportError( Endpoint p, Object context, Exception e )
+    {
+        // Should really be queued up so the outer thread can
+        // retrieve them.  For now we'll just log it.  FIXME
+        log.log( Level.SEVERE, "Unhandled error, endpoint:" + p + ", context:" + context, e );
+        
+        // In lieu of other options, at least close the endpoint
+        p.close();
+    }                                                      
+
+    protected HostedConnection getConnection( Endpoint p )
+    {
+        return server.getConnection(p);
+    }
+ 
+    protected void connectionClosed( Endpoint p )
+    {
+        // Remove any message buffer we've been accumulating 
+        // on behalf of this endpoing
+        messageBuffers.remove(p);
+
+        log.log( Level.FINE, "Buffers size:{0}", messageBuffers.size() );
+    
+        server.connectionClosed(p);
+    }
+ 
+    /**
+     *  Note on threading for those writing their own server 
+     *  or adapter implementations.  The rule that a single connection be 
+     *  processed by only one thread at a time is more about ensuring that
+     *  the messages are delivered in the order that they are received
+     *  than for any user-code safety.  99% of the time the user code should
+     *  be writing for multithreaded access anyway.
+     *
+     *  <p>The issue with the messages is that if a an implementation is
+     *  using a general thread pool then it would be possible for a 
+     *  naive implementation to have one thread grab an Envelope from
+     *  connection 1's and another grab the next Envelope.  Since an Envelope
+     *  may contain several messages, delivering the second thread's messages
+     *  before or during the first's would be really confusing and hard
+     *  to code for in user code.</p>
+     *
+     *  <p>And that's why this note is here.  DefaultServer does a rudimentary
+     *  per-connection locking but it couldn't possibly guard against
+     *  out of order Envelope processing.</p>    
+     */
+    protected void dispatch( Endpoint p, Message m )
+    {
+        // Because this class is the only one with the information
+        // to do it... we need to pull of the registration message
+        // here.
+        if( m instanceof ClientRegistrationMessage ) {
+            server.registerClient( this, p, (ClientRegistrationMessage)m );
+            return;           
+        }                
+ 
+        try {           
+            HostedConnection source = getConnection(p);
+            if( source == null ) {
+                if( reliable ) {
+                    // If it's a reliable connection then it's slightly more
+                    // concerning but this can happen all the time for a UDP endpoint.
+                    log.log( Level.WARNING, "Recieved message from unconnected endpoint:" + p + "  message:" + m );
+                }                    
+                return; 
+            }
+            messageDispatcher.messageReceived( source, m );
+        } catch( Exception e ) {
+            reportError(p, m, e);
+        }
+    }
+
+    protected MessageProtocol getMessageBuffer( Endpoint p )
+    {
+        if( !reliable ) {
+            // Since UDP comes in packets and they aren't split
+            // up, there is no reason to buffer.  In fact, there would
+            // be a down side because there is no way for us to reliably
+            // clean these up later since we'd create another one for 
+            // any random UDP packet that comes to the port.
+            return new MessageProtocol();
+        } else {
+            // See if we already have one
+            MessageProtocol result = messageBuffers.get(p);
+            if( result == null ) {
+                result = new MessageProtocol();
+                messageBuffers.put(p, result);
+            }
+            return result;
+        }
+    }
+
+    protected void createAndDispatch( Envelope env )
+    {
+        MessageProtocol protocol = getMessageBuffer(env.getSource()); 
+    
+        byte[] data = env.getData();
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+
+        int count = protocol.addBuffer( buffer );
+        if( count == 0 ) {
+            // This can happen if there was only a partial message
+            // received.  However, this should never happen for unreliable
+            // connections.
+            if( !reliable ) {
+                // Log some additional information about the packet.
+                int len = Math.min( 10, data.length );
+                StringBuilder sb = new StringBuilder();
+                for( int i = 0; i < len; i++ ) {
+                    sb.append( "[" + Integer.toHexString(data[i]) + "]" ); 
+                }
+                log.log( Level.INFO, "First 10 bytes of incomplete nessage:" + sb );         
+                throw new RuntimeException( "Envelope contained incomplete data:" + env );
+            }                
+        }            
+        
+        // Should be complete... and maybe we should check but we don't
+        Message m = null;
+        while( (m = protocol.getMessage()) != null ) {
+            m.setReliable(reliable);
+            dispatch( env.getSource(), m );
+        }
+    } 
+
+    protected void createAndDispatch( EndpointEvent event )
+    {
+        // Only need to tell the server about disconnects 
+        if( event.getType() == EndpointEvent.Type.REMOVE ) {            
+            connectionClosed( event.getEndpoint() );
+        }            
+    }
+ 
+    protected void flushEvents()
+    {
+        EndpointEvent event;
+        while( (event = kernel.nextEvent()) != null ) {
+            try {
+                createAndDispatch( event );
+            } catch( Exception e ) {
+                reportError(event.getEndpoint(), event, e);        
+            }
+        }
+    }
+ 
+    public void run()
+    {
+        while( go.get() ) {
+        
+            try {           
+                // Check for pending events
+                flushEvents();
+ 
+                // Grab the next envelope
+                Envelope e = kernel.read();
+                if( e == Kernel.EVENTS_PENDING )
+                    continue; // We'll catch it up above
+ 
+                // Check for pending events that might have
+                // come in while we were blocking.  This is usually
+                // when the connection add events come through
+                flushEvents();
+            
+                try {
+                    createAndDispatch( e );
+                } catch( Exception ex ) {
+                    reportError(e.getSource(), e, ex);        
+                }
+                        
+            } catch( InterruptedException ex ) {
+                if( !go.get() )
+                    return;
+                throw new RuntimeException( "Unexpected interruption", ex );
+            }
+        }
+    }
+        
+}
+
+
diff --git a/engine/src/networking/com/jme3/network/base/KernelFactory.java b/engine/src/networking/com/jme3/network/base/KernelFactory.java
new file mode 100644
index 0000000..0074d39
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/KernelFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.kernel.Kernel;
+import java.io.IOException;
+
+
+/**
+ *  Supplied to the DefaultServer to create any additional
+ *  channel kernels that might be required.
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public interface KernelFactory
+{
+    public static final KernelFactory DEFAULT = new NioKernelFactory();
+
+    public Kernel createKernel( int channel, int port ) throws IOException;
+}
diff --git a/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java b/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java
new file mode 100644
index 0000000..c861786
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/MessageListenerRegistry.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  Keeps track of message listeners registered to specific
+ *  types or to any type.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class MessageListenerRegistry<S> implements MessageListener<S>
+{
+    static Logger log = Logger.getLogger(MessageListenerRegistry.class.getName());
+    
+    private List<MessageListener<? super S>> listeners = new CopyOnWriteArrayList<MessageListener<? super S>>();
+    private Map<Class,List<MessageListener<? super S>>> typeListeners 
+                    = new ConcurrentHashMap<Class,List<MessageListener<? super S>>>(); 
+
+    public MessageListenerRegistry()
+    {
+    }
+ 
+    public void messageReceived( S source, Message m )
+    {
+        boolean delivered = false;
+        
+        for( MessageListener<? super S> l : listeners ) {
+            l.messageReceived( source, m );
+            delivered = true;
+        }
+        
+        for( MessageListener<? super S> l : getListeners(m.getClass(),false) ) {
+            l.messageReceived( source, m );
+            delivered = true;
+        }
+        
+        if( !delivered ) {
+            log.log( Level.INFO, "Received message had no registered listeners: {0}", m );
+        }
+    }
+ 
+    protected List<MessageListener<? super S>> getListeners( Class c, boolean create )
+    {
+        List<MessageListener<? super S>> result = typeListeners.get(c);
+        if( result == null && create ) {
+            result = new CopyOnWriteArrayList<MessageListener<? super S>>();
+            typeListeners.put( c, result );
+        }
+        
+        if( result == null ) {
+            result = Collections.emptyList();
+        }
+        return result;   
+    }
+    
+    public void addMessageListener( MessageListener<? super S> listener )
+    {
+        listeners.add(listener);
+    } 
+
+    public void removeMessageListener( MessageListener<? super S> listener )
+    {
+        listeners.remove(listener);
+    } 
+
+    public void addMessageListener( MessageListener<? super S> listener, Class... classes )
+    {
+        for( Class c : classes ) {
+            getListeners(c, true).add(listener);
+        }
+    } 
+
+    public void removeMessageListener( MessageListener<? super S> listener, Class... classes )
+    {
+        for( Class c : classes ) {
+            getListeners(c, false).remove(listener);
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/base/MessageProtocol.java b/engine/src/networking/com/jme3/network/base/MessageProtocol.java
new file mode 100644
index 0000000..eb22302
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/MessageProtocol.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.Message;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+
+/**
+ *  Consolidates the conversion of messages to/from byte buffers
+ *  and provides a rolling message buffer.  ByteBuffers can be
+ *  pushed in and messages will be extracted, accumulated, and 
+ *  available for retrieval.  This is not thread safe and is meant
+ *  to be used within a single message processing thread.
+ *
+ *  <p>The protocol is based on a simple length + data format
+ *  where two bytes represent the (short) length of the data
+ *  and the rest is the raw data for the Serializers class.</p>
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class MessageProtocol
+{
+    private LinkedList<Message> messages = new LinkedList<Message>();
+    private ByteBuffer current;
+    private int size;
+    private Byte carry;
+ 
+    /**
+     *  Converts a message to a ByteBuffer using the Serializer
+     *  and the (short length) + data protocol.  If target is null
+     *  then a 32k byte buffer will be created and filled.
+     */
+    public static ByteBuffer messageToBuffer( Message message, ByteBuffer target )
+    {
+        // Could let the caller pass their own in       
+        ByteBuffer buffer = target == null ? ByteBuffer.allocate( 32767 + 2 ) : target;
+        
+        try {
+            buffer.position( 2 );
+            Serializer.writeClassAndObject( buffer, message );
+            buffer.flip();
+            short dataLength = (short)(buffer.remaining() - 2);
+            buffer.putShort( dataLength );
+            buffer.position( 0 );
+            
+            return buffer;
+        } catch( IOException e ) {
+            throw new RuntimeException( "Error serializing message", e );
+        }
+    }
+ 
+    /**
+     *  Retrieves and removes an extracted message from the accumulated buffer
+     *  or returns null if there are no more messages.
+     */
+    public Message getMessage()
+    {
+        if( messages.isEmpty() ) {
+            return null;
+        }
+        
+        return messages.removeFirst();
+    }     
+   
+    /**
+     *  Adds the specified buffer, extracting the contained messages 
+     *  and making them available to getMessage().  The left over
+     *  data is buffered to be combined with future data.
+     &
+     *  @return The total number of queued messages after this call.       
+     */
+    public int addBuffer( ByteBuffer buffer )
+    {
+        // push the data from the buffer into as
+        // many messages as we can
+        while( buffer.remaining() > 0 ) {
+
+            if( current == null ) {
+
+                // If we have a left over carry then we need to
+                // do manual processing to get the short value
+                if( carry != null ) {
+                    byte high = carry;
+                    byte low = buffer.get();
+                    
+                    size = (high & 0xff) << 8 | (low & 0xff);
+                    carry = null;
+                }
+                else if( buffer.remaining() < 2 ) {
+                    // It's possible that the supplied buffer only has one
+                    // byte in it... and in that case we will get an underflow
+                    // when attempting to read the short below.
+                    
+                    // It has to be 1 or we'd never get here... but one
+                    // isn't enough so we stash it away.
+                    carry = buffer.get();
+                    break;
+                } else {
+                    // We are not currently reading an object so
+                    // grab the size.
+                    // Note: this is somewhat limiting... int would
+                    // be better.
+                    size = buffer.getShort();
+                }               
+ 
+                // Allocate the buffer into which we'll feed the
+                // data as we get it               
+                current = ByteBuffer.allocate(size);
+            } 
+
+            if( current.remaining() <= buffer.remaining() ) {
+                // We have at least one complete object so
+                // copy what we can into current, create a message,
+                // and then continue pulling from buffer.
+                    
+                // Artificially set the limit so we don't overflow
+                int extra = buffer.remaining() - current.remaining();
+                buffer.limit( buffer.position() + current.remaining() );
+ 
+                // Now copy the data                   
+                current.put( buffer );
+                current.flip();
+                    
+                // Now set the limit back to a good value
+                buffer.limit( buffer.position() + extra );
+ 
+                createMessage( current );
+ 
+                current = null;                    
+            } else {
+                
+                // Not yet a complete object so just copy what we have
+                current.put( buffer ); 
+            }            
+        }            
+        
+        return messages.size();        
+    }
+ 
+    /**
+     *  Creates a message from the properly sized byte buffer
+     *  and adds it to the messages queue.
+     */   
+    protected void createMessage( ByteBuffer buffer )
+    {
+        try {
+            Object obj = Serializer.readClassAndObject( buffer );
+            Message m = (Message)obj;
+            messages.add(m);
+        } catch( IOException e ) {
+            throw new RuntimeException( "Error deserializing object", e );   
+        }         
+    }
+}
+
+
+
diff --git a/engine/src/networking/com/jme3/network/base/NioKernelFactory.java b/engine/src/networking/com/jme3/network/base/NioKernelFactory.java
new file mode 100644
index 0000000..c9fab08
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/NioKernelFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.kernel.tcp.SelectorKernel;
+import java.io.IOException;
+
+
+/**
+ *  KernelFactory implemention for creating TCP kernels
+ *  using the NIO selector model.
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public class NioKernelFactory implements KernelFactory
+{
+    public Kernel createKernel( int channel, int port ) throws IOException
+    {
+        return new SelectorKernel(port);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/base/TcpConnectorFactory.java b/engine/src/networking/com/jme3/network/base/TcpConnectorFactory.java
new file mode 100644
index 0000000..e53e62d
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/TcpConnectorFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011 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.network.base;
+
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.kernel.tcp.SocketConnector;
+import java.io.IOException;
+import java.net.InetAddress;
+
+
+/**
+ *  Creates TCP connectors to a specific remote address.  
+ *
+ *  @version   $Revision: 8938 $
+ *  @author    Paul Speed
+ */
+public class TcpConnectorFactory implements ConnectorFactory
+{
+    private InetAddress remoteAddress;
+    
+    public TcpConnectorFactory( InetAddress remoteAddress )
+    {
+        this.remoteAddress = remoteAddress;
+    }
+
+    public Connector createConnector( int channel, int port ) throws IOException
+    {
+        return new SocketConnector( remoteAddress, port );        
+    }    
+}
diff --git a/engine/src/networking/com/jme3/network/base/package.html b/engine/src/networking/com/jme3/network/base/package.html
new file mode 100644
index 0000000..a00cb16
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/base/package.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>    
+<body>
+The base package contains the default implementations for the
+{@link com.jme3.network.Client} and {@link com.jme3.network.Server} 
+interfaces from the public API.
+</body>
+</html>
diff --git a/engine/src/networking/com/jme3/network/kernel/AbstractKernel.java b/engine/src/networking/com/jme3/network/kernel/AbstractKernel.java
new file mode 100644
index 0000000..9881bfa
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/AbstractKernel.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  Base implementation of the Kernel interface providing several
+ *  useful default implementations of some methods.  This implementation
+ *  assumes that the kernel will be managing its own internal threads
+ *  and queuing any results for the caller to retrieve on their own
+ *  thread.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public abstract class AbstractKernel implements Kernel
+{
+    static Logger log = Logger.getLogger(AbstractKernel.class.getName());
+
+    private AtomicLong nextId = new AtomicLong(1);
+
+    /**
+     *  Contains the pending endpoint events waiting for the caller
+     *  to retrieve them.
+     */
+    private ConcurrentLinkedQueue<EndpointEvent> endpointEvents = new ConcurrentLinkedQueue<EndpointEvent>();
+
+    /**
+     *  Contains the pending envelopes waiting for the caller to
+     *  retrieve them.
+     */
+    private LinkedBlockingQueue<Envelope> envelopes = new LinkedBlockingQueue<Envelope>();
+
+    protected AbstractKernel()
+    {
+    }
+
+    protected void reportError( Exception e )
+    {
+        // Should really be queued up so the outer thread can
+        // retrieve them.  For now we'll just log it.  FIXME
+        log.log( Level.SEVERE, "Unhanddled kernel error", e );
+    }
+
+    protected long nextEndpointId()
+    {
+        return nextId.getAndIncrement();
+    }
+
+    /**
+     *  Returns true if there are waiting envelopes.
+     */
+    public boolean hasEnvelopes()
+    {
+        return !envelopes.isEmpty();
+    }
+
+    /**
+     *  Removes one envelope from the received messages queue or
+     *  blocks until one is available.
+     */
+    public Envelope read() throws InterruptedException
+    {
+        return envelopes.take();
+    }
+
+    /**
+     *  Removes and returnsn one endpoint event from the event queue or
+     *  null if there are no endpoint events.
+     */
+    public EndpointEvent nextEvent()
+    {
+        return endpointEvents.poll();
+    }
+
+    protected void addEvent( EndpointEvent e )
+    {
+        endpointEvents.add( e );
+    }
+
+    protected void addEnvelope( Envelope env )
+    {
+        if( !envelopes.offer( env ) ) {
+            throw new KernelException( "Critical error, could not enqueue envelope." );
+        }            
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/Connector.java b/engine/src/networking/com/jme3/network/kernel/Connector.java
new file mode 100644
index 0000000..f86c17b
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/Connector.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+import java.nio.ByteBuffer;
+
+/**
+ *  A single channel remote connection allowing the sending
+ *  and receiving of data.  As opposed to the Kernel, this will
+ *  only ever receive data from one Endpoint and so bypasses
+ *  the envelope wrapping.
+ *
+ *  @version   $Revision: 7012 $
+ *  @author    Paul Speed
+ */
+public interface Connector
+{
+    /**
+     *  Returns true if this connector is currently connected.
+     */
+    public boolean isConnected();
+
+    /**
+     *  Closes the connection.  Any subsequent attempts to read
+     *  or write will fail with an exception.
+     */
+    public void close();     
+
+    /**
+     *  Returns true if there is currently data available for
+     *  reading.  Some connector implementations may not be able
+     *  to answer this question accurately and will always return
+     *  false.
+     */
+    public boolean available();     
+    
+    /**
+     *  Reads a chunk of data from the connection, blocking if
+     *  there is no data available.  The buffer may only be valid
+     *  until the next read() call is made.  Callers should copy
+     *  the data if they need it for longer than that.
+     *
+     *  @return The data read or null if there is no more data
+     *          because the connection is closed.
+     */
+    public ByteBuffer read();
+    
+    /**
+     *  Writes a chunk of data to the connection from data.position()
+     *  to data.limit().
+     */
+    public void write( ByteBuffer data );
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/ConnectorException.java b/engine/src/networking/com/jme3/network/kernel/ConnectorException.java
new file mode 100644
index 0000000..31d02b0
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/ConnectorException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+
+/**
+ *  Represents a client-side connection error, usually encapsulating
+ *  an IOException as its cause.
+ *
+ *  @version   $Revision: 7009 $
+ *  @author    Paul Speed
+ */
+public class ConnectorException extends RuntimeException
+{
+    public ConnectorException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+    
+    public ConnectorException( String message )
+    {
+        super( message );
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/Endpoint.java b/engine/src/networking/com/jme3/network/kernel/Endpoint.java
new file mode 100644
index 0000000..d63fdae
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/Endpoint.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+import java.nio.ByteBuffer;
+
+/**
+ *  An abstract endpoint in a Kernel that can be used for
+ *  sending/receiving messages within the kernel space.
+ *
+ *  @version   $Revision: 7049 $
+ *  @author    Paul Speed
+ */
+public interface Endpoint
+{
+    /**
+     *  Returns an ID that is unique for this endpoint within its
+     *  Kernel instance.
+     */
+    public long getId();
+
+    /**
+     *  Returns the transport specific remote address of this endpoint
+     *  as a string.  This may or may not be unique per endpoint depending
+     *  on the type of transport. 
+     */
+    public String getAddress();     
+
+    /**
+     *  Returns the kernel to which this endpoint belongs.
+     */
+    public Kernel getKernel();    
+
+    /**
+     *  Returns true if this endpoint is currently connected.
+     */
+    public boolean isConnected();
+
+    /**
+     *  Sends data to the other end of the connection represented
+     *  by this endpoint.
+     */
+    public void send( ByteBuffer data );
+
+    /**
+     *  Closes this endpoint without flushing any of its
+     *  currently enqueued outbound data.
+     */
+    public void close();
+    
+    /**
+     *  Closes this endpoint, optionally flushing any queued
+     *  data before closing.  As soon as this method is called,
+     *  ne send() calls will fail with an exception... even while
+     *  close() is still flushing the earlier queued messages.
+     */
+    public void close(boolean flushData);
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/EndpointEvent.java b/engine/src/networking/com/jme3/network/kernel/EndpointEvent.java
new file mode 100644
index 0000000..feb48a6
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/EndpointEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+
+/**
+ *  Provides information about an added or
+ *  removed connection.
+ *
+ *  @version   $Revision: 7009 $
+ *  @author    Paul Speed
+ */
+public class EndpointEvent
+{
+    public enum Type { ADD, REMOVE };
+
+    private Kernel source;
+    private Endpoint endpoint;
+    private Type type;
+
+    public EndpointEvent( Kernel source, Endpoint p, Type type )
+    {
+        this.source = source;
+        this.endpoint = p;
+        this.type = type;
+    }
+    
+    public static EndpointEvent createAdd( Kernel source, Endpoint p )
+    {
+        return new EndpointEvent( source, p, Type.ADD );
+    }
+
+    public static EndpointEvent createRemove( Kernel source, Endpoint p )
+    {
+        return new EndpointEvent( source, p, Type.REMOVE );
+    }
+    
+    public Kernel getSource()
+    {
+        return source;
+    }
+    
+    public Endpoint getEndpoint()
+    {
+        return endpoint;
+    }
+    
+    public Type getType()
+    {
+        return type;
+    }
+    
+    public String toString()
+    {
+        return "EndpointEvent[" + type + ", " + endpoint + "]";
+    } 
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/Envelope.java b/engine/src/networking/com/jme3/network/kernel/Envelope.java
new file mode 100644
index 0000000..3c0028b
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/Envelope.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+/**
+ *  Encapsulates a received piece of data.  This is used by the Kernel
+ *  to track incoming chunks of data.  
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class Envelope
+{
+    private Endpoint source;  
+    private byte[] data;
+    private boolean reliable;
+    
+    /**
+     *  Creates an incoming envelope holding the data from the specified
+     *  source.  The 'reliable' flag further indicates on which mode of
+     *  transport the data arrrived.
+     */
+    public Envelope( Endpoint source, byte[] data, boolean reliable )
+    {
+        this.source = source;
+        this.data = data;
+        this.reliable = reliable;
+    }
+    
+    public Endpoint getSource()
+    {
+        return source;
+    }
+    
+    public byte[] getData()
+    {
+        return data;
+    }
+    
+    public boolean isReliable()
+    {
+        return reliable;
+    }
+    
+    public String toString()
+    {
+        return "Envelope[" + source + ", " + (reliable?"reliable":"unreliable") + ", " + data.length + "]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/Kernel.java b/engine/src/networking/com/jme3/network/kernel/Kernel.java
new file mode 100644
index 0000000..c13b736
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/Kernel.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+import com.jme3.network.Filter;
+import java.nio.ByteBuffer;
+
+/**
+ *  Defines the basic byte[] passing messaging
+ *  kernel.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public interface Kernel
+{
+    /**
+     *  A marker envelope returned from read() that indicates that
+     *  there are events pending.  This allows a single thread to
+     *  more easily process the envelopes and endpoint events.
+     */
+    public static final Envelope EVENTS_PENDING = new Envelope( null, new byte[0], false );     
+
+    /**
+     *  Initializes the kernel and starts any internal processing.
+     */
+    public void initialize();
+    
+    /**
+     *  Gracefully terminates the kernel and stops any internal 
+     *  daemon processing.  This method will not return until all
+     *  internal threads have been shut down.
+     */
+    public void terminate() throws InterruptedException;
+
+    /**
+     *  Dispatches the data to all endpoints managed by the
+     *  kernel that match the specified endpoint filter..
+     *  If 'copy' is true then the implementation will copy the byte buffer
+     *  before delivering it to endpoints.  This allows the caller to reuse
+     *  the data buffer.  Though it is important that the buffer not be changed
+     *  by another thread while this call is running.
+     *  Only the bytes from data.position() to data.remaining() are sent.  
+     */ 
+    public void broadcast( Filter<? super Endpoint> filter, ByteBuffer data, boolean reliable, 
+                           boolean copy );
+ 
+    /**
+     *  Returns true if there are waiting envelopes.
+     */   
+    public boolean hasEnvelopes();
+ 
+    /**
+     *  Removes one envelope from the received messages queue or
+     *  blocks until one is available.
+     */   
+    public Envelope read() throws InterruptedException;
+    
+    /**
+     *  Removes and returnsn one endpoint event from the event queue or
+     *  null if there are no endpoint events.     
+     */
+    public EndpointEvent nextEvent();     
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/KernelException.java b/engine/src/networking/com/jme3/network/kernel/KernelException.java
new file mode 100644
index 0000000..62a57e4
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/KernelException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+
+/**
+ *  Represents a kernel-level error, usually encapsulating
+ *  an IOException as its cause.
+ *
+ *  @version   $Revision: 7009 $
+ *  @author    Paul Speed
+ */
+public class KernelException extends RuntimeException
+{
+    public KernelException( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+    
+    public KernelException( String message )
+    {
+        super( message );
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/NamedThreadFactory.java b/engine/src/networking/com/jme3/network/kernel/NamedThreadFactory.java
new file mode 100644
index 0000000..f56c23a
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/NamedThreadFactory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011 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.network.kernel;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ *  A simple factory that delegates to java.util.concurrent's
+ *  default thread factory but adds a prefix to the beginning
+ *  of the thread name.
+ *
+ *  @version   $Revision: 7314 $
+ *  @author    Paul Speed
+ */
+public class NamedThreadFactory implements ThreadFactory
+{
+    private String name;
+    private boolean daemon;
+    private ThreadFactory delegate;
+    
+    public NamedThreadFactory( String name )
+    {
+        this( name, Executors.defaultThreadFactory() );
+    }
+    
+    public NamedThreadFactory( String name, boolean daemon )
+    {
+        this( name, daemon, Executors.defaultThreadFactory() );
+    }
+    
+    public NamedThreadFactory( String name, ThreadFactory delegate )
+    {
+        this( name, false, delegate );
+    }
+
+    public NamedThreadFactory( String name, boolean daemon, ThreadFactory delegate )
+    {
+        this.name = name;
+        this.daemon = daemon;
+        this.delegate = delegate;
+    }
+    
+    public Thread newThread( Runnable r )
+    {
+        Thread result = delegate.newThread(r);
+        String s = result.getName();
+        result.setName( name + "[" + s + "]" );
+        result.setDaemon(daemon);
+        return result;
+    } 
+}
+
diff --git a/engine/src/networking/com/jme3/network/kernel/package.html b/engine/src/networking/com/jme3/network/kernel/package.html
new file mode 100644
index 0000000..7029c95
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+    
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>    
+    
+<body>
+The kernel package is the heart of the JME networking module
+and controls the routing and dispatch of message data over
+different transport implementations.  Most users will never 
+have to deal with these classes unless they are writing their own 
+client and server implementations that diverge from the standard 
+classes that are provided.
+
+<p>{@link com.jme3.network.kernel.Kernel} defines the core of a server-side message
+broker that abstracts away the specific transport and underlying
+threading model used.  For example, it might use NIO selectors
+in a single threaded model or straight multithreaded socket
+model.  Or it might implement SSL connections.  Once created,
+{@link com.jme3.network.kernel.Kernel} users don't need to care about the details.</p>
+
+<p>{@link com.jme3.network.kernel.Endpoint} is a managed connection within a 
+{@link com.jme3.network.kernel.Kernel} providing kernel to client connectivity.</p>
+
+<p>{@link com.jme3.network.kernel.Connector} defines the basic client-side message sender
+and these objects are typically used to connect to a {@link com.jme3.network.kernel.Kernel} 
+though they can connect to any network port that supports the implementation's
+protocol.  Implementations are provided for straight TCP and UDP communication
+and could be extended to support SSL or different threading models.</p>  
+
+</body>
+</html>
diff --git a/engine/src/networking/com/jme3/network/kernel/tcp/NioEndpoint.java b/engine/src/networking/com/jme3/network/kernel/tcp/NioEndpoint.java
new file mode 100644
index 0000000..87b721f
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/tcp/NioEndpoint.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.tcp;
+
+import com.jme3.network.kernel.Endpoint;
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.kernel.KernelException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+
+/**
+ *  Endpoint implementation that encapsulates the
+ *  channel IO based connection information and keeps
+ *  track of the outbound data queue for the channel.
+ *
+ *  @version   $Revision: 8944 $
+ *  @author    Paul Speed
+ */
+public class NioEndpoint implements Endpoint
+{
+    protected static final ByteBuffer CLOSE_MARKER = ByteBuffer.allocate(0);
+
+    private long id;
+    private SocketChannel socket;
+    private SelectorKernel kernel;
+    private ConcurrentLinkedQueue<ByteBuffer> outbound = new ConcurrentLinkedQueue<ByteBuffer>();
+    private boolean closing = false;
+
+    public NioEndpoint( SelectorKernel kernel, long id, SocketChannel socket )
+    {
+        this.id = id;
+        this.socket = socket;
+        this.kernel = kernel;
+    }
+
+    public Kernel getKernel()
+    {
+        return kernel;
+    }
+
+    public void close()
+    {
+        close(false);
+    }
+
+    public void close( boolean flushData )
+    {
+        if( flushData ) {
+            closing = true;
+            
+            // Enqueue a close marker message to let the server
+            // know we should close
+            send( CLOSE_MARKER, false, true );
+            
+            return;
+        }
+    
+        try {
+            // Note: even though we may be disconnected from the socket.isConnected()
+            // standpoint, it's still safest to tell the kernel so that it can be sure
+            // to stop managing us gracefully.
+            kernel.closeEndpoint(this);
+        } catch( IOException e ) {
+            throw new KernelException( "Error closing endpoint for socket:" + socket, e );
+        }
+    }
+
+    public long getId()
+    {
+        return id;
+    }
+
+    public String getAddress()
+    {
+        return String.valueOf(socket.socket().getRemoteSocketAddress()); 
+    }     
+
+    public boolean isConnected()
+    {
+        return socket.isConnected();
+    }
+
+    /**
+     *  The wakeup option is used internally when the kernel is
+     *  broadcasting out to a bunch of endpoints and doesn't want to
+     *  necessarily wakeup right away.
+     */
+    protected void send( ByteBuffer data, boolean copy, boolean wakeup )
+    {
+        // We create a ByteBuffer per endpoint since we
+        // use it to track the data sent to each endpoint
+        // separately.
+        ByteBuffer buffer;
+        if( !copy ) {
+            buffer = data;
+        } else {
+            // Copy the buffer
+            buffer = ByteBuffer.allocate(data.remaining());
+            buffer.put(data);
+            buffer.flip();
+        }
+
+        // Queue it up
+        outbound.add(buffer);
+
+        if( wakeup )
+            kernel.wakeupSelector();
+    }
+
+    /**
+     *  Called by the SelectorKernel to get the current top
+     *  buffer for writing.
+     */
+    protected ByteBuffer peekPending()
+    {
+        return outbound.peek();
+    }
+
+    /**
+     *  Called by the SelectorKernel when the top buffer
+     *  has been exhausted.
+     */
+    protected ByteBuffer removePending()
+    {
+        return outbound.poll();
+    }
+
+    protected boolean hasPending()
+    {
+        return !outbound.isEmpty();
+    }
+
+    public void send( ByteBuffer data )
+    {   
+        if( data == null ) {
+            throw new IllegalArgumentException( "Data cannot be null." );
+        }
+        if( closing ) {
+            throw new KernelException( "Endpoint has been closed:" + socket );
+        }
+        send( data, true, true );
+    }
+
+    public String toString()
+    {
+        return "NioEndpoint[" + id + ", " + socket + "]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/tcp/SelectorKernel.java b/engine/src/networking/com/jme3/network/kernel/tcp/SelectorKernel.java
new file mode 100644
index 0000000..5fa75f3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/tcp/SelectorKernel.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.tcp;
+
+import com.jme3.network.Filter;
+import com.jme3.network.kernel.*;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.*;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ *  A Kernel implementation based on NIO selectors.
+ *
+ *  @version   $Revision: 8944 $
+ *  @author    Paul Speed
+ */
+public class SelectorKernel extends AbstractKernel
+{
+    static Logger log = Logger.getLogger(SelectorKernel.class.getName());
+
+    private InetSocketAddress address;
+    private SelectorThread thread;
+
+    private Map<Long,NioEndpoint> endpoints = new ConcurrentHashMap<Long,NioEndpoint>();
+
+    public SelectorKernel( InetAddress host, int port )
+    {
+        this( new InetSocketAddress(host, port) );
+    }
+
+    public SelectorKernel( int port ) throws IOException
+    {
+        this( new InetSocketAddress(port) );
+    }
+
+    public SelectorKernel( InetSocketAddress address )
+    {
+        this.address = address;
+    }
+
+    protected SelectorThread createSelectorThread()
+    {
+        return new SelectorThread();
+    }
+
+    public void initialize()
+    {
+        if( thread != null )
+            throw new IllegalStateException( "Kernel already initialized." );
+
+        thread = createSelectorThread();
+
+        try {
+            thread.connect();
+            thread.start();
+        } catch( IOException e ) {
+            throw new KernelException( "Error hosting:" + address, e );
+        }
+    }
+
+    public void terminate() throws InterruptedException
+    {
+        if( thread == null )
+            throw new IllegalStateException( "Kernel not initialized." );
+
+        try {
+            thread.close();
+            thread = null;
+        } catch( IOException e ) {
+            throw new KernelException( "Error closing host connection:" + address, e );
+        }
+    }
+
+    public void broadcast( Filter<? super Endpoint> filter, ByteBuffer data, boolean reliable,
+                           boolean copy )
+    {
+        if( !reliable )
+            throw new UnsupportedOperationException( "Unreliable send not supported by this kernel." );
+
+        if( copy ) {
+            // Copy the data just once
+            byte[] temp = new byte[data.remaining()];
+            System.arraycopy(data.array(), data.position(), temp, 0, data.remaining());
+            data = ByteBuffer.wrap(temp);
+        }
+
+        // Hand it to all of the endpoints that match our routing
+        for( NioEndpoint p : endpoints.values() ) {
+            // Does it match the filter?
+            if( filter != null && !filter.apply(p) )
+                continue;
+
+            // Give it the data... but let each endpoint track their
+            // own completion over the shared array of bytes by
+            // duplicating it
+            p.send( data.duplicate(), false, false );
+        }
+
+        // Wake up the selector so it can reinitialize its
+        // state accordingly.
+        wakeupSelector();
+    }
+
+    protected NioEndpoint addEndpoint( SocketChannel c )
+    {
+        // Note: we purposely do NOT put the key in the endpoint.
+        //       SelectionKeys are dangerous outside the selector thread
+        //       and this is safer.
+        NioEndpoint p = new NioEndpoint( this, nextEndpointId(), c );
+
+        endpoints.put( p.getId(), p );
+
+        // Enqueue an endpoint event for the listeners
+        addEvent( EndpointEvent.createAdd( this, p ) );
+
+        return p;
+    }
+
+    protected void removeEndpoint( NioEndpoint p, SocketChannel c )
+    {
+        endpoints.remove( p.getId() );
+        log.log( Level.FINE, "Endpoints size:{0}", endpoints.size() );
+
+        // Enqueue an endpoint event for the listeners
+        addEvent( EndpointEvent.createRemove( this, p ) );
+
+        // If there are no pending messages then add one so that the
+        // kernel-user knows to wake up if it is only listening for
+        // envelopes.
+        if( !hasEnvelopes() ) {
+            // Note: this is not really a race condition.  At worst, our
+            // event has already been handled by now and it does no harm
+            // to check again.
+            addEnvelope( EVENTS_PENDING );
+        }
+    }
+
+    /**
+     *  Called by the endpoints when they need to be closed.
+     */
+    protected void closeEndpoint( NioEndpoint p ) throws IOException
+    {
+        //log.log( Level.INFO, "Closing endpoint:{0}.", p );
+            
+        thread.cancel(p);
+    }
+
+    /**
+     *  Used internally by the endpoints to wakeup the selector
+     *  when they have data to send.
+     */
+    protected void wakeupSelector()
+    {
+        thread.wakeupSelector();
+    }
+
+    protected void newData( NioEndpoint p, SocketChannel c, ByteBuffer shared, int size )
+    {
+        // Note: if ever desirable, it would be possible to accumulate
+        //       data per source channel and only 'finalize' it when
+        //       asked for more envelopes then were ready.  I just don't
+        //       think it will be an issue in practice.  The busier the
+        //       server, the more the buffers will fill before we get to them.
+        //       And if the server isn't busy, who cares if we chop things up
+        //       smaller... the network is still likely to deliver things in
+        //       bulk anyway.
+
+        // Must copy the shared data before we use it
+        byte[] dataCopy = new byte[size];
+		System.arraycopy(shared.array(), 0, dataCopy, 0, size);
+
+        Envelope env = new Envelope( p, dataCopy, true );
+        addEnvelope( env );
+    }
+
+    /**
+     *  This class is purposely tucked neatly away because
+     *  messing with the selector from other threads for any
+     *  reason is very bad.  This is the safest architecture.
+     */
+    protected class SelectorThread extends Thread
+    {
+        private ServerSocketChannel serverChannel;
+        private Selector selector;
+        private AtomicBoolean go = new AtomicBoolean(true);
+        private ByteBuffer working = ByteBuffer.allocate( 8192 );
+
+        /**
+         *  Because we want to keep the keys to ourselves, we'll do
+         *  the endpoint -> key mapping internally.
+         */
+        private Map<NioEndpoint,SelectionKey> endpointKeys = new ConcurrentHashMap<NioEndpoint,SelectionKey>();
+
+        public SelectorThread()
+        {
+            setName( "Selector@" + address );
+            setDaemon(true);
+        }
+
+        public void connect() throws IOException
+        {
+            // Create a new selector
+            this.selector = SelectorProvider.provider().openSelector();
+
+            // Create a new non-blocking server socket channel
+            this.serverChannel = ServerSocketChannel.open();
+            serverChannel.configureBlocking(false);
+
+            // Bind the server socket to the specified address and port
+            serverChannel.socket().bind(address);
+
+            // Register the server socket channel, indicating an interest in
+            // accepting new connections
+            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
+
+            log.log( Level.INFO, "Hosting TCP connection:{0}.", address );
+        }
+
+        public void close() throws IOException, InterruptedException
+        {
+            // Set the thread to stop
+            go.set(false);
+
+            // Make sure the channel is closed
+            serverChannel.close();
+
+            // Force the selector to stop blocking
+            wakeupSelector();
+
+            // And wait for it
+            join();
+        }
+
+        protected void wakeupSelector()
+        {
+            selector.wakeup();
+        }
+
+        protected void setupSelectorOptions()
+        {
+            // For now, selection keys will either be in OP_READ
+            // or OP_WRITE.  So while we are writing a buffer, we
+            // will not be reading.  This is way simpler and less
+            // error prone... it can always be changed when everything
+            // else works if we are looking to micro-optimize.
+
+            // Setup options based on the current state of
+            // the endpoints.  This could potentially be more
+            // efficiently done as change requests... or simply
+            // keeping a thread-safe set of endpoints with pending
+            // writes.  For most cases, it shouldn't matter.
+            for( Map.Entry<NioEndpoint,SelectionKey> e : endpointKeys.entrySet() ) {
+                if( e.getKey().hasPending() ) {
+                    e.getValue().interestOps(SelectionKey.OP_WRITE);
+                }
+            }
+        }
+
+        protected void accept( SelectionKey key ) throws IOException
+        {
+            // Would only get accepts on a server channel
+            ServerSocketChannel serverChan = (ServerSocketChannel)key.channel();
+
+            // Setup the connection to be non-blocking
+            SocketChannel remoteChan = serverChan.accept();
+            remoteChan.configureBlocking(false);
+
+            // And disable Nagle's buffering algorithm... we want
+            // data to go when we put it there.
+            Socket sock = remoteChan.socket();
+            sock.setTcpNoDelay(true);
+
+            // Let the selector know we're interested in reading
+            // data from the channel
+            SelectionKey endKey = remoteChan.register( selector, SelectionKey.OP_READ );
+
+            // And now create a new endpoint
+            NioEndpoint p = addEndpoint( remoteChan );
+            endKey.attach(p);
+            endpointKeys.put(p, endKey);
+        }
+
+        protected void cancel( NioEndpoint p ) throws IOException
+        {
+            SelectionKey key = endpointKeys.remove(p);
+            if( key == null ) {
+                //log.log( Level.INFO, "Endpoint already closed:{0}.", p );
+                return;  // already closed it
+            }                
+            log.log( Level.FINE, "Endpoint keys size:{0}", endpointKeys.size() );
+
+            log.log( Level.INFO, "Closing endpoint:{0}.", p );
+            SocketChannel c = (SocketChannel)key.channel();
+
+            // Note: key.cancel() is specifically thread safe.  One of
+            //       the few things one can do with a key from another
+            //       thread.
+            key.cancel();
+            c.close();
+            removeEndpoint( p, c );
+        }
+
+        protected void cancel( SelectionKey key, SocketChannel c ) throws IOException
+        {
+            NioEndpoint p = (NioEndpoint)key.attachment();            
+            log.log( Level.INFO, "Closing channel endpoint:{0}.", p );
+            Object o = endpointKeys.remove(p);
+
+            log.log( Level.FINE, "Endpoint keys size:{0}", endpointKeys.size() );
+
+            key.cancel();
+            c.close();
+            removeEndpoint( p, c );
+        }
+
+        protected void read( SelectionKey key ) throws IOException
+        {
+            NioEndpoint p = (NioEndpoint)key.attachment();
+            SocketChannel c = (SocketChannel)key.channel();
+            working.clear();
+
+            int size;
+            try {
+                size = c.read(working);
+            } catch( IOException e ) {
+                // The remove end forcibly closed the connection...
+                // close out our end and cancel the key
+                cancel( key, c );
+                return;
+            }
+
+            if( size == -1 ) {
+                // The remote end shut down cleanly...
+                // close out our end and cancel the key
+                cancel( key, c );
+                return;
+            }
+
+            newData( p, c, working, size );
+        }
+
+        protected void write( SelectionKey key ) throws IOException
+        {
+            NioEndpoint p = (NioEndpoint)key.attachment();
+            SocketChannel c = (SocketChannel)key.channel();
+
+            // We will send what we can and move on.
+            ByteBuffer current = p.peekPending();
+            if( current == NioEndpoint.CLOSE_MARKER ) {
+                // This connection wants to be closed now
+                closeEndpoint(p);
+
+                // Nothing more to do
+                return;
+            }
+
+            c.write( current );
+
+            // If we wrote all of that packet then we need to remove it
+            if( current.remaining() == 0 ) {
+                p.removePending();
+            }
+
+            // If we happened to empty the pending queue then let's read
+            // again.
+            if( !p.hasPending() ) {
+                key.interestOps( SelectionKey.OP_READ );
+            }
+        }
+
+        protected void select() throws IOException
+        {
+            selector.select();
+
+            for( Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext(); ) {
+                SelectionKey key = i.next();
+                i.remove();
+
+                if( !key.isValid() )
+                    {
+                    // When does this happen?
+                    log.log( Level.INFO, "Key is not valid:{0}.", key );
+                    continue;
+                    }
+
+                try {
+                    if( key.isAcceptable() )
+                        accept(key);
+                    else if( key.isWritable() )
+                        write(key);
+                    else if( key.isReadable() )
+                        read(key);
+                } catch( IOException e ) {
+                    if( !go.get() )
+                        return;  // error likely due to shutting down
+                    reportError( e );
+                    
+                    // And at this level, errors likely mean the key is now
+                    // dead and it doesn't hurt to kick them anyway.  If we
+                    // find IOExceptions that are not fatal, this can be
+                    // readdressed
+                    cancel( key, (SocketChannel)key.channel() );                    
+                }                        
+            }
+        }
+
+        public void run()
+        {
+            log.log( Level.INFO, "Kernel started for connection:{0}.", address );
+
+            // An atomic is safest and costs almost nothing
+            while( go.get() ) {
+                // Setup any queued option changes
+                setupSelectorOptions();
+
+                // Check for available keys and process them
+                try {
+                    select();
+                } catch( ClosedSelectorException e ) {
+                    if( !go.get() )
+                        return;  // it's because we're shutting down
+                    throw new KernelException( "Premature selector closing", e );
+                } catch( IOException e ) {
+                    if( !go.get() )
+                        return;  // error likely due to shutting down
+                    reportError( e );
+                }
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/tcp/SocketConnector.java b/engine/src/networking/com/jme3/network/kernel/tcp/SocketConnector.java
new file mode 100644
index 0000000..66d2040
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/tcp/SocketConnector.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.tcp;
+
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.kernel.ConnectorException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+/**
+ *  A straight forward socket-based connector implementation that
+ *  does not use any separate threading.  It relies completely on
+ *  the buffering in the OS network layer.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class SocketConnector implements Connector
+{
+    private Socket sock;
+    private InputStream in;
+    private OutputStream out;
+    private SocketAddress remoteAddress;
+    private byte[] buffer = new byte[65535];
+    private AtomicBoolean connected = new AtomicBoolean(false);
+
+    public SocketConnector( InetAddress address, int port ) throws IOException
+    {
+        this.sock = new Socket(address, port);
+        remoteAddress = sock.getRemoteSocketAddress(); // for info purposes 
+        
+        // Disable Nagle's buffering so data goes out when we
+        // put it there.
+        sock.setTcpNoDelay(true);
+        
+        in = sock.getInputStream();
+        out = sock.getOutputStream();
+        
+        connected.set(true);
+    }
+ 
+    protected void checkClosed()
+    {
+        if( sock == null )
+            throw new ConnectorException( "Connection is closed:" + remoteAddress );
+    }
+     
+    public boolean isConnected()
+    {
+        if( sock == null )
+            return false;
+        return sock.isConnected();
+    }
+
+    public void close()
+    {
+        checkClosed();
+        try {
+            Socket temp = sock;
+            sock = null;            
+            connected.set(false);
+            temp.close();
+        } catch( IOException e ) {            
+            throw new ConnectorException( "Error closing socket for:" + remoteAddress, e );
+        }            
+    }     
+
+    public boolean available()
+    {
+        checkClosed();
+        try {
+            return in.available() > 0;
+        } catch( IOException e ) {
+            throw new ConnectorException( "Error retrieving data availability for:" + remoteAddress, e );
+        }       
+    }     
+    
+    public ByteBuffer read()
+    {
+        checkClosed();
+        
+        try {
+            // Read what we can
+            int count = in.read(buffer);
+            if( count < 0 ) {
+                // Socket is closed
+                close();
+                return null;
+            }
+
+            // Wrap it in a ByteBuffer for the caller
+            return ByteBuffer.wrap( buffer, 0, count ); 
+        } catch( IOException e ) {
+            if( !connected.get() ) {
+                // Nothing to see here... just move along
+                return null;
+            }        
+            throw new ConnectorException( "Error reading from connection to:" + remoteAddress, e );    
+        }                
+    }
+    
+    public void write( ByteBuffer data )
+    {
+        checkClosed();
+        
+        try {
+            out.write(data.array(), data.position(), data.remaining());
+        } catch( IOException e ) {
+            throw new ConnectorException( "Error writing to connection:" + remoteAddress, e );
+        }
+    }   
+    
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/udp/UdpConnector.java b/engine/src/networking/com/jme3/network/kernel/udp/UdpConnector.java
new file mode 100644
index 0000000..0193e1c
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/udp/UdpConnector.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.udp;
+
+import com.jme3.network.kernel.Connector;
+import com.jme3.network.kernel.ConnectorException;
+import java.io.IOException;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+/**
+ *  A straight forward datagram socket-based UDP connector 
+ *  implementation.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class UdpConnector implements Connector
+{
+    private DatagramSocket sock = new DatagramSocket();
+    private SocketAddress remoteAddress;
+    private byte[] buffer = new byte[65535];
+    private AtomicBoolean connected = new AtomicBoolean(false);
+
+    /**
+     *  In order to provide proper available() checking, we
+     *  potentially queue one datagram.
+     */
+    private DatagramPacket pending;
+
+    /**
+     *  Creates a new UDP connection that send datagrams to the
+     *  specified address and port.
+     */
+    public UdpConnector( InetAddress remote, int remotePort ) throws IOException
+    {
+        InetSocketAddress localSocketAddress = new InetSocketAddress(0);
+        this.sock = new DatagramSocket( localSocketAddress );
+        remoteAddress = new InetSocketAddress( remote, remotePort );
+        
+        // Setup to receive only from the remote address
+        sock.connect( remoteAddress );
+ 
+        connected.set(true);
+    }
+ 
+    protected void checkClosed()
+    {
+        if( sock == null )
+            throw new ConnectorException( "Connection is closed:" + remoteAddress );
+    }
+     
+    public boolean isConnected()
+    {
+        if( sock == null )
+            return false;
+        return sock.isConnected();
+    }
+
+    public void close()
+    {
+        checkClosed();
+        DatagramSocket temp = sock;
+        sock = null;
+        connected.set(false);            
+        temp.close();
+    }     
+
+    /**
+     *  This always returns false since the simple DatagramSocket usage
+     *  cannot be run in a non-blocking way.
+     */
+    public boolean available()
+    {
+        // It would take a separate thread or an NIO Selector based implementation to get this
+        // to work.  If a polling strategy is never employed by callers then it doesn't
+        // seem worth it to implement all of that just for this method.
+        checkClosed();
+        return false;
+    }     
+    
+    public ByteBuffer read()
+    {
+        checkClosed();
+        
+        try {
+            DatagramPacket packet = new DatagramPacket( buffer, buffer.length );
+            sock.receive(packet);
+            
+            // Wrap it in a ByteBuffer for the caller
+            return ByteBuffer.wrap( buffer, 0, packet.getLength() ); 
+        } catch( IOException e ) {
+            if( !connected.get() ) {
+                // Nothing to see here... just move along
+                return null;
+            }        
+            throw new ConnectorException( "Error reading from connection to:" + remoteAddress, e );    
+        }                
+    }
+    
+    public void write( ByteBuffer data )
+    {
+        checkClosed();
+        
+        try {
+            DatagramPacket p = new DatagramPacket( data.array(), data.position(), data.remaining(), 
+                                                   remoteAddress );
+            sock.send(p);
+        } catch( IOException e ) {
+            throw new ConnectorException( "Error writing to connection:" + remoteAddress, e );
+        }
+    }    
+}
+
diff --git a/engine/src/networking/com/jme3/network/kernel/udp/UdpEndpoint.java b/engine/src/networking/com/jme3/network/kernel/udp/UdpEndpoint.java
new file mode 100644
index 0000000..4a98387
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/udp/UdpEndpoint.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.udp;
+
+import com.jme3.network.kernel.Endpoint;
+import com.jme3.network.kernel.Kernel;
+import com.jme3.network.kernel.KernelException;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+
+
+/**
+ *  Endpoint implementation that encapsulates the
+ *  UDP connection information for return messaging,
+ *  identification of envelope sources, etc.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class UdpEndpoint implements Endpoint
+{
+    private long id;    
+    private SocketAddress address;
+    private DatagramSocket socket;
+    private UdpKernel kernel;
+    private boolean connected = true; // it's connectionless but we track logical state
+
+    public UdpEndpoint( UdpKernel kernel, long id, SocketAddress address, DatagramSocket socket )
+    {
+        this.id = id;
+        this.address = address;
+        this.socket = socket;
+        this.kernel = kernel;
+    }
+
+    public Kernel getKernel()
+    {
+        return kernel;
+    }
+
+    protected SocketAddress getRemoteAddress()
+    {
+        return address;
+    }
+
+    public void close()
+    {
+        close( false );
+    }
+
+    public void close( boolean flush )
+    {
+        // No real reason to flush UDP traffic yet... especially
+        // when considering that the outbound UDP isn't even
+        // queued.
+    
+        try {
+            kernel.closeEndpoint(this);
+            connected = false;
+        } catch( IOException e ) {
+            throw new KernelException( "Error closing endpoint for socket:" + socket, e );
+        }
+    }
+
+    public long getId()
+    {
+        return id;
+    }
+
+    public String getAddress()
+    {
+        return String.valueOf(address); 
+    }     
+
+    public boolean isConnected()
+    {
+        // The socket is always unconnected anyway so we track our
+        // own logical state for the kernel's benefit.
+        return connected;
+    }
+
+    public void send( ByteBuffer data )
+    {
+        if( !isConnected() ) {
+            throw new KernelException( "Endpoint is not connected:" + this );
+        }
+        
+        
+        try {
+            DatagramPacket p = new DatagramPacket( data.array(), data.position(), 
+                                                   data.remaining(), address );
+                                                   
+            // Just queue it up for the kernel threads to write
+            // out
+            kernel.enqueueWrite( this, p );
+                                                               
+            //socket.send(p);
+        } catch( IOException e ) {
+            throw new KernelException( "Error sending datagram to:" + address, e );
+        }
+    }
+
+    public String toString()
+    {
+        return "UdpEndpoint[" + id + ", " + address + "]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/kernel/udp/UdpKernel.java b/engine/src/networking/com/jme3/network/kernel/udp/UdpKernel.java
new file mode 100644
index 0000000..c0a0f88
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/kernel/udp/UdpKernel.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2011 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.network.kernel.udp;
+
+import com.jme3.network.Filter;
+import com.jme3.network.kernel.*;
+import java.io.IOException;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *  A Kernel implementation using UDP packets.
+ *
+ *  @version   $Revision: 8944 $
+ *  @author    Paul Speed
+ */
+public class UdpKernel extends AbstractKernel
+{
+    static Logger log = Logger.getLogger(UdpKernel.class.getName());
+
+    private InetSocketAddress address;
+    private HostThread thread;
+
+    private ExecutorService writer;
+    
+    // The nature of UDP means that even through a firewall,
+    // a user would have to have a unique address+port since UDP
+    // can't really be NAT'ed.
+    private Map<SocketAddress,UdpEndpoint> socketEndpoints = new ConcurrentHashMap<SocketAddress,UdpEndpoint>();
+
+    public UdpKernel( InetAddress host, int port )
+    {
+        this( new InetSocketAddress(host, port) );
+    }
+
+    public UdpKernel( int port ) throws IOException
+    {
+        this( new InetSocketAddress(port) );
+    }
+
+    public UdpKernel( InetSocketAddress address )
+    {
+        this.address = address;
+    }
+
+    protected HostThread createHostThread()
+    {
+        return new HostThread();
+    }
+
+    public void initialize()
+    {
+        if( thread != null )
+            throw new IllegalStateException( "Kernel already initialized." );
+
+        writer = Executors.newFixedThreadPool(2, new NamedThreadFactory(toString() + "-writer"));
+        
+        thread = createHostThread();
+
+        try {
+            thread.connect();
+            thread.start();
+        } catch( IOException e ) {
+            throw new KernelException( "Error hosting:" + address, e );
+        }
+    }
+
+    public void terminate() throws InterruptedException
+    {
+        if( thread == null )
+            throw new IllegalStateException( "Kernel not initialized." );
+
+        try {
+            thread.close();
+            writer.shutdown();
+            thread = null;
+        } catch( IOException e ) {
+            throw new KernelException( "Error closing host connection:" + address, e );
+        }
+    }
+
+    /**
+     *  Dispatches the data to all endpoints managed by the
+     *  kernel.  'routing' is currently ignored.
+     */
+    public void broadcast( Filter<? super Endpoint> filter, ByteBuffer data, boolean reliable,
+                           boolean copy )
+    {
+        if( reliable )
+            throw new UnsupportedOperationException( "Reliable send not supported by this kernel." );
+
+        if( copy ) {
+            // Copy the data just once
+            byte[] temp = new byte[data.remaining()];
+            System.arraycopy(data.array(), data.position(), temp, 0, data.remaining());
+            data = ByteBuffer.wrap(temp);
+        }
+
+        // Hand it to all of the endpoints that match our routing
+        for( UdpEndpoint p : socketEndpoints.values() ) {
+            // Does it match the filter?
+            if( filter != null && !filter.apply(p) )
+                continue;
+    
+            // Send the data
+            p.send( data );
+        }
+    }
+
+    protected Endpoint getEndpoint( SocketAddress address, boolean create )
+    {
+        UdpEndpoint p = socketEndpoints.get(address);
+        if( p == null && create ) {
+            p = new UdpEndpoint( this, nextEndpointId(), address, thread.getSocket() );
+            socketEndpoints.put( address, p );
+
+            // Add an event for it.
+            addEvent( EndpointEvent.createAdd( this, p ) );
+        }
+        return p;
+    }
+
+    /**
+     *  Called by the endpoints when they need to be closed.
+     */
+    protected void closeEndpoint( UdpEndpoint p ) throws IOException
+    {
+        // Just book-keeping to do here.
+        if( socketEndpoints.remove( p.getRemoteAddress() ) == null )
+            return;
+
+        log.log( Level.INFO, "Closing endpoint:{0}.", p );            
+        log.log( Level.FINE, "Socket endpoints size:{0}", socketEndpoints.size() );
+
+        addEvent( EndpointEvent.createRemove( this, p ) );
+        
+        // If there are no pending messages then add one so that the
+        // kernel-user knows to wake up if it is only listening for
+        // envelopes.
+        if( !hasEnvelopes() ) {
+            // Note: this is not really a race condition.  At worst, our
+            // event has already been handled by now and it does no harm
+            // to check again.
+            addEnvelope( EVENTS_PENDING );
+        }
+    }
+
+    protected void newData( DatagramPacket packet )
+    {
+        // So the tricky part here is figuring out the endpoint and
+        // whether it's new or not.  In these UDP schemes, firewalls have
+        // to be ported back to a specific machine so we will consider
+        // the address + port (ie: SocketAddress) the defacto unique
+        // ID.
+        Endpoint p = getEndpoint( packet.getSocketAddress(), true );
+
+        // We'll copy the data to trim it.
+        byte[] data = new byte[packet.getLength()];
+        System.arraycopy(packet.getData(), 0, data, 0, data.length);
+
+        Envelope env = new Envelope( p, data, false );
+        addEnvelope( env );
+    }
+
+    protected void enqueueWrite( Endpoint endpoint, DatagramPacket packet )
+    {
+        writer.execute( new MessageWriter(endpoint, packet) );
+    } 
+
+    protected class MessageWriter implements Runnable
+    {
+        private Endpoint endpoint;
+        private DatagramPacket packet;
+        
+        public MessageWriter( Endpoint endpoint, DatagramPacket packet )
+        {
+            this.endpoint = endpoint;
+            this.packet = packet;
+        }
+        
+        public void run()
+        {
+            // Not guaranteed to always work but an extra datagram
+            // to a dead connection isn't so big of a deal.
+            if( !endpoint.isConnected() ) {
+                return;
+            }
+            
+            try {
+                thread.getSocket().send(packet);
+            } catch( Exception e ) {
+                KernelException exc = new KernelException( "Error sending datagram to:" + address, e );
+                exc.fillInStackTrace();
+                reportError(exc);
+            }
+        } 
+    }
+
+    protected class HostThread extends Thread
+    {
+        private DatagramSocket socket;
+        private AtomicBoolean go = new AtomicBoolean(true);
+
+        private byte[] buffer = new byte[65535]; // slightly bigger than needed.
+
+        public HostThread()
+        {
+            setName( "UDP Host@" + address );
+            setDaemon(true);
+        }
+
+        protected DatagramSocket getSocket()
+        {
+            return socket;
+        }
+
+        public void connect() throws IOException
+        {
+            socket = new DatagramSocket( address );
+            log.log( Level.INFO, "Hosting UDP connection:{0}.", address );
+        }
+
+        public void close() throws IOException, InterruptedException
+        {
+            // Set the thread to stop
+            go.set(false);
+
+            // Make sure the channel is closed
+            socket.close();
+
+            // And wait for it
+            join();
+        }
+
+        public void run()
+        {
+            log.log( Level.INFO, "Kernel started for connection:{0}.", address );
+
+            // An atomic is safest and costs almost nothing
+            while( go.get() ) {
+                try {
+                    // Could reuse the packet but I don't see the
+                    // point and it may lead to subtle bugs if not properly
+                    // reset.
+                    DatagramPacket packet = new DatagramPacket( buffer, buffer.length );
+                    socket.receive(packet);
+
+                    newData( packet );
+                } catch( IOException e ) {
+                    if( !go.get() )
+                        return;
+                    reportError( e );
+                }
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/ChannelInfoMessage.java b/engine/src/networking/com/jme3/network/message/ChannelInfoMessage.java
new file mode 100644
index 0000000..91e54bc
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/ChannelInfoMessage.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011 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.network.message;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *  Contains information about any extra server channels (if they exist).  
+ *
+ *  @author Paul Speed
+ */
+@Serializable()
+public class ChannelInfoMessage extends AbstractMessage {
+    private long id;
+    private int[] ports;
+
+    public ChannelInfoMessage() {
+        super( true );        
+    }
+
+    public ChannelInfoMessage( long id, List<Integer> ports ) {
+        super( true );
+        this.id = id;
+        this.ports = new int[ports.size()];
+        for( int i = 0; i < ports.size(); i++ ) {
+            this.ports[i] = ports.get(i);
+        }        
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public int[] getPorts() {
+        return ports;
+    }
+    
+    public String toString() {
+        return "ChannelInfoMessage[" + id + ", " + Arrays.asList(ports) + "]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/ClientRegistrationMessage.java b/engine/src/networking/com/jme3/network/message/ClientRegistrationMessage.java
new file mode 100644
index 0000000..fcbf36b
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/ClientRegistrationMessage.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2009-2010 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.network.message;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ *  Client registration is a message that contains a unique ID. This ID
+ *  is simply the current time in milliseconds, providing multiple clients
+ *  will not connect to the same server within one millisecond. This is used
+ *  to couple the TCP and UDP connections together into one 'Client' on the
+ *  server.
+ *
+ * @author Lars Wesselius
+ */
+@Serializable()
+public class ClientRegistrationMessage extends AbstractMessage {
+    private long id;
+    private String gameName;
+    private int version;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+    
+    public void setGameName( String name ) {
+        this.gameName = name;
+    }
+ 
+    public String getGameName() {
+        return gameName;
+    }
+    
+    public void setVersion( int version ) {
+        this.version = version;
+    }
+    
+    public int getVersion() {
+        return version;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/CompressedMessage.java b/engine/src/networking/com/jme3/network/message/CompressedMessage.java
new file mode 100644
index 0000000..80ac2d0
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/CompressedMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009-2010 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.network.message;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.Message;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * CompressedMessage is a base class for all messages that
+ *  compress others.
+ *
+ * @author Lars Wesselius
+ */
+@Serializable()
+public class CompressedMessage extends AbstractMessage {
+    private Message message;
+
+    public CompressedMessage() { }
+
+    public CompressedMessage(Message msg) {
+        this.message = msg;
+    }
+
+    public void setMessage(Message message) {
+        this.message = message;
+    }
+
+    public Message getMessage() {
+        return message;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/DisconnectMessage.java b/engine/src/networking/com/jme3/network/message/DisconnectMessage.java
new file mode 100644
index 0000000..ab667c3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/DisconnectMessage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.network.message;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Represents a disconnect message.
+ *
+ * @author Lars Wesselius
+ */
+@Serializable()
+public class DisconnectMessage extends AbstractMessage {
+    public static final String KICK = "Kick";
+    public static final String USER_REQUESTED = "User requested";
+    public static final String ERROR = "Error";
+    public static final String FILTERED = "Filtered";
+
+    private String reason;
+    private String type;
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/GZIPCompressedMessage.java b/engine/src/networking/com/jme3/network/message/GZIPCompressedMessage.java
new file mode 100644
index 0000000..91af83d
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/GZIPCompressedMessage.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 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.network.message;
+
+import com.jme3.network.Message;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * GZIPCompressedMessage is the class that you need to use should you want to
+ *  compress a message using Gzip.
+ *
+ * @author Lars Wesselius
+ */
+@Serializable()
+public class GZIPCompressedMessage extends CompressedMessage {
+    public GZIPCompressedMessage() {
+        super();
+    }
+
+    public GZIPCompressedMessage(Message msg) {
+        super(msg);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/message/ZIPCompressedMessage.java b/engine/src/networking/com/jme3/network/message/ZIPCompressedMessage.java
new file mode 100644
index 0000000..5dff3e5
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/message/ZIPCompressedMessage.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2010 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.network.message;
+
+import com.jme3.network.Message;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Compress a message using this ZIPCompressedMessage class
+ *
+ * @author Lars Wesselius
+ */
+@Serializable()
+public class ZIPCompressedMessage extends CompressedMessage {
+    private static int compressionLevel = 6;
+
+    public ZIPCompressedMessage() {
+        super();
+    }
+
+    public ZIPCompressedMessage(Message msg) {
+        super(msg);
+    }
+
+    public ZIPCompressedMessage(Message msg, int level) {
+        super(msg);
+        setLevel(level);
+    }
+
+    /**
+     * Set the compression level, where 1 is the best compression but slower and 9 is the weakest
+     *  compression but the quickest. Default is 6.
+     *
+     * @param level The level.
+     */
+    public static void setLevel(int level) {
+        compressionLevel = level;
+    }
+
+    public int getLevel() { return compressionLevel; }
+}
diff --git a/engine/src/networking/com/jme3/network/package.html b/engine/src/networking/com/jme3/network/package.html
new file mode 100644
index 0000000..46dfa80
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>    
+<body>
+The network package contains the public API for the jME3 
+SpiderMonkey networking module.  The {@link com.jme3.network.Network}
+class is the entry point for creating default implementations
+of {@link com.jme3.network.Client} and {@link com.jme3.network.Server}
+implementations.
+</body>
+</html>
diff --git a/engine/src/networking/com/jme3/network/rmi/LocalObject.java b/engine/src/networking/com/jme3/network/rmi/LocalObject.java
new file mode 100644
index 0000000..a6477aa
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/LocalObject.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import java.lang.reflect.Method;
+
+/**
+ * Describes a RMI interface on the local machine.
+ *
+ * @author Kirill Vainer
+ */
+public class LocalObject {
+
+    /**
+     * Object name
+     */
+    String objectName;
+
+    /**
+     * The RMI interface implementation
+     */
+    Object theObject;
+
+    /**
+     * Shared Object ID
+     */
+    short objectId;
+
+    /**
+     * Methods exposed by the RMI interface. The "methodID" is used
+     * to look-up methods in this array.
+     */
+    Method[] methods;
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/MethodDef.java b/engine/src/networking/com/jme3/network/rmi/MethodDef.java
new file mode 100644
index 0000000..9164e06
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/MethodDef.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+
+/**
+ * Method definition is used to map methods on an RMI interface
+ * to an implementation on a remote machine.
+ *
+ * @author Kirill Vainer
+ */
+public class MethodDef {
+
+    /**
+     * Method name
+     */
+    public String name;
+
+    /**
+     * Return type
+     */
+    public Class<?> retType;
+
+    /**
+     * Parameter types
+     */
+    public Class<?>[] paramTypes;
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/ObjectDef.java b/engine/src/networking/com/jme3/network/rmi/ObjectDef.java
new file mode 100644
index 0000000..6a338ae
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/ObjectDef.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+
+import com.jme3.network.serializing.Serializable;
+import java.lang.reflect.Method;
+
+@Serializable
+public class ObjectDef {
+
+    /**
+     * The object name, can be null if undefined.
+     */
+    public String   objectName;
+
+    /**
+     * Object ID
+     */
+    public int    objectId;
+
+    /**
+     * Methods of the implementation on the local client. Set to null
+     * on remote clients.
+     */
+    public Method[] methods;
+
+    /**
+     * Method definitions of the implementation. Set to null on
+     * the local client.
+     */
+    public MethodDef[] methodDefs;
+
+    @Override
+    public String toString(){
+        return "ObjectDef[name=" + objectName + ", objectId=" + objectId+"]";
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/ObjectStore.java b/engine/src/networking/com/jme3/network/rmi/ObjectStore.java
new file mode 100644
index 0000000..137320d
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/ObjectStore.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.ClientStateListener.DisconnectInfo;
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ObjectStore {
+
+    private static final Logger logger = Logger.getLogger(ObjectStore.class.getName());
+
+    private static final class Invocation {
+
+        Object retVal;
+        boolean available = false;
+
+        @Override
+        public String toString(){
+            return "Invocation[" + retVal + "]";
+        }
+    }
+
+    private Client client;
+    private Server server;
+    
+    private ClientEventHandler clientEventHandler = new ClientEventHandler();
+    private ServerEventHandler serverEventHandler = new ServerEventHandler();
+
+    // Local object ID counter
+    private volatile short objectIdCounter = 0;
+    
+    // Local invocation ID counter
+    private volatile short invocationIdCounter = 0;
+
+    // Invocations waiting ..
+    private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>();
+    
+    // Objects I share with other people
+    private IntMap<LocalObject> localObjects = new IntMap<LocalObject>();
+
+    // Objects others share with me
+    private HashMap<String, RemoteObject> remoteObjects = new HashMap<String, RemoteObject>();
+    private IntMap<RemoteObject> remoteObjectsById = new IntMap<RemoteObject>();
+
+    private final Object receiveObjectLock = new Object();
+
+    public class ServerEventHandler implements MessageListener<HostedConnection>,
+                                                      ConnectionListener {
+
+        public void messageReceived(HostedConnection source, Message m) {
+            onMessage(source, m);
+        }
+
+        public void connectionAdded(Server server, HostedConnection conn) {
+            onConnection(conn);
+        }
+
+        public void connectionRemoved(Server server, HostedConnection conn) {
+        }
+        
+    } 
+    
+    public class ClientEventHandler implements MessageListener,
+                                                      ClientStateListener {
+
+        public void messageReceived(Object source, Message m) {
+            onMessage(null, m);
+        }
+
+        public void clientConnected(Client c) {
+            onConnection(null);
+        }
+
+        public void clientDisconnected(Client c, DisconnectInfo info) {
+        }
+        
+    }
+    
+    static {
+        Serializer s = new RmiSerializer();
+        Serializer.registerClass(RemoteObjectDefMessage.class, s);
+        Serializer.registerClass(RemoteMethodCallMessage.class, s);
+        Serializer.registerClass(RemoteMethodReturnMessage.class, s);
+    }
+
+    public ObjectStore(Client client) {
+        this.client = client;
+        client.addMessageListener(clientEventHandler, 
+                RemoteObjectDefMessage.class,
+                RemoteMethodCallMessage.class,
+                RemoteMethodReturnMessage.class);
+        client.addClientStateListener(clientEventHandler);
+    }
+
+    public ObjectStore(Server server) {
+        this.server = server;
+        server.addMessageListener(serverEventHandler, 
+                RemoteObjectDefMessage.class,
+                RemoteMethodCallMessage.class,
+                RemoteMethodReturnMessage.class);
+        server.addConnectionListener(serverEventHandler);
+    }
+
+    private ObjectDef makeObjectDef(LocalObject localObj){
+        ObjectDef def = new ObjectDef();
+        def.objectName = localObj.objectName;
+        def.objectId   = localObj.objectId;
+        def.methods    = localObj.methods;
+        return def;
+    }
+
+    public void exposeObject(String name, Object obj) throws IOException{
+        // Create a local object
+        LocalObject localObj = new LocalObject();
+        localObj.objectName = name;
+        localObj.objectId  = objectIdCounter++;
+        localObj.theObject = obj;
+        //localObj.methods   = obj.getClass().getMethods();
+        
+        ArrayList<Method> methodList = new ArrayList<Method>();
+        for (Method method : obj.getClass().getMethods()){
+            if (method.getDeclaringClass() == obj.getClass()){
+                methodList.add(method);
+            }
+        }
+        localObj.methods = methodList.toArray(new Method[methodList.size()]);  
+        
+        // Put it in the store
+        localObjects.put(localObj.objectId, localObj);
+
+        // Inform the others of its existence
+        RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
+        defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) };
+
+        if (client != null) {
+            client.send(defMsg);
+            logger.log(Level.INFO, "Client: Sending {0}", defMsg);
+        } else {
+            server.broadcast(defMsg);
+            logger.log(Level.INFO, "Server: Sending {0}", defMsg);
+        }
+    }
+
+    public <T> T getExposedObject(String name, Class<T> type, boolean waitFor) throws InterruptedException{
+        RemoteObject ro = remoteObjects.get(name);
+        if (ro == null){
+            if (!waitFor)
+                throw new RuntimeException("Cannot find remote object named: " + name);
+            else{
+                do {
+                    synchronized (receiveObjectLock){
+                        receiveObjectLock.wait();
+                    }
+                } while ( (ro = remoteObjects.get(name)) == null );
+            }
+        }
+            
+        Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro);
+        ro.loadMethods(type);
+        return (T) proxy;
+    }
+
+    Object invokeRemoteMethod(RemoteObject remoteObj, Method method, Object[] args){
+        Integer methodIdInt = remoteObj.methodMap.get(method);
+        if (methodIdInt == null)
+             throw new RuntimeException("Method not implemented by remote object owner: "+method);
+
+        boolean needReturn = method.getReturnType() != void.class;
+        short objectId = remoteObj.objectId;
+        short methodId = methodIdInt.shortValue();
+        RemoteMethodCallMessage call = new RemoteMethodCallMessage();
+        call.methodId = methodId;
+        call.objectId = objectId;
+        call.args = args;
+
+        Invocation invoke = null;
+        if (needReturn){
+            call.invocationId = invocationIdCounter++;
+            invoke = new Invocation();
+            // Note: could cause threading issues if used from multiple threads
+            pendingInvocations.put(call.invocationId, invoke);
+        }
+
+        if (server != null){
+            remoteObj.client.send(call);
+            logger.log(Level.INFO, "Server: Sending {0}", call);
+        }else{
+            client.send(call);
+            logger.log(Level.INFO, "Client: Sending {0}", call);
+        }
+       
+        if (invoke != null){
+            synchronized(invoke){
+                while (!invoke.available){
+                    try {
+                        invoke.wait();
+                    } catch (InterruptedException ex){
+                        ex.printStackTrace();
+                    }
+                }
+            }
+            // Note: could cause threading issues if used from multiple threads
+            pendingInvocations.remove(call.invocationId);
+            return invoke.retVal;
+        }else{
+            return null;
+        }
+    }
+
+    private void onMessage(HostedConnection source, Message message) {
+        // Might want to do more strict validation of the data
+        // in the message to prevent crashes
+
+        if (message instanceof RemoteObjectDefMessage){
+            RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message;
+
+            ObjectDef[] defs = defMsg.objects;
+            for (ObjectDef def : defs){
+                RemoteObject remoteObject = new RemoteObject(this, source);
+                remoteObject.objectId = (short)def.objectId;
+                remoteObject.methodDefs = def.methodDefs;
+                remoteObjects.put(def.objectName, remoteObject);
+                remoteObjectsById.put(def.objectId, remoteObject);
+            }
+            
+            synchronized (receiveObjectLock){
+                receiveObjectLock.notifyAll();
+            }
+        }else if (message instanceof RemoteMethodCallMessage){
+            RemoteMethodCallMessage call = (RemoteMethodCallMessage) message;
+            LocalObject localObj = localObjects.get(call.objectId);
+            if (localObj == null)
+                return;
+
+            if (call.methodId < 0 || call.methodId >= localObj.methods.length)
+                return;
+
+            Object obj = localObj.theObject;
+            Method method = localObj.methods[call.methodId];
+            Object[] args = call.args;
+            Object ret = null;
+            try {
+                ret = method.invoke(obj, args);
+            } catch (IllegalAccessException ex){
+                logger.log(Level.WARNING, "RMI: Error accessing method", ex);
+            } catch (IllegalArgumentException ex){
+                logger.log(Level.WARNING, "RMI: Invalid arguments", ex);
+            } catch (InvocationTargetException ex){
+                logger.log(Level.WARNING, "RMI: Invocation exception", ex);
+            }
+
+            if (method.getReturnType() != void.class){
+                // send return value back
+                RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage();
+                retMsg.invocationID = call.invocationId;
+                retMsg.retVal = ret;
+                if (server != null){
+                    source.send(retMsg);
+                    logger.log(Level.INFO, "Server: Sending {0}", retMsg);
+                } else{
+                    client.send(retMsg);
+                    logger.log(Level.INFO, "Client: Sending {0}", retMsg);
+                }
+            }
+        }else if (message instanceof RemoteMethodReturnMessage){
+            RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message;
+            Invocation invoke = pendingInvocations.get(retMsg.invocationID);
+            if (invoke == null){
+                logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID);
+                return;
+            }
+
+            synchronized (invoke){
+                invoke.retVal = retMsg.retVal;
+                invoke.available = true;
+                invoke.notifyAll();
+            }
+        }
+    }
+
+    private void onConnection(HostedConnection conn) {
+        if (localObjects.size() > 0){
+            // send a object definition message
+            ObjectDef[] defs = new ObjectDef[localObjects.size()];
+            int i = 0;
+            for (Entry<LocalObject> entry : localObjects){
+                defs[i] = makeObjectDef(entry.getValue());
+                i++;
+            }
+
+            RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
+            defMsg.objects = defs;
+            if (this.client != null){
+                this.client.send(defMsg);
+                logger.log(Level.INFO, "Client: Sending {0}", defMsg);
+            } else{
+                conn.send(defMsg);
+                logger.log(Level.INFO, "Server: Sending {0}", defMsg);
+            }
+        }
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java
new file mode 100644
index 0000000..6870ec4
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Sent to a remote client to make a remote method invocation.
+ *
+ * @author Kirill Vainer
+ */
+@Serializable
+public class RemoteMethodCallMessage extends AbstractMessage {
+
+    public RemoteMethodCallMessage(){
+        super(true);
+    }
+
+    /**
+     * The object ID on which the call is being made.
+     */
+    public int objectId;
+
+    /**
+     * The method ID used for look-up in the LocalObject.methods array.
+     */
+    public short methodId;
+
+    /**
+     * Invocation ID is used to identify a particular call if the calling
+     * client needs the return value of the called RMI method.
+     * This is set to zero if the method does not return a value.
+     */
+    public short invocationId;
+
+    /**
+     * Arguments of the remote method invocation.
+     */
+    public Object[] args;
+
+    
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        sb.append("RemoteMethodCallMessage[objectID=").append(objectId).append(", methodID=")
+          .append(methodId);
+        if (args != null && args.length > 0){
+            sb.append(", args={");
+            for (Object arg : args){
+                sb.append(arg.toString()).append(", ");
+            }
+            sb.setLength(sb.length()-2);
+            sb.append("}");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java
new file mode 100644
index 0000000..b95f5c8
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Contains the return value for a remote method invocation, sent as a response
+ * to a {@link RemoteMethodCallMessage} with a non-zero invocationID.
+ *
+ * @author Kirill Vainer.
+ */
+@Serializable
+public class RemoteMethodReturnMessage extends AbstractMessage {
+
+    public RemoteMethodReturnMessage(){
+        super(true);
+    }
+
+    /**
+     * Invocation ID that was set in the {@link RemoteMethodCallMessage}.
+     */
+    public short invocationID;
+
+    /**
+     * The return value, could be null.
+     */
+    public Object retVal;
+
+
+    @Override
+    public String toString(){
+        return "RemoteMethodReturnMessage[ID="+invocationID+", Value="+retVal.toString()+"]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteObject.java b/engine/src/networking/com/jme3/network/rmi/RemoteObject.java
new file mode 100644
index 0000000..24a9463
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/RemoteObject.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.HostedConnection;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Contains various meta-data about an RMI interface.
+ *
+ * @author Kirill Vainer
+ */
+public class RemoteObject implements InvocationHandler {
+
+    /**
+     * Object ID
+     */
+    short objectId;
+
+    /**
+     * Contains {@link MethodDef method definitions} for all exposed
+     * RMI methods in the remote RMI interface.
+     */
+    MethodDef[] methodDefs;
+
+    /**
+     * Maps from methods locally retrieved from the RMI interface to
+     * a method ID.
+     */
+    HashMap<Method, Integer> methodMap = new HashMap<Method, Integer>();
+
+    /**
+     * The {@link ObjectStore} which stores this RMI interface.
+     */
+    ObjectStore store;
+    
+    /**
+     * The client who exposed the RMI interface, or null if the server
+     * exposed it.
+     */
+    HostedConnection client;
+
+    public RemoteObject(ObjectStore store, HostedConnection client){
+        this.store = store;
+        this.client = client;
+    }
+
+    private boolean methodEquals(MethodDef methodDef, Method method){
+        Class<?>[] interfaceTypes = method.getParameterTypes();
+        Class<?>[] defTypes       = methodDef.paramTypes;
+
+        if (interfaceTypes.length == defTypes.length){
+            for (int i = 0; i < interfaceTypes.length; i++){
+                if (!defTypes[i].isAssignableFrom(interfaceTypes[i])){
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Generates mappings from the given interface into the remote RMI
+     * interface's implementation.
+     *
+     * @param interfaceClass The interface class to use.
+     */
+    public void loadMethods(Class<?> interfaceClass){
+        HashMap<String, ArrayList<Method>> nameToMethods
+                = new HashMap<String, ArrayList<Method>>();
+
+        for (Method method : interfaceClass.getDeclaredMethods()){
+            ArrayList<Method> list = nameToMethods.get(method.getName());
+            if (list == null){
+                list = new ArrayList<Method>();
+                nameToMethods.put(method.getName(), list);
+            }
+            list.add(method);
+        }
+
+        mapping_search: for (int i = 0; i < methodDefs.length; i++){
+            MethodDef methodDef = methodDefs[i];
+            ArrayList<Method> methods = nameToMethods.get(methodDef.name);
+            if (methods == null)
+                continue;
+            
+            for (Method method : methods){
+                if (methodEquals(methodDef, method)){
+                    methodMap.put(method, i);
+                    continue mapping_search;
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback from InvocationHandler.
+     */
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        return store.invokeRemoteMethod(this, method, args);
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java
new file mode 100644
index 0000000..b4639ae
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+/**
+ * Sent to expose RMI interfaces on the local client to other clients.
+ * @author Kirill Vainer
+ */
+@Serializable
+public class RemoteObjectDefMessage extends AbstractMessage {
+
+    public ObjectDef[] objects;
+    
+    public RemoteObjectDefMessage(){
+        super(true);
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        sb.append("RemoteObjectDefMessage[\n");
+        for (ObjectDef def : objects){
+            sb.append("\t").append(def).append("\n");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java b/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java
new file mode 100644
index 0000000..76709dc
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2009-2010 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.network.rmi;
+
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerRegistration;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * {@link RmiSerializer} is responsible for serializing RMI messages
+ * like define object, call, and return.
+ *
+ * @author Kirill Vainer
+ */
+public class RmiSerializer extends Serializer {
+
+    private static final Logger logger = Logger.getLogger(RmiSerializer.class.getName());
+
+    // not good for multithread applications
+    private char[] chrBuf = new char[256];
+
+    private void writeString(ByteBuffer buffer, String string) throws IOException{
+        int length = string.length();
+        if (length > 255){
+            logger.log(Level.WARNING, "The string length exceeds the limit! {0} > 255", length);
+            buffer.put( (byte) 0 );
+            return;
+        }
+
+        buffer.put( (byte) length );
+        for (int i = 0; i < length; i++){
+            buffer.put( (byte) string.charAt(i) );
+        }
+    }
+
+    private String readString(ByteBuffer buffer){
+        int length = buffer.get() & 0xff;
+        for (int i = 0; i < length; i++){
+            chrBuf[i] = (char) (buffer.get() & 0xff);
+        }
+        return String.valueOf(chrBuf, 0, length);
+    }
+
+    private void writeType(ByteBuffer buffer, Class<?> clazz) throws IOException{
+        if (clazz == void.class){
+            buffer.putShort((short)0);
+        } else {
+            SerializerRegistration reg = Serializer.getSerializerRegistration(clazz);
+            if (reg == null){
+                logger.log(Level.WARNING, "Unknown class: {0}", clazz);
+                throw new IOException(); // prevents message from being serialized
+            }
+            buffer.putShort(reg.getId());
+        }
+    }
+
+    private Class<?> readType(ByteBuffer buffer) throws IOException{
+        SerializerRegistration reg = Serializer.readClass(buffer);
+        if (reg == null){
+            // either "void" or unknown val
+            short id = buffer.getShort(buffer.position()-2);
+            if (id == 0){
+                return void.class;
+            } else{
+                logger.log(Level.WARNING, "Undefined class ID: {0}", id);
+                throw new IOException(); // prevents message from being serialized
+            }
+        }
+        return reg.getType();
+    }
+
+    private void writeMethod(ByteBuffer buffer, Method method) throws IOException{
+        String     name = method.getName();
+        Class<?>[] paramTypes = method.getParameterTypes();
+        Class<?>   returnType = method.getReturnType();
+
+        writeString(buffer, name);
+        writeType(buffer, returnType);
+        buffer.put((byte)paramTypes.length);
+        for (Class<?> paramType : paramTypes)
+            writeType(buffer, paramType);
+    }
+
+    private MethodDef readMethod(ByteBuffer buffer) throws IOException{
+        String name = readString(buffer);
+        Class<?> retType = readType(buffer);
+        
+        int numParams = buffer.get() & 0xff;
+        Class<?>[] paramTypes = new Class<?>[numParams];
+        for (int i = 0; i < numParams; i++){
+            paramTypes[i] = readType(buffer);
+        }
+
+        MethodDef def = new MethodDef();
+        def.name = name;
+        def.paramTypes = paramTypes;
+        def.retType = retType;
+        return def;
+    }
+
+    private void writeObjectDef(ByteBuffer buffer, ObjectDef def) throws IOException{
+        buffer.putShort((short)def.objectId);
+        writeString(buffer, def.objectName);
+        Method[] methods = def.methods;
+        buffer.put( (byte) methods.length );
+        for (Method method : methods){
+            writeMethod(buffer, method);
+        }
+    }
+
+    private ObjectDef readObjectDef(ByteBuffer buffer) throws IOException{
+        ObjectDef def = new ObjectDef();
+
+        def.objectId = buffer.getShort();
+        def.objectName = readString(buffer);
+
+        int numMethods = buffer.get() & 0xff;
+        MethodDef[] methodDefs = new MethodDef[numMethods];
+        for (int i = 0; i < numMethods; i++){
+            methodDefs[i] = readMethod(buffer);
+        }
+        def.methodDefs = methodDefs;
+        return def;
+    }
+
+    private void writeObjectDefs(ByteBuffer buffer, RemoteObjectDefMessage defMsg) throws IOException{
+        ObjectDef[] defs = defMsg.objects;
+        buffer.put( (byte) defs.length );
+        for (ObjectDef def : defs)
+            writeObjectDef(buffer, def);
+    }
+
+    private RemoteObjectDefMessage readObjectDefs(ByteBuffer buffer) throws IOException{
+        RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
+        int numObjs = buffer.get() & 0xff;
+        ObjectDef[] defs = new ObjectDef[numObjs];
+        for (int i = 0; i < numObjs; i++){
+            defs[i] = readObjectDef(buffer);
+        }
+        defMsg.objects = defs;
+        return defMsg;
+    }
+
+    private void writeMethodCall(ByteBuffer buffer, RemoteMethodCallMessage call) throws IOException{
+        buffer.putShort((short)call.objectId);
+        buffer.putShort(call.methodId);
+        buffer.putShort(call.invocationId);
+        if (call.args == null){
+            buffer.put((byte)0);
+        }else{
+            buffer.put((byte)call.args.length);
+
+            // Right now it writes 0 for every null argument
+            // and 1 for every non-null argument followed by the serialized
+            // argument. For the future, using a bit set should be considered.
+            for (Object obj : call.args){
+                if (obj != null){
+                    buffer.put((byte)0x01);
+                    Serializer.writeClassAndObject(buffer, obj);
+                }else{
+                    buffer.put((byte)0x00);
+                }
+            }
+        }
+    }
+
+    private RemoteMethodCallMessage readMethodCall(ByteBuffer buffer) throws IOException{
+        RemoteMethodCallMessage call = new RemoteMethodCallMessage();
+        call.objectId = buffer.getShort();
+        call.methodId = buffer.getShort();
+        call.invocationId = buffer.getShort();
+        int numArgs = buffer.get() & 0xff;
+        if (numArgs > 0){
+            Object[] args = new Object[numArgs];
+            for (int i = 0; i < numArgs; i++){
+                if (buffer.get() == (byte)0x01){
+                    args[i] = Serializer.readClassAndObject(buffer);
+                }
+            }
+            call.args = args;
+        }
+        return call;
+    }
+
+    private void writeMethodReturn(ByteBuffer buffer, RemoteMethodReturnMessage ret) throws IOException{
+        buffer.putShort(ret.invocationID);
+        if (ret.retVal != null){
+            buffer.put((byte)0x01);
+            Serializer.writeClassAndObject(buffer, ret.retVal);
+        }else{
+            buffer.put((byte)0x00);
+        }
+    }
+
+    private RemoteMethodReturnMessage readMethodReturn(ByteBuffer buffer) throws IOException{
+        RemoteMethodReturnMessage ret = new RemoteMethodReturnMessage();
+        ret.invocationID = buffer.getShort();
+        if (buffer.get() == (byte)0x01){
+            ret.retVal = Serializer.readClassAndObject(buffer);
+        }
+        return ret;
+    }
+            
+    @Override
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        if (c == RemoteObjectDefMessage.class){
+            return (T) readObjectDefs(data);
+        }else if (c == RemoteMethodCallMessage.class){
+            return (T) readMethodCall(data);
+        }else if (c == RemoteMethodReturnMessage.class){
+            return (T) readMethodReturn(data);
+        }
+        return null;
+    }
+
+    @Override
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+//        int p = buffer.position();
+        if (object instanceof RemoteObjectDefMessage){
+            RemoteObjectDefMessage def = (RemoteObjectDefMessage) object;
+            writeObjectDefs(buffer, def);
+        }else if (object instanceof RemoteMethodCallMessage){
+            RemoteMethodCallMessage call = (RemoteMethodCallMessage) object;
+            writeMethodCall(buffer, call);
+        }else if (object instanceof RemoteMethodReturnMessage){
+            RemoteMethodReturnMessage ret = (RemoteMethodReturnMessage) object;
+            writeMethodReturn(buffer, ret);
+        }
+//        p = buffer.position() - p;
+//        System.out.println(object+": uses " + p + " bytes");
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/Serializable.java b/engine/src/networking/com/jme3/network/serializing/Serializable.java
new file mode 100644
index 0000000..751084e
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/Serializable.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing;
+
+import com.jme3.network.serializing.serializers.FieldSerializer;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Use this annotation when a class is going to be transferred
+ *  over the network.
+ *
+ * @author Lars Wesselius
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Serializable {
+    Class serializer() default FieldSerializer.class;
+    short id() default 0;
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/Serializer.java b/engine/src/networking/com/jme3/network/serializing/Serializer.java
new file mode 100644
index 0000000..db87b54
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/Serializer.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing;
+
+import com.jme3.math.Vector3f;
+import com.jme3.network.message.ChannelInfoMessage;
+import com.jme3.network.message.ClientRegistrationMessage;
+import com.jme3.network.message.DisconnectMessage;
+import com.jme3.network.message.GZIPCompressedMessage;
+import com.jme3.network.message.ZIPCompressedMessage;
+import com.jme3.network.serializing.serializers.*;
+import java.beans.beancontext.BeanContextServicesSupport;
+import java.beans.beancontext.BeanContextSupport;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The main serializer class, which will serialize objects such that
+ *  they can be sent across the network. Serializing classes should extend
+ *  this to provide their own serialization.
+ *
+ * @author Lars Wesselius
+ */
+public abstract class Serializer {
+    protected static final Logger log = Logger.getLogger(Serializer.class.getName());
+
+    private static final SerializerRegistration NULL_CLASS = new SerializerRegistration( null, Void.class, (short)-1 );
+
+    private static final Map<Short, SerializerRegistration> idRegistrations         = new HashMap<Short, SerializerRegistration>();
+    private static final Map<Class, SerializerRegistration> classRegistrations      = new HashMap<Class, SerializerRegistration>();
+
+    private static final Serializer                         fieldSerializer         = new FieldSerializer();
+    private static final Serializer                         serializableSerializer  = new SerializableSerializer();
+    private static final Serializer                         arraySerializer         = new ArraySerializer();
+
+    private static short                                    nextId                  = -1;
+
+    private static boolean strictRegistration = true;
+
+    /****************************************************************
+     ****************************************************************
+     ****************************************************************
+     
+        READ THIS BEFORE CHANGING ANYTHING BELOW
+        
+        If a registration is moved or removed before the 
+        ClientRegistrationMessage then it screws up the application's
+        ability to gracefully warn users about bad versions. 
+ 
+        There really needs to be a version rolled into the protocol
+        and I intend to do that very soon.  In the mean time, don't
+        edit the static registrations without decrementing nextId
+        appropriately.
+        
+        Yes, that's how fragile this is.  Live and learn.       
+     
+     ****************************************************************     
+     ****************************************************************
+     ****************************************************************/
+
+
+    // Registers the classes we already have serializers for.
+    static {
+        registerClass(boolean.class,   new BooleanSerializer());
+        registerClass(byte.class,      new ByteSerializer());
+        registerClass(char.class,      new CharSerializer());
+        registerClass(short.class,     new ShortSerializer());
+        registerClass(int.class,       new IntSerializer());
+        registerClass(long.class,      new LongSerializer());
+        registerClass(float.class,     new FloatSerializer());
+        registerClass(double.class,    new DoubleSerializer());
+
+        registerClass(Boolean.class,   new BooleanSerializer());
+        registerClass(Byte.class,      new ByteSerializer());
+        registerClass(Character.class, new CharSerializer());
+        registerClass(Short.class,     new ShortSerializer());
+        registerClass(Integer.class,   new IntSerializer());
+        registerClass(Long.class,      new LongSerializer());
+        registerClass(Float.class,     new FloatSerializer());
+        registerClass(Double.class,    new DoubleSerializer());
+        registerClass(String.class,    new StringSerializer());
+
+        registerClass(Vector3f.class,  new Vector3Serializer());
+
+        registerClass(Date.class,      new DateSerializer());
+        
+        // all the Collection classes go here
+        registerClass(AbstractCollection.class,         new CollectionSerializer());
+        registerClass(AbstractList.class,               new CollectionSerializer());
+        registerClass(AbstractSet.class,                new CollectionSerializer());
+        registerClass(ArrayList.class,                  new CollectionSerializer());
+        registerClass(BeanContextServicesSupport.class, new CollectionSerializer());
+        registerClass(BeanContextSupport.class,         new CollectionSerializer());
+        registerClass(HashSet.class,                    new CollectionSerializer());
+        registerClass(LinkedHashSet.class,              new CollectionSerializer());
+        registerClass(LinkedList.class,                 new CollectionSerializer());
+        registerClass(TreeSet.class,                    new CollectionSerializer());
+        registerClass(Vector.class,                     new CollectionSerializer());
+        
+        // All the Map classes go here
+        registerClass(AbstractMap.class,                new MapSerializer());
+        registerClass(Attributes.class,                 new MapSerializer());
+        registerClass(HashMap.class,                    new MapSerializer());
+        registerClass(Hashtable.class,                  new MapSerializer());
+        registerClass(IdentityHashMap.class,            new MapSerializer());
+        registerClass(TreeMap.class,                    new MapSerializer());
+        registerClass(WeakHashMap.class,                new MapSerializer());
+        
+        registerClass(Enum.class,      new EnumSerializer());
+        registerClass(GZIPCompressedMessage.class, new GZIPSerializer());
+        registerClass(ZIPCompressedMessage.class, new ZIPSerializer());
+
+        registerClass(DisconnectMessage.class);
+        registerClass(ClientRegistrationMessage.class);
+        registerClass(ChannelInfoMessage.class);
+    }
+    
+    /**
+     *  When set to true, classes that do not have intrinsic IDs in their
+     *  @Serializable will not be auto-registered during write.  Defaults
+     *  to true since this is almost never desired behavior with the way
+     *  this code works.  Set to false to get the old permissive behavior.
+     */
+    public static void setStrictRegistration( boolean b ) {
+        strictRegistration = b;
+    }
+
+    public static SerializerRegistration registerClass(Class cls) {
+        return registerClass(cls, true);
+    }
+    
+    public static void registerClasses(Class... classes) {
+        for( Class c : classes ) {
+            registerClass(c);
+        }
+    }
+    
+    /**
+     *  Registers the specified class. The failOnMiss flag controls whether or
+     *  not this method returns null for failed registration or throws an exception.
+     */
+    @SuppressWarnings("unchecked")
+    public static SerializerRegistration registerClass(Class cls, boolean failOnMiss) {
+        if (cls.isAnnotationPresent(Serializable.class)) {
+            Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class);
+
+            Class serializerClass = serializable.serializer();
+            short classId = serializable.id();
+            if (classId == 0) classId = --nextId;
+
+            Serializer serializer = getSerializer(serializerClass, false);
+
+            if (serializer == null) serializer = fieldSerializer;
+
+            SerializerRegistration existingReg = getExactSerializerRegistration(cls);
+
+            if (existingReg != null) classId = existingReg.getId();
+            SerializerRegistration reg = new SerializerRegistration(serializer, cls, classId);
+
+            idRegistrations.put(classId, reg);
+            classRegistrations.put(cls, reg);
+
+            serializer.initialize(cls);
+
+            log.log( Level.INFO, "Registered class[" + classId + "]:{0}.", cls );
+            return reg;
+        }
+        if (failOnMiss) {
+            throw new IllegalArgumentException( "Class is not marked @Serializable:" + cls );            
+        }
+        return null;
+    }
+
+    /**
+     *  @deprecated This cannot be implemented in a reasonable way that works in
+     *              all deployment methods.
+     */
+    @Deprecated
+    public static SerializerRegistration[] registerPackage(String pkgName) {
+        try {
+            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+            String path = pkgName.replace('.', '/');
+            Enumeration<URL> resources = classLoader.getResources(path);
+            List<File> dirs = new ArrayList<File>();
+            while (resources.hasMoreElements()) {
+                URL resource = resources.nextElement();
+                dirs.add(new File(resource.getFile()));
+            }
+            ArrayList<Class> classes = new ArrayList<Class>();
+            for (File directory : dirs) {
+                classes.addAll(findClasses(directory, pkgName));
+            }
+
+            SerializerRegistration[] registeredClasses = new SerializerRegistration[classes.size()];
+            for (int i = 0; i != classes.size(); ++i) {
+                Class clz = classes.get(i);
+                registeredClasses[i] = registerClass(clz, false);
+            }
+            return registeredClasses;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return new SerializerRegistration[0];
+    }
+
+    private static List<Class> findClasses(File dir, String pkgName) throws ClassNotFoundException {
+        List<Class> classes = new ArrayList<Class>();
+        if (!dir.exists()) {
+            return classes;
+        }
+        File[] files = dir.listFiles();
+        for (File file : files) {
+            if (file.isDirectory()) {
+                assert !file.getName().contains(".");
+                classes.addAll(findClasses(file, pkgName + "." + file.getName()));
+            } else if (file.getName().endsWith(".class")) {
+                classes.add(Class.forName(pkgName + '.' + file.getName().substring(0, file.getName().length() - 6)));
+            }
+        }
+        return classes;
+    }
+
+    public static SerializerRegistration registerClass(Class cls, Serializer serializer) {
+        SerializerRegistration existingReg = getExactSerializerRegistration(cls);
+
+        short id;
+        if (existingReg != null) { 
+            id = existingReg.getId();
+        } else {
+            id = --nextId;
+        }            
+        SerializerRegistration reg = new SerializerRegistration(serializer, cls, id);
+
+        idRegistrations.put(id, reg);
+        classRegistrations.put(cls, reg);
+        
+        log.log( Level.INFO, "Registered class[" + id + "]:{0} to:" + serializer, cls );
+
+        serializer.initialize(cls);
+
+        return reg;
+    }
+
+    public static Serializer getExactSerializer(Class cls) {
+        return classRegistrations.get(cls).getSerializer();
+    }
+
+    public static Serializer getSerializer(Class cls) {
+        return getSerializer(cls, true);
+    }
+    
+    public static Serializer getSerializer(Class cls, boolean failOnMiss) {
+        return getSerializerRegistration(cls, failOnMiss).getSerializer();
+    }
+
+    public static SerializerRegistration getExactSerializerRegistration(Class cls) {
+        return classRegistrations.get(cls);
+    }
+    
+    public static SerializerRegistration getSerializerRegistration(Class cls) {
+        return getSerializerRegistration(cls, strictRegistration); 
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static SerializerRegistration getSerializerRegistration(Class cls, boolean failOnMiss) {
+        SerializerRegistration reg = classRegistrations.get(cls);
+
+        if (reg != null) return reg;
+
+        for (Map.Entry<Class, SerializerRegistration> entry : classRegistrations.entrySet()) {
+            if (entry.getKey().isAssignableFrom(Serializable.class)) continue;
+            if (entry.getKey().isAssignableFrom(cls)) return entry.getValue();
+        }
+
+        if (cls.isArray()) return registerClass(cls, arraySerializer);
+
+        if (Serializable.class.isAssignableFrom(cls)) { 
+            return getExactSerializerRegistration(java.io.Serializable.class);
+        }
+
+        // See if the class could be safely auto-registered
+        if (cls.isAnnotationPresent(Serializable.class)) {
+            Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class);
+            short classId = serializable.id();
+            if( classId != 0 ) {
+                // No reason to fail because the ID is fixed
+                failOnMiss = false;
+            }
+        }            
+        
+        if( failOnMiss ) {
+            throw new IllegalArgumentException( "Class has not been registered:" + cls );
+        }
+        return registerClass(cls, fieldSerializer);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Read the class from given buffer and return its SerializerRegistration.
+     *
+     * @param buffer The buffer to read from.
+     * @return The SerializerRegistration, or null if non-existent.
+     */
+    public static SerializerRegistration readClass(ByteBuffer buffer) {
+        short classID = buffer.getShort();
+        if (classID == -1) return NULL_CLASS;
+        return idRegistrations.get(classID);
+    }
+
+    /**
+     * Read the class and the object.
+     *
+     * @param buffer Buffer to read from.
+     * @return The Object that was read.
+     * @throws IOException If serialization failed.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object readClassAndObject(ByteBuffer buffer) throws IOException {
+        SerializerRegistration reg = readClass(buffer);
+        if (reg == NULL_CLASS) return null;
+        if (reg == null) throw new SerializerException( "Class not found for buffer data." );
+        return reg.getSerializer().readObject(buffer, reg.getType());
+    }
+
+    /**
+     * Write a class and return its SerializerRegistration.
+     *
+     * @param buffer The buffer to write the given class to.
+     * @param type The class to write.
+     * @return The SerializerRegistration that's registered to the class.
+     */
+    public static SerializerRegistration writeClass(ByteBuffer buffer, Class type) throws IOException {
+        SerializerRegistration reg = getSerializerRegistration(type);
+        if (reg == null) {
+            throw new SerializerException( "Class not registered:" + type );
+        }
+        buffer.putShort(reg.getId());
+        return reg;
+    }
+
+    /**
+     * Write the class and object.
+     *
+     * @param buffer The buffer to write to.
+     * @param object The object to write.
+     * @throws IOException If serializing fails.
+     */
+    public static void writeClassAndObject(ByteBuffer buffer, Object object) throws IOException {
+        if (object == null) {
+            buffer.putShort((short)-1);
+            return;
+        }
+        SerializerRegistration reg = writeClass(buffer, object.getClass());
+        reg.getSerializer().writeObject(buffer, object);
+    }
+
+    /**
+     * Read an object from the buffer, effectively deserializing it.
+     *
+     * @param data The buffer to read from.
+     * @param c The class of the object.
+     * @return The object read.
+     * @throws IOException If deserializing fails.
+     */
+    public abstract <T> T readObject(ByteBuffer data, Class<T> c) throws IOException;
+
+    /**
+     * Write an object to the buffer, effectively serializing it.
+     *
+     * @param buffer The buffer to write to.
+     * @param object The object to serialize.
+     * @throws IOException If serializing fails.
+     */
+    public abstract void writeObject(ByteBuffer buffer, Object object) throws IOException;
+
+    /**
+     * Registration for when a serializer may need to cache something.
+     *
+     * Override to use.
+     *
+     * @param clazz The class that has been registered to the serializer.
+     */
+    public void initialize(Class clazz) { }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/SerializerException.java b/engine/src/networking/com/jme3/network/serializing/SerializerException.java
new file mode 100644
index 0000000..32e8aab
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/SerializerException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011 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.network.serializing;
+
+import java.io.IOException;
+
+/**
+ *  A general exception from the serialization routines.
+ *
+ *  @version   $Revision: 7118 $
+ *  @author    Paul Speed
+ */
+public class SerializerException extends IOException
+{
+    public SerializerException( String msg, Throwable cause )
+    {
+        super( msg );
+        initCause(cause);
+    }
+
+    public SerializerException( String msg )
+    {
+        super( msg );
+    }      
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/SerializerRegistration.java b/engine/src/networking/com/jme3/network/serializing/SerializerRegistration.java
new file mode 100644
index 0000000..00b365e
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/SerializerRegistration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing;
+
+/**
+ * A SerializerRegistration represents a connection between a class, and
+ *  its serializer. It also includes the class ID, as a short.
+ *
+ * @author Lars Wesselius
+ */
+public final class SerializerRegistration {
+    private Serializer serializer;
+    private short id;
+    private Class type;
+
+    public SerializerRegistration(Serializer serializer, Class cls, short id) {
+        this.serializer = serializer;
+        type = cls;
+        this.id = id;
+    }
+
+    /**
+     * Get the serializer.
+     *
+     * @return The serializer.
+     */
+    public Serializer getSerializer() {
+        return serializer;
+    }
+
+    /**
+     * Get the ID.
+     *
+     * @return The ID.
+     */
+    public short getId() {
+        return id;
+    }
+
+    /**
+     * Get the class type.
+     *
+     * @return The class type.
+     */
+    public Class getType() {
+        return type;
+    }
+    
+    public String toString() {
+        return "SerializerRegistration[" + id + ", " + type + ", " + serializer + "]";
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/ArraySerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/ArraySerializer.java
new file mode 100644
index 0000000..71c43d4
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/ArraySerializer.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine, Java Game Networking
+ * 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+
+/**
+ * Array serializer
+ *
+ * @author Nathan Sweet
+ */
+@SuppressWarnings("unchecked")
+public class ArraySerializer extends Serializer {
+    private int[] getDimensions (Object array) {
+        int depth = 0;
+        Class nextClass = array.getClass().getComponentType();
+        while (nextClass != null) {
+            depth++;
+            nextClass = nextClass.getComponentType();
+        }
+        int[] dimensions = new int[depth];
+        dimensions[0] = Array.getLength(array);
+        if (depth > 1) collectDimensions(array, 1, dimensions);
+        return dimensions;
+    }
+
+    private void collectDimensions (Object array, int dimension, int[] dimensions) {
+        boolean elementsAreArrays = dimension < dimensions.length - 1;
+        for (int i = 0, s = Array.getLength(array); i < s; i++) {
+            Object element = Array.get(array, i);
+            if (element == null) continue;
+            dimensions[dimension] = Math.max(dimensions[dimension], Array.getLength(element));
+            if (elementsAreArrays) collectDimensions(element, dimension + 1, dimensions);
+        }
+    }
+
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        byte dimensionCount = data.get();
+        if (dimensionCount == 0)
+            return null;
+
+        int[] dimensions = new int[dimensionCount];
+        for (int i = 0; i < dimensionCount; i++)
+                dimensions[i] = data.getInt();
+
+        Serializer elementSerializer = null;
+
+        Class elementClass = c;
+        while (elementClass.getComponentType() != null)
+            elementClass = elementClass.getComponentType();
+
+        if (Modifier.isFinal(elementClass.getModifiers())) elementSerializer = Serializer.getSerializer(elementClass);
+        // Create array and read in the data.
+        T array = (T)Array.newInstance(elementClass, dimensions);
+        readArray(elementSerializer, elementClass, data, array, 0, dimensions);
+        return array;
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        if (object == null){
+            buffer.put((byte)0);
+            return;
+        }
+
+        int[] dimensions = getDimensions(object);
+        buffer.put((byte)dimensions.length);
+        for (int dimension : dimensions) buffer.putInt(dimension);
+        Serializer elementSerializer = null;
+
+        Class elementClass = object.getClass();
+        while (elementClass.getComponentType() != null) {
+            elementClass = elementClass.getComponentType();
+        }
+
+        if (Modifier.isFinal(elementClass.getModifiers())) elementSerializer = Serializer.getSerializer(elementClass);
+        writeArray(elementSerializer, buffer, object, 0, dimensions.length);
+    }
+
+    private void writeArray(Serializer elementSerializer, ByteBuffer buffer, Object array, int dimension, int dimensionCount) throws IOException {
+        int length = Array.getLength(array);
+        if (dimension > 0) {
+            buffer.putInt(length);
+        }
+        // Write array data.
+        boolean elementsAreArrays = dimension < dimensionCount - 1;
+        for (int i = 0; i < length; i++) {
+            Object element = Array.get(array, i);
+            if (elementsAreArrays) {
+                if (element != null) writeArray(elementSerializer, buffer, element, dimension + 1, dimensionCount);
+            } else if (elementSerializer != null) {
+                elementSerializer.writeObject(buffer, element);
+            } else {
+                // Each element could be a different type. Store the class with the object.
+                Serializer.writeClassAndObject(buffer, element);
+            }
+        }
+    }
+
+    private void readArray (Serializer elementSerializer, Class elementClass, ByteBuffer buffer, Object array, int dimension, int[] dimensions) throws IOException {
+        boolean elementsAreArrays = dimension < dimensions.length - 1;
+        int length;
+        if (dimension == 0) {
+            length = dimensions[0];
+        } else {
+            length = buffer.getInt();
+        }
+        for (int i = 0; i < length; i++) {
+            if (elementsAreArrays) {
+                // Nested array.
+                Object element = Array.get(array, i);
+                if (element != null) readArray(elementSerializer, elementClass, buffer, element, dimension + 1, dimensions);
+            } else if (elementSerializer != null) {
+                // Use same converter (and class) for all elements.
+                Array.set(array, i, elementSerializer.readObject(buffer, elementClass));
+            } else {
+                // Each element could be a different type. Look up the class with the object.
+                Array.set(array, i, Serializer.readClassAndObject(buffer));
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/BooleanSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/BooleanSerializer.java
new file mode 100644
index 0000000..52f5fea
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/BooleanSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Boolean serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class BooleanSerializer extends Serializer {
+
+    public Boolean readObject(ByteBuffer data, Class c) throws IOException {
+        return data.get() == 1;
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.put(((Boolean)object) ? (byte)1 : (byte)0);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/ByteSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/ByteSerializer.java
new file mode 100644
index 0000000..39b9a74
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/ByteSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Byte serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class ByteSerializer extends Serializer {
+
+    public Byte readObject(ByteBuffer data, Class c) throws IOException {
+        return data.get();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.put((Byte)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/CharSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/CharSerializer.java
new file mode 100644
index 0000000..5dfb852
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/CharSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Char serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class CharSerializer extends Serializer {
+
+    public Character readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getChar();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putChar((Character)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/CollectionSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/CollectionSerializer.java
new file mode 100644
index 0000000..687b84d
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/CollectionSerializer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerRegistration;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.logging.Level;
+
+/**
+ * Serializes collections.
+ *
+ * @author Lars Wesselius
+ */
+public class CollectionSerializer extends Serializer {
+
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        int length = data.getInt();
+
+        Collection collection;
+        try {
+            collection = (Collection)c.newInstance();
+        } catch (Exception e) {
+            log.log(Level.FINE, "[Serializer][???] Could not determine collection type. Using ArrayList.");
+            collection = new ArrayList(length);
+        }
+
+        if (length == 0) return (T)collection;
+
+        if (data.get() == (byte)1) {
+            SerializerRegistration reg = Serializer.readClass(data);
+            Class clazz = reg.getType();
+            Serializer serializer = reg.getSerializer();
+
+            for (int i = 0; i != length; ++i) {
+                collection.add(serializer.readObject(data, clazz));
+            }
+        } else {
+            for (int i = 0; i != length; ++i) {
+                collection.add(Serializer.readClassAndObject(data));
+            }
+        }
+        return (T)collection;
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        Collection collection = (Collection)object;
+        int length = collection.size();
+
+        buffer.putInt(length);
+        if (length == 0) return;
+
+        Iterator it = collection.iterator();
+        Class elementClass = it.next().getClass();
+        while (it.hasNext()) {
+            Object obj = it.next();
+
+            if (obj.getClass() != elementClass) {
+                elementClass = null;
+                break;
+            }
+        }
+
+        if (elementClass != null) {
+            buffer.put((byte)1);
+            Serializer.writeClass(buffer, elementClass);
+            Serializer serializer = Serializer.getSerializer(elementClass);
+
+            for (Object elem : collection) {
+                serializer.writeObject(buffer, elem);
+            }
+        } else {
+            buffer.put((byte)0);
+            for (Object elem : collection) {
+                Serializer.writeClassAndObject(buffer, elem);
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/DateSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/DateSerializer.java
new file mode 100644
index 0000000..957796c
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/DateSerializer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+/**
+ * Date serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class DateSerializer extends Serializer {
+
+    public Date readObject(ByteBuffer data, Class c) throws IOException {
+        return new Date(data.getLong());
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putLong(((Date)object).getTime());
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/DoubleSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/DoubleSerializer.java
new file mode 100644
index 0000000..7608eba
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/DoubleSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Double serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class DoubleSerializer extends Serializer {
+
+    public Double readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getDouble();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putDouble((Double)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/EnumSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/EnumSerializer.java
new file mode 100644
index 0000000..f4e5ed3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/EnumSerializer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Enum serializer.
+ *
+ * @author Lars Wesselius
+ */
+public class EnumSerializer extends Serializer {
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        try {
+            int ordinal = data.getInt();
+
+            if (ordinal == -1) return null;
+            T[] enumConstants = c.getEnumConstants();
+            if (enumConstants == null)
+                throw new SerializerException( "Class has no enum constants:" + c );
+            return enumConstants[ordinal];
+        } catch (IndexOutOfBoundsException ex) {
+            return null;
+        }
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        if (object == null) {
+            buffer.putInt(-1);
+        } else {
+            buffer.putInt(((Enum)object).ordinal());
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/FieldSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/FieldSerializer.java
new file mode 100644
index 0000000..3f13c50
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/FieldSerializer.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine, Java Game Networking
+ * 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerException;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.logging.Level;
+
+/**
+ * The field serializer is the default serializer used for custom class.
+ *
+ * @author Lars Wesselius, Nathan Sweet
+ */
+public class FieldSerializer extends Serializer {
+    private static Map<Class, SavedField[]> savedFields = new HashMap<Class, SavedField[]>();
+
+    protected void checkClass(Class clazz) {
+    
+        // See if the class has a public no-arg constructor
+        try {
+            clazz.getConstructor();
+        } catch( NoSuchMethodException e ) {
+            throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz ); 
+        } 
+    }        
+    
+    public void initialize(Class clazz) {
+
+        checkClass(clazz);   
+    
+        List<Field> fields = new ArrayList<Field>();
+
+        Class processingClass = clazz;
+        while (processingClass != Object.class ) {
+            Collections.addAll(fields, processingClass.getDeclaredFields());
+            processingClass = processingClass.getSuperclass();
+        }
+
+        List<SavedField> cachedFields = new ArrayList<SavedField>(fields.size());
+        for (Field field : fields) {
+            int modifiers = field.getModifiers();
+            if (Modifier.isTransient(modifiers)) continue;
+            if (Modifier.isFinal(modifiers)) continue;
+            if (Modifier.isStatic(modifiers)) continue;
+            if (field.isSynthetic()) continue;
+            field.setAccessible(true);
+
+            SavedField cachedField = new SavedField();
+            cachedField.field = field;
+
+            if (Modifier.isFinal(field.getType().getModifiers())) {
+                // The type of this field is implicit in the outer class
+                // definition and because the type is final, it can confidently
+                // be determined on the other end.
+                // Note: passing false to this method has the side-effect that field.getType()
+                // will be registered as a real class that can then be read/written
+                // directly as any other registered class.  It should be safe to take
+                // an ID like this because Serializer.initialize() is only called 
+                // during registration... so this is like nested registration and
+                // doesn't have any ordering problems.
+                // ...well, as long as the order of fields is consistent from one
+                // end to the next. 
+                cachedField.serializer = Serializer.getSerializer(field.getType(), false);
+            }                
+
+            cachedFields.add(cachedField);
+        }
+
+        Collections.sort(cachedFields, new Comparator<SavedField>() {
+            public int compare (SavedField o1, SavedField o2) {
+                    return o1.field.getName().compareTo(o2.field.getName());
+            }
+        });
+        savedFields.put(clazz, cachedFields.toArray(new SavedField[cachedFields.size()]));
+
+        
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+    
+        // Read the null/non-null marker
+        if (data.get() == 0x0)
+            return null;
+    
+        SavedField[] fields = savedFields.get(c);
+
+        T object;
+        try {
+            object = c.newInstance();
+        } catch (Exception e) {
+            throw new SerializerException( "Error creating object of type:" + c, e );
+        }
+
+        for (SavedField savedField : fields) {
+            Field field = savedField.field;
+            Serializer serializer = savedField.serializer;
+            Object value;
+
+            if (serializer != null) {
+                value = serializer.readObject(data, field.getType());
+            } else {
+                value = Serializer.readClassAndObject(data);
+            }
+            try {
+                field.set(object, value);
+            } catch (IllegalAccessException e) {
+                throw new SerializerException( "Error reading object", e);
+            }
+        }
+        return object;
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+    
+        // Add the null/non-null marker
+        buffer.put( (byte)(object != null ? 0x1 : 0x0) );
+        if (object == null) {
+            // Nothing left to do
+            return;
+        }
+        
+        SavedField[] fields = savedFields.get(object.getClass());
+        if (fields == null)
+            throw new IOException("The " + object.getClass() + " is not registered"
+                                + " in the serializer!");
+
+        for (SavedField savedField : fields) {
+            Object val = null;
+            try {
+                val = savedField.field.get(object);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+            Serializer serializer = savedField.serializer;
+
+            try {
+                if (serializer != null) {
+                    serializer.writeObject(buffer, val);
+                } else {
+                    Serializer.writeClassAndObject(buffer, val);
+                }
+            } catch (BufferOverflowException boe) {
+                throw boe;
+            } catch (Exception e) {
+                throw new SerializerException( "Error writing object for field:" + savedField.field, e );
+            }
+        }
+    }
+
+    private final class SavedField {
+        public Field field;
+        public Serializer serializer;
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/FloatSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/FloatSerializer.java
new file mode 100644
index 0000000..404e165
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/FloatSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Float serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class FloatSerializer extends Serializer {
+
+    public Float readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getFloat();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putFloat((Float)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/GZIPSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/GZIPSerializer.java
new file mode 100644
index 0000000..b681511
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/GZIPSerializer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.Message;
+import com.jme3.network.message.GZIPCompressedMessage;
+import com.jme3.network.serializing.Serializer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Serializes GZIP messages.
+ *
+ * @author Lars Wesselius
+ */
+public class GZIPSerializer extends Serializer {
+
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        try
+        {
+            GZIPCompressedMessage result = new GZIPCompressedMessage();
+
+            byte[] byteArray = new byte[data.remaining()];
+
+            data.get(byteArray);
+
+            GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(byteArray));
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            byte[] tmp = new byte[9012];
+            int read;
+
+            while (in.available() > 0 && ((read = in.read(tmp)) > 0)) {
+                out.write(tmp, 0, read);
+            }
+
+            result.setMessage((Message)Serializer.readClassAndObject(ByteBuffer.wrap(out.toByteArray())));
+            return (T)result;
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e.toString());
+        }
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        if (!(object instanceof GZIPCompressedMessage)) return;
+        Message message = ((GZIPCompressedMessage)object).getMessage();
+
+        ByteBuffer tempBuffer = ByteBuffer.allocate(512000);
+        Serializer.writeClassAndObject(tempBuffer, message);
+
+        ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
+        GZIPOutputStream gzipOutput = new GZIPOutputStream(byteArrayOutput);
+
+        gzipOutput.write(tempBuffer.array());
+        gzipOutput.flush();
+        gzipOutput.finish();
+        gzipOutput.close();
+
+        buffer.put(byteArrayOutput.toByteArray());
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/IntSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/IntSerializer.java
new file mode 100644
index 0000000..52a2ed3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/IntSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * The Integer serializer serializes...integers. Big surprise.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class IntSerializer extends Serializer {
+
+    public Integer readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getInt();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putInt((Integer)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/LongSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/LongSerializer.java
new file mode 100644
index 0000000..287c1de
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/LongSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * The Long serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class LongSerializer extends Serializer {
+
+    public Long readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getLong();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putLong((Long)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/MapSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/MapSerializer.java
new file mode 100644
index 0000000..ba5eef3
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/MapSerializer.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerRegistration;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+
+public class MapSerializer extends Serializer {
+
+    /*
+
+    Structure:
+
+    struct Map {
+        INT length
+        BYTE flags = { 0x01 = all keys have the same type,
+                       0x02 = all values have the same type }
+        if (flags has 0x01 set)
+            SHORT keyType
+        if (flags has 0x02 set)
+            SHORT valType
+
+        struct MapEntry[length] entries {
+            if (flags does not have 0x01 set)
+                SHORT keyType
+            OBJECT key
+
+            if (flags does not have 0x02 set)
+                SHORT valType
+            OBJECT value
+        }
+    }
+
+     */
+
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        int length = data.getInt();
+
+        Map map;
+        try {
+            map = (Map)c.newInstance();
+        } catch (Exception e) {
+            log.log(Level.WARNING, "[Serializer][???] Could not determine map type. Using HashMap.");
+            map = new HashMap();
+        }
+
+        if (length == 0) return (T)map;
+
+        int flags = data.get() & 0xff;
+        boolean uniqueKeys = (flags & 0x01) == 0;
+        boolean uniqueVals = (flags & 0x02) == 0;
+
+        Class keyClazz = null;
+        Class valClazz = null;
+        Serializer keySerial = null;
+        Serializer valSerial = null;
+        if (!uniqueKeys){
+            SerializerRegistration reg = Serializer.readClass(data);
+            keyClazz = reg.getType();
+            keySerial = reg.getSerializer();
+        }
+        if (!uniqueVals){
+            SerializerRegistration reg = Serializer.readClass(data);
+            valClazz = reg.getType();
+            valSerial = reg.getSerializer();
+        }
+
+        for (int i = 0; i < length; i++){
+            Object key;
+            Object value;
+            if (uniqueKeys){
+                key = Serializer.readClassAndObject(data);
+            }else{
+                key = keySerial.readObject(data, keyClazz);
+            }
+            if (uniqueVals){
+                value = Serializer.readClassAndObject(data);
+            }else{
+                value = valSerial.readObject(data, valClazz);
+            }
+
+            map.put(key, value);
+        }
+
+        return (T)map;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        Map map = (Map)object;
+        int length = map.size();
+
+        buffer.putInt(length);
+        if (length == 0) return;
+
+
+        Set<Entry> entries = map.entrySet();
+
+        Iterator<Entry> it = entries.iterator();
+
+        Entry entry = it.next();
+        Class keyClass = entry.getKey().getClass();
+        Class valClass = entry.getValue().getClass();
+        while (it.hasNext()) {
+            entry = it.next();
+
+            if (entry.getKey().getClass() != keyClass){
+                keyClass = null;
+                if (valClass == null)
+                    break;
+            }
+            if (entry.getValue().getClass() != valClass){
+                valClass = null;
+                if (keyClass == null)
+                    break;
+            }
+        }
+
+        boolean uniqueKeys = keyClass == null;
+        boolean uniqueVals = valClass == null;
+        int flags = 0;
+        if (!uniqueKeys) flags |= 0x01;
+        if (!uniqueVals) flags |= 0x02;
+        buffer.put( (byte) flags );
+
+        Serializer keySerial = null, valSerial = null;
+        if (!uniqueKeys){
+            Serializer.writeClass(buffer, keyClass);
+            keySerial = Serializer.getSerializer(keyClass);
+        }
+        if (!uniqueVals){
+            Serializer.writeClass(buffer, valClass);
+            valSerial = Serializer.getSerializer(valClass);
+        }
+
+        it = entries.iterator();
+        while (it.hasNext()) {
+            entry = it.next();
+            if (uniqueKeys){
+                Serializer.writeClassAndObject(buffer, entry.getKey());
+            }else{
+                keySerial.writeObject(buffer, entry.getKey());
+            }
+            if (uniqueVals){
+                Serializer.writeClassAndObject(buffer, entry.getValue());
+            }else{
+                valSerial.writeObject(buffer, entry.getValue());
+            }
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/SavableSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/SavableSerializer.java
new file mode 100644
index 0000000..09e991d
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/SavableSerializer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.export.Savable;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+public class SavableSerializer extends Serializer {
+
+    private BinaryExporter exporter = new BinaryExporter();
+    private BinaryImporter importer = new BinaryImporter();
+
+    private static class BufferOutputStream extends OutputStream {
+
+        ByteBuffer output;
+
+        public BufferOutputStream(ByteBuffer output){
+            this.output = output;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            output.put( (byte) b );
+        }
+
+        @Override
+        public void write(byte[] b){
+            output.put(b);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len){
+            output.put(b, off, len);
+        }
+    }
+
+    private static class BufferInputStream extends InputStream {
+
+        ByteBuffer input;
+
+        public BufferInputStream(ByteBuffer input){
+            this.input = input;
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (input.remaining() == 0)
+                return -1;
+            else
+                return input.get() & 0xff;
+        }
+
+        @Override
+        public int read(byte[] b){
+            return read(b, 0, b.length);
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len){
+            int toRead = len > input.remaining() ? input.remaining() : len;
+            input.get(b, off, len);
+            return toRead;
+        }
+
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        BufferInputStream in = new BufferInputStream(data);
+        Savable s = importer.load(in);
+        in.close();
+        return (T) s;
+    }
+
+    @Override
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        Savable s = (Savable) object;
+        BufferOutputStream out = new BufferOutputStream(buffer);
+        exporter.save(s, out);
+        out.close();
+    }
+
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/SerializableSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/SerializableSerializer.java
new file mode 100644
index 0000000..dba4db6
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/SerializableSerializer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+/**
+ * Serializes uses Java built-in method.
+ *
+ * TODO
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class SerializableSerializer extends Serializer {
+
+    public Serializable readObject(ByteBuffer data, Class c) throws IOException {
+        throw new UnsupportedOperationException( "Serializable serialization not supported." );
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        throw new UnsupportedOperationException( "Serializable serialization not supported." );
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/ShortSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/ShortSerializer.java
new file mode 100644
index 0000000..2c3fe29
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/ShortSerializer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Short serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class ShortSerializer extends Serializer {
+    public Short readObject(ByteBuffer data, Class c) throws IOException {
+        return data.getShort();
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        buffer.putShort((Short)object);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/StringSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/StringSerializer.java
new file mode 100644
index 0000000..ab18f85
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/StringSerializer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * String serializer.
+ *
+ * @author Lars Wesselius
+ */
+@SuppressWarnings("unchecked")
+public class StringSerializer extends Serializer {
+
+    public String readObject(ByteBuffer data, Class c) throws IOException {
+
+        int length = -1;
+        byte type = data.get();
+        if (type == (byte)0) {
+            return null;
+        } else if (type == (byte)1) {
+            // Byte
+            length = data.get();
+        } else if (type == (byte)2) {
+            // Short
+            length = data.getShort();
+        } else if (type == (byte)3) {
+            // Int
+            length = data.getInt();
+        }
+        if (length == -1) throw new IOException("Could not read String: Invalid length identifier.");
+
+        byte[] buffer = new byte[length];
+        data.get(buffer);
+        return new String(buffer, "UTF-8");
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        String string = (String)object;
+
+        if (string == null) {
+            // Write that it's 0.
+            buffer.put((byte)0);
+            return;
+        }
+        byte[] stringBytes = string.getBytes("UTF-8");
+        int bufferLength = stringBytes.length;
+
+        try {
+            if (bufferLength <= Byte.MAX_VALUE) {
+                buffer.put((byte)1);
+                buffer.put((byte)bufferLength);
+            } else if (bufferLength <= Short.MAX_VALUE) {
+                buffer.put((byte)2);
+                buffer.putShort((short)bufferLength);
+            } else {
+                buffer.put((byte)3);
+                buffer.putInt(bufferLength);
+            }
+            buffer.put(stringBytes);
+        }
+        catch (BufferOverflowException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/Vector3Serializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/Vector3Serializer.java
new file mode 100644
index 0000000..f09066a
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/Vector3Serializer.java
@@ -0,0 +1,28 @@
+package com.jme3.network.serializing.serializers;
+
+import com.jme3.math.Vector3f;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * @author Kirill Vainer
+ */
+@SuppressWarnings("unchecked")
+public class Vector3Serializer extends Serializer {
+
+    public Vector3f readObject(ByteBuffer data, Class c) throws IOException {
+        Vector3f vec3 = new Vector3f();
+        vec3.x = data.getFloat();
+        vec3.y = data.getFloat();
+        vec3.z = data.getFloat();
+        return vec3;
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        Vector3f vec3 = (Vector3f) object;
+        buffer.putFloat(vec3.x);
+        buffer.putFloat(vec3.y);
+        buffer.putFloat(vec3.z);
+    }
+}
diff --git a/engine/src/networking/com/jme3/network/serializing/serializers/ZIPSerializer.java b/engine/src/networking/com/jme3/network/serializing/serializers/ZIPSerializer.java
new file mode 100644
index 0000000..fafb2fa
--- /dev/null
+++ b/engine/src/networking/com/jme3/network/serializing/serializers/ZIPSerializer.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.network.serializing.serializers;
+
+import com.jme3.network.Message;
+import com.jme3.network.message.ZIPCompressedMessage;
+import com.jme3.network.serializing.Serializer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Serializes ZIP messages.
+ *
+ * @author Lars Wesselius
+ */
+public class ZIPSerializer extends Serializer {
+
+    @SuppressWarnings("unchecked")
+    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
+        try
+        {
+            ZIPCompressedMessage result = new ZIPCompressedMessage();
+
+            byte[] byteArray = new byte[data.remaining()];
+
+            data.get(byteArray);
+
+            ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(byteArray));
+            in.getNextEntry();
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            byte[] tmp = new byte[9012];
+            int read;
+
+            while (in.available() > 0 && ((read = in.read(tmp)) > 0)) {
+                out.write(tmp, 0, read);
+            }
+
+            in.closeEntry();
+            out.flush();
+            in.close();
+
+            result.setMessage((Message)Serializer.readClassAndObject(ByteBuffer.wrap(out.toByteArray())));
+            return (T)result;
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e.toString());
+        }
+    }
+
+    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
+        if (!(object instanceof ZIPCompressedMessage)) return;
+
+        ZIPCompressedMessage zipMessage = (ZIPCompressedMessage)object;
+        Message message = zipMessage.getMessage();
+        ByteBuffer tempBuffer = ByteBuffer.allocate(512000);
+        Serializer.writeClassAndObject(tempBuffer, message);
+
+        ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
+        ZipOutputStream zipOutput = new ZipOutputStream(byteArrayOutput);
+        zipOutput.setLevel(zipMessage.getLevel());
+
+        ZipEntry zipEntry = new ZipEntry("zip");
+
+        zipOutput.putNextEntry(zipEntry);
+        zipOutput.write(tempBuffer.array());
+        zipOutput.flush();
+        zipOutput.closeEntry();
+        zipOutput.close();
+
+        buffer.put(byteArrayOutput.toByteArray());
+    }
+}
diff --git a/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.frag b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.frag
new file mode 100644
index 0000000..5e77548
--- /dev/null
+++ b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.frag
@@ -0,0 +1,13 @@
+uniform bool m_UseTex;

+uniform sampler2D m_Texture;

+uniform vec4 m_Color;

+

+varying vec2 texCoord;

+varying vec4 color;

+

+void main() {

+    vec4 texVal = texture2D(m_Texture, texCoord);

+    texVal = m_UseTex ? texVal : vec4(1.0);

+    gl_FragColor = texVal * color * m_Color;

+}

+

diff --git a/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.j3md b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.j3md
new file mode 100644
index 0000000..9ba39b1
--- /dev/null
+++ b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.j3md
@@ -0,0 +1,21 @@
+MaterialDef Default GUI {

+

+    MaterialParameters {

+        Texture2D Texture

+        Boolean UseTex

+        Vector4 Color (Color)

+    }

+

+    Technique {

+        VertexShader GLSL100:   Common/MatDefs/Nifty/Nifty.vert

+        FragmentShader GLSL100: Common/MatDefs/Nifty/Nifty.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.vert b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.vert
new file mode 100644
index 0000000..67c864d
--- /dev/null
+++ b/engine/src/niftygui/Common/MatDefs/Nifty/Nifty.vert
@@ -0,0 +1,16 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec4 inPosition;

+attribute vec4 inColor;

+attribute vec2 inTexCoord;

+

+varying vec2 texCoord;

+varying vec4 color;

+

+void main() {

+    vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy;

+    gl_Position = vec4(pos, 0.0, 1.0);

+

+    texCoord = inTexCoord;

+    color = inColor;

+}
\ No newline at end of file
diff --git a/engine/src/niftygui/com/jme3/cinematic/events/GuiTrack.java b/engine/src/niftygui/com/jme3/cinematic/events/GuiTrack.java
new file mode 100644
index 0000000..281c450
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/cinematic/events/GuiTrack.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009-2010 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.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import de.lessvoid.nifty.Nifty;
+import java.io.IOException;
+
+/**
+ *
+ * @author Nehon
+ */
+public class GuiTrack extends AbstractCinematicEvent {
+
+    protected String screen;
+    protected Nifty nifty;
+
+    public GuiTrack() {
+    }
+
+    public GuiTrack(Nifty nifty, String screen) {
+        this.screen = screen;
+        this.nifty = nifty;
+    }
+
+    public GuiTrack(Nifty nifty, String screen, float initialDuration) {
+        super(initialDuration);
+        this.screen = screen;
+        this.nifty = nifty;
+    }
+
+    public GuiTrack(Nifty nifty, String screen, LoopMode loopMode) {
+        super(loopMode);
+        this.screen = screen;
+        this.nifty = nifty;
+    }
+
+    public GuiTrack(Nifty nifty, String screen, float initialDuration, LoopMode loopMode) {
+        super(initialDuration, loopMode);
+        this.screen = screen;
+        this.nifty = nifty;
+    }
+
+    @Override
+    public void onPlay() {
+        System.out.println("screen should be "+screen);
+        nifty.gotoScreen(screen);
+    }
+
+    @Override
+    public void onStop() {
+        nifty.gotoScreen("");
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    public void setNifty(Nifty nifty) {
+        this.nifty = nifty;
+    }
+
+    public void setScreen(String screen) {
+        this.screen = screen;
+    }
+
+    @Override
+    public void onUpdate(float tpf) {
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(screen, "screen", "");
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        screen = ic.readString("screen", "");
+    }
+}
diff --git a/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java
new file mode 100644
index 0000000..0b80131
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.*;
+import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.NiftyInputConsumer;
+import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader;
+import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent;
+import de.lessvoid.nifty.spi.input.InputSystem;
+import java.util.ArrayList;
+
+public class InputSystemJme implements InputSystem, RawInputListener {
+
+    private final ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();
+
+    private InputManager inputManager;
+
+    private boolean isDragging = false, niftyOwnsDragging = false;
+    private boolean pressed = false;
+    private int buttonIndex;
+    private int x, y;
+    private int height;
+
+    private boolean shiftDown = false;
+    private boolean ctrlDown  = false;
+
+    private Nifty nifty;
+
+    public InputSystemJme(InputManager inputManager){
+        this.inputManager = inputManager;
+    }
+
+    public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) {
+    }
+
+    public void setNifty(Nifty nifty) {
+        this.nifty = nifty;
+    }
+
+    /**
+     * @param height The height of the viewport. Used to convert
+     * buttom-left origin to upper-left origin.
+     */
+    public void setHeight(int height){
+        this.height = height;
+    }
+
+    public void setMousePosition(int x, int y){
+    }
+
+    public void beginInput(){
+    }
+
+    public void endInput(){
+        boolean result = nifty.update();
+    }
+
+    private void onTouchEventQueued(TouchEvent evt, NiftyInputConsumer nic) {  
+        boolean consumed = false;
+
+        x = (int) evt.getX();
+        y = (int) (height - evt.getY());
+
+        switch (evt.getType()) {
+           case DOWN:
+               consumed = nic.processMouseEvent(x, y, 0, 0, false);
+               isDragging = true;
+               niftyOwnsDragging = consumed;
+               if (consumed){
+                   evt.setConsumed();
+               }
+
+               break;
+
+           case UP:
+               if (niftyOwnsDragging){
+                   consumed = nic.processMouseEvent(x, y, 0, buttonIndex, pressed);
+                   if (consumed){
+                       evt.setConsumed();
+                   }
+               }
+
+               isDragging = false;
+               niftyOwnsDragging = false;
+               break;
+       }
+    }
+    
+    private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer nic) {
+        x = evt.getX();
+        y = height - evt.getY();
+        nic.processMouseEvent(x, y, evt.getDeltaWheel(), buttonIndex, pressed);
+//        if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){
+            // Do not consume motion events
+            //evt.setConsumed();
+//        }
+    }
+
+    private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) {
+        boolean wasPressed = pressed;
+        boolean forwardToNifty = true;
+        
+        buttonIndex = evt.getButtonIndex();
+        pressed = evt.isPressed();
+        
+        // Mouse button raised. End dragging
+        if (wasPressed && !pressed){
+            if (!niftyOwnsDragging){
+                forwardToNifty = false;
+            }
+            isDragging = false;
+            niftyOwnsDragging = false;
+        }
+
+        boolean consumed = false;
+        if (forwardToNifty){
+            consumed = nic.processMouseEvent(x, y, 0, buttonIndex, pressed);
+            if (consumed){
+                evt.setConsumed();
+            }
+        }
+        
+        // Mouse button pressed. Begin dragging
+        if (!wasPressed && pressed){
+            isDragging = true;
+            niftyOwnsDragging = consumed;
+        }
+    }
+
+    private void onKeyEventQueued(KeyInputEvent evt, NiftyInputConsumer nic) {
+        int code = evt.getKeyCode();
+
+        if (code == KeyInput.KEY_LSHIFT || code == KeyInput.KEY_RSHIFT) {
+            shiftDown = evt.isPressed();
+        } else if (code == KeyInput.KEY_LCONTROL || code == KeyInput.KEY_RCONTROL) {
+            ctrlDown = evt.isPressed();
+        }
+        
+        KeyboardInputEvent keyEvt = new KeyboardInputEvent(code,
+                                                           evt.getKeyChar(),
+                                                           evt.isPressed(),
+                                                           shiftDown,
+                                                           ctrlDown);
+
+        if (nic.processKeyboardEvent(keyEvt)){
+            evt.setConsumed();
+        }
+    }
+    
+    public void onMouseMotionEvent(MouseMotionEvent evt) {
+        // Only forward the event if there's actual motion involved.
+        if (inputManager.isCursorVisible() && (evt.getDX() != 0 ||
+                                               evt.getDY() != 0 ||
+                                               evt.getDeltaWheel() != 0)){
+            inputQueue.add(evt);
+        }
+    }
+
+    public void onMouseButtonEvent(MouseButtonEvent evt) {
+        if (inputManager.isCursorVisible() && evt.getButtonIndex() >= 0 && evt.getButtonIndex() <= 2){
+            inputQueue.add(evt);
+        }
+    }
+    
+    public void onJoyAxisEvent(JoyAxisEvent evt) {
+    }
+
+    public void onJoyButtonEvent(JoyButtonEvent evt) {
+    }
+    
+    public void onKeyEvent(KeyInputEvent evt) {
+        inputQueue.add(evt);
+    }
+    
+    public void onTouchEvent(TouchEvent evt) {     
+        inputQueue.add(evt);
+    }
+
+    public void forwardEvents(NiftyInputConsumer nic) {
+        int queueSize = inputQueue.size();
+
+        for (int i = 0; i < queueSize; i++){
+            InputEvent evt = inputQueue.get(i);
+            if (evt instanceof MouseMotionEvent){
+                onMouseMotionEventQueued( (MouseMotionEvent)evt, nic);
+            }else if (evt instanceof MouseButtonEvent){
+                onMouseButtonEventQueued( (MouseButtonEvent)evt, nic);
+            }else if (evt instanceof KeyInputEvent){
+                onKeyEventQueued( (KeyInputEvent)evt, nic);
+            }else if (evt instanceof TouchEvent){
+                onTouchEventQueued( (TouchEvent)evt, nic);
+            }
+        }
+
+        inputQueue.clear();
+    }
+    
+    
+}
diff --git a/engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java b/engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java
new file mode 100644
index 0000000..97ddf9d
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/NiftyJmeDisplay.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.input.InputManager;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.tools.TimeProvider;
+import de.lessvoid.nifty.tools.resourceloader.ResourceLocation;
+import java.io.InputStream;
+import java.net.URL;
+
+public class NiftyJmeDisplay implements SceneProcessor {
+
+    protected boolean inited = false;
+    protected Nifty nifty;
+    protected AssetManager assetManager;
+    protected RenderManager renderManager;
+    protected RenderDeviceJme renderDev;
+    protected InputSystemJme inputSys;
+    protected SoundDeviceJme soundDev;
+    protected Renderer renderer;
+    protected ViewPort vp;
+    
+    protected ResourceLocationJme resourceLocation;
+
+    protected int w, h;
+
+    protected class ResourceLocationJme implements ResourceLocation {
+
+        public InputStream getResourceAsStream(String path) {
+            AssetKey<Object> key = new AssetKey<Object>(path);
+            AssetInfo info = assetManager.locateAsset(key);
+            if (info != null){
+                return info.openStream();
+            }else{
+                throw new AssetNotFoundException(path);
+            }
+        }
+
+        public URL getResource(String path) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    //Empty constructor needed for jMP to create replacement input system
+    public NiftyJmeDisplay() {
+    }
+    
+    public NiftyJmeDisplay(AssetManager assetManager, 
+                           InputManager inputManager,
+                           AudioRenderer audioRenderer,
+                           ViewPort vp){
+        this.assetManager = assetManager;
+
+        w = vp.getCamera().getWidth();
+        h = vp.getCamera().getHeight();
+
+        soundDev = new SoundDeviceJme(assetManager, audioRenderer);
+        renderDev = new RenderDeviceJme(this);
+        inputSys = new InputSystemJme(inputManager);
+        if (inputManager != null)
+            inputManager.addRawInputListener(inputSys);
+        
+        nifty = new Nifty(renderDev, soundDev, inputSys, new TimeProvider());
+        inputSys.setNifty(nifty);
+
+        resourceLocation = new ResourceLocationJme();
+        nifty.getResourceLoader().removeAllResourceLocations();
+        nifty.getResourceLoader().addResourceLocation(resourceLocation);
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        this.renderManager = rm;
+        renderDev.setRenderManager(rm);
+        inited = true;
+        this.vp = vp;
+        this.renderer = rm.getRenderer();
+        
+        inputSys.setHeight(vp.getCamera().getHeight());
+    }
+
+    public Nifty getNifty() {
+        return nifty;
+    }
+
+    RenderDeviceJme getRenderDevice() {
+        return renderDev;
+    }
+
+    AssetManager getAssetManager() {
+        return assetManager;
+    }
+
+    RenderManager getRenderManager() {
+        return renderManager;
+    }
+
+    int getHeight() {
+        return h;
+    }
+
+    int getWidth() {
+        return w;
+    }
+
+    Renderer getRenderer(){
+        return renderer;
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+        this.w = w;
+        this.h = h;
+        inputSys.setHeight(h);
+        nifty.resolutionChanged();
+    }
+
+    public boolean isInitialized() {
+        return inited;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+        // render nifty before anything else
+        renderManager.setCamera(vp.getCamera(), true);
+        //nifty.update();
+        nifty.render(false);
+        renderManager.setCamera(vp.getCamera(), false);
+    }
+
+    public void postFrame(FrameBuffer out) {
+    }
+
+    public void cleanup() {
+        inited = false;
+//        nifty.exit();
+    }
+
+}
diff --git a/engine/src/niftygui/com/jme3/niftygui/RenderDeviceJme.java b/engine/src/niftygui/com/jme3/niftygui/RenderDeviceJme.java
new file mode 100644
index 0000000..8977ed6
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/RenderDeviceJme.java
@@ -0,0 +1,393 @@
+/*

+ * Copyright (c) 2009-2010 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.niftygui;

+

+import com.jme3.font.BitmapText;

+import com.jme3.material.Material;

+import com.jme3.material.RenderState;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Matrix4f;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.Renderer;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Format;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.scene.VertexBuffer.Usage;

+import com.jme3.scene.shape.Quad;

+import com.jme3.texture.Texture2D;

+import com.jme3.util.BufferUtils;

+import de.lessvoid.nifty.elements.render.TextRenderer.RenderFontNull;

+import de.lessvoid.nifty.render.BlendMode;

+import de.lessvoid.nifty.spi.render.MouseCursor;

+import de.lessvoid.nifty.spi.render.RenderDevice;

+import de.lessvoid.nifty.spi.render.RenderFont;

+import de.lessvoid.nifty.spi.render.RenderImage;

+import de.lessvoid.nifty.tools.Color;

+import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader;

+import java.nio.ByteBuffer;

+import java.nio.FloatBuffer;

+import java.util.HashMap;

+

+public class RenderDeviceJme implements RenderDevice {

+

+    private NiftyJmeDisplay display;

+    private RenderManager rm;

+    private Renderer r;

+    

+    private HashMap<String, BitmapText> textCacheLastFrame = new HashMap<String, BitmapText>();

+    private HashMap<String, BitmapText> textCacheCurrentFrame = new HashMap<String, BitmapText>();

+

+    private final Quad quad = new Quad(1, -1, true);

+    private final Geometry quadGeom = new Geometry("nifty-quad", quad);

+    private final Material niftyMat;

+

+    private boolean clipWasSet = false;

+    private BlendMode blendMode = null;

+

+    private VertexBuffer quadDefaultTC = quad.getBuffer(Type.TexCoord);

+    private VertexBuffer quadModTC = quadDefaultTC.clone();

+    private VertexBuffer quadColor;

+

+    private Matrix4f tempMat = new Matrix4f();

+    private ColorRGBA tempColor = new ColorRGBA();

+

+    public RenderDeviceJme(NiftyJmeDisplay display){

+        this.display = display;

+

+        quadColor = new VertexBuffer(Type.Color);

+        quadColor.setNormalized(true);

+        ByteBuffer bb = BufferUtils.createByteBuffer(4 * 4);

+        quadColor.setupData(Usage.Stream, 4, Format.UnsignedByte, bb);

+        quad.setBuffer(quadColor);

+

+        quadModTC.setUsage(Usage.Stream);

+

+        niftyMat = new Material(display.getAssetManager(), "Common/MatDefs/Nifty/Nifty.j3md");

+        niftyMat.getAdditionalRenderState().setDepthTest(false);

+    }

+

+    public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) {

+    }

+

+    public void setRenderManager(RenderManager rm){

+        this.rm = rm;

+        this.r = rm.getRenderer();

+    }

+

+    // TODO: Cursor support

+    public MouseCursor createMouseCursor(String str, int x, int y){

+        return new MouseCursor() {

+            public void dispose() {

+            }

+        };

+    }

+

+    public void enableMouseCursor(MouseCursor cursor){

+    }

+

+    public void disableMouseCursor(){

+    }

+

+    public RenderImage createImage(String filename, boolean linear) {

+        return new RenderImageJme(filename, linear, display);

+    }

+

+    public RenderFont createFont(String filename) {

+        return new RenderFontJme(filename, display);

+    }

+

+    public void beginFrame() {

+    }

+

+    public void endFrame() {

+        HashMap<String, BitmapText> temp = textCacheLastFrame;

+        textCacheLastFrame = textCacheCurrentFrame;

+        textCacheCurrentFrame = temp;

+        textCacheCurrentFrame.clear();

+        

+//        System.exit(1);

+    }

+

+    public int getWidth() {

+        return display.getWidth();

+    }

+

+    public int getHeight() {

+        return display.getHeight();

+    }

+

+    public void clear() {

+    }

+

+    public void setBlendMode(BlendMode blendMode) {

+        if (this.blendMode != blendMode){

+            this.blendMode = blendMode;

+        }

+    }

+

+    private RenderState.BlendMode convertBlend(){

+        if (blendMode == null)

+            return RenderState.BlendMode.Off;

+        else if (blendMode == BlendMode.BLEND)

+            return RenderState.BlendMode.Alpha;

+        else if (blendMode == BlendMode.MULIPLY)

+            return RenderState.BlendMode.Modulate;

+        else

+            throw new UnsupportedOperationException();

+    }

+

+    private int convertColor(Color color){

+        int color2 = 0;

+        color2 |= ((int)(255.0 * color.getAlpha())) << 24;

+        color2 |= ((int)(255.0 * color.getBlue())) << 16;

+        color2 |= ((int)(255.0 * color.getGreen())) << 8;

+        color2 |= ((int)(255.0 * color.getRed()));

+        return color2;

+    }

+

+    private ColorRGBA convertColor(Color inColor, ColorRGBA outColor){

+        return outColor.set(inColor.getRed(), inColor.getGreen(), inColor.getBlue(), inColor.getAlpha());

+    }

+

+    private void setColor(Color color){

+        ByteBuffer buf = (ByteBuffer) quadColor.getData();

+        buf.rewind();

+

+        int color2 = convertColor(color);

+        buf.putInt(color2);

+        buf.putInt(color2);

+        buf.putInt(color2);

+        buf.putInt(color2);

+

+        buf.flip();

+        quadColor.updateData(buf);

+    }

+    

+    /**

+     * 

+     * @param font

+     * @param str

+     * @param x

+     * @param y

+     * @param color

+     * @param size 

+     * @deprecated use renderFont(RenderFont font, String str, int x, int y, Color color, float sizeX, float sizeY) instead

+     */

+    @Deprecated

+    public void renderFont(RenderFont font, String str, int x, int y, Color color, float size){        

+        renderFont(font, str, x, y, color, size, size);

+    }

+ 

+    @Override

+    public void renderFont(RenderFont font, String str, int x, int y, Color color, float sizeX, float sizeY){        

+        //TODO find out what the f1 param is for

+        if (str.length() == 0)

+            return;

+

+        if (font instanceof RenderFontNull)

+            return;

+

+        RenderFontJme jmeFont = (RenderFontJme) font;

+        

+        String key = font+str+color.getColorString();

+        BitmapText text = textCacheLastFrame.get(key);

+        if (text == null) {

+            text = jmeFont.createText();

+            text.setText(str);

+            text.updateLogicalState(0);

+        }

+        textCacheCurrentFrame.put(key, text);

+

+        niftyMat.setColor("Color", convertColor(color, tempColor));

+        niftyMat.setBoolean("UseTex", true);

+        niftyMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);

+//        niftyMat.getAdditionalRenderState().setBlendMode(convertBlend());

+        text.setMaterial(niftyMat);

+

+        float width = text.getLineWidth();

+        float height = text.getLineHeight();

+

+        float x0 = x + 0.5f * width  * (1f - sizeX);

+        float y0 = y + 0.5f * height * (1f - sizeY);

+

+        tempMat.loadIdentity();

+        tempMat.setTranslation(x0, getHeight() - y0, 0);

+        tempMat.setScale(sizeX, sizeY, 0);

+

+        rm.setWorldMatrix(tempMat);

+        text.render(rm);

+        

+//        System.out.println("renderFont");

+    }

+

+    public void renderImage(RenderImage image, int x, int y, int w, int h,

+                            int srcX, int srcY, int srcW, int srcH,

+                            Color color, float scale,

+                            int centerX, int centerY){

+        RenderImageJme jmeImage = (RenderImageJme) image;

+        Texture2D texture = jmeImage.getTexture();

+

+        niftyMat.getAdditionalRenderState().setBlendMode(convertBlend());

+        niftyMat.setColor("Color", ColorRGBA.White);

+        niftyMat.setTexture("Texture", texture);

+        niftyMat.setBoolean("UseTex", true);

+        setColor(color);

+

+        float imageWidth  = jmeImage.getWidth();

+        float imageHeight = jmeImage.getHeight();

+        FloatBuffer texCoords = (FloatBuffer) quadModTC.getData();

+

+        float startX = srcX / imageWidth;

+        float startY = srcY / imageHeight;

+        float endX   = startX + (srcW / imageWidth);

+        float endY   = startY + (srcH / imageHeight);

+

+        startY = 1f - startY;

+        endY   = 1f - endY;

+

+        texCoords.rewind();

+        texCoords.put(startX).put(startY);

+        texCoords.put(endX)  .put(startY);

+        texCoords.put(endX)  .put(endY);

+        texCoords.put(startX).put(endY);

+        texCoords.flip();

+        quadModTC.updateData(texCoords);

+

+        quad.clearBuffer(Type.TexCoord);

+        quad.setBuffer(quadModTC);

+

+        float x0 = centerX + (x - centerX) * scale;

+        float y0 = centerY + (y - centerY) * scale;

+

+        tempMat.loadIdentity();

+        tempMat.setTranslation(x0, getHeight() - y0, 0);

+        tempMat.setScale(w * scale, h * scale, 0);

+

+        rm.setWorldMatrix(tempMat);

+        niftyMat.render(quadGeom, rm);

+//        

+//        System.out.println("renderImage (Sub)");

+    }

+

+    public void renderImage(RenderImage image, int x, int y, int width, int height,

+                       Color color, float imageScale){

+

+        RenderImageJme jmeImage = (RenderImageJme) image;

+

+        niftyMat.getAdditionalRenderState().setBlendMode(convertBlend());

+        niftyMat.setColor("Color", ColorRGBA.White);

+        niftyMat.setTexture("Texture", jmeImage.getTexture());

+        niftyMat.setBoolean("UseTex", true);

+        setColor(color);

+

+        quad.clearBuffer(Type.TexCoord);

+        quad.setBuffer(quadDefaultTC);

+

+        float x0 = x + 0.5f * width  * (1f - imageScale);

+        float y0 = y + 0.5f * height * (1f - imageScale);

+

+        tempMat.loadIdentity();

+        tempMat.setTranslation(x0, getHeight() - y0, 0);

+        tempMat.setScale(width * imageScale, height * imageScale, 0);

+

+        rm.setWorldMatrix(tempMat);

+        niftyMat.render(quadGeom, rm);

+//        

+//        System.out.println("renderImage");

+    }

+

+    public void renderQuad(int x, int y, int width, int height, Color color){

+        niftyMat.getAdditionalRenderState().setBlendMode(convertBlend());

+        niftyMat.setColor("Color", ColorRGBA.White);

+        niftyMat.clearParam("Texture");

+        niftyMat.setBoolean("UseTex", false);

+        setColor(color);

+

+        tempMat.loadIdentity();

+        tempMat.setTranslation(x, getHeight() - y, 0);

+        tempMat.setScale(width, height, 0);

+

+        rm.setWorldMatrix(tempMat);

+        niftyMat.render(quadGeom, rm);

+        

+//        System.out.println("renderQuad (Solid)");

+    }

+

+    public void renderQuad(int x, int y, int width, int height,

+                           Color topLeft, Color topRight, Color bottomRight, Color bottomLeft) {

+        

+        ByteBuffer buf = (ByteBuffer) quadColor.getData();

+        buf.rewind();

+        

+        buf.putInt(convertColor(topRight));

+        buf.putInt(convertColor(topLeft));

+

+        buf.putInt(convertColor(bottomLeft));

+        buf.putInt(convertColor(bottomRight));

+        

+        buf.flip();

+        quadColor.updateData(buf);

+        

+        niftyMat.getAdditionalRenderState().setBlendMode(convertBlend());

+        niftyMat.setColor("Color", ColorRGBA.White);

+        niftyMat.clearParam("Texture");

+        niftyMat.setBoolean("UseTex", false);

+

+        tempMat.loadIdentity();

+        tempMat.setTranslation(x, getHeight() - y, 0);

+        tempMat.setScale(width, height, 0);

+

+        rm.setWorldMatrix(tempMat);

+        niftyMat.render(quadGeom, rm);

+//        

+//        System.out.println("renderQuad (Grad)");

+    }

+

+    public void enableClip(int x0, int y0, int x1, int y1){

+//        System.out.println("enableClip");

+        clipWasSet = true;

+        r.setClipRect(x0, getHeight() - y1, x1 - x0, y1 - y0);

+    }

+

+    public void disableClip() {

+//        System.out.println("disableClip");

+        if (clipWasSet){

+            r.clearClipRect();

+            clipWasSet = false;

+        }

+    }

+

+    

+

+}

diff --git a/engine/src/niftygui/com/jme3/niftygui/RenderFontJme.java b/engine/src/niftygui/com/jme3/niftygui/RenderFontJme.java
new file mode 100644
index 0000000..dccef34
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/RenderFontJme.java
@@ -0,0 +1,121 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.niftygui;

+

+import com.jme3.font.BitmapFont;

+import com.jme3.font.BitmapText;

+import de.lessvoid.nifty.spi.render.RenderFont;

+

+public class RenderFontJme implements RenderFont {

+

+    private NiftyJmeDisplay display;

+    private BitmapFont font;

+    private BitmapText text;

+    private float actualSize;

+

+    /**

+     * Initialize the font.

+     * @param name font filename

+     */

+    public RenderFontJme(String name, NiftyJmeDisplay display) {

+        this.display = display;

+        font = display.getAssetManager().loadFont(name);

+        if (font == null) {

+            throw new RuntimeException( "Font not loaded:" + name );

+        }

+        text = new BitmapText(font);

+        actualSize = font.getPreferredSize();

+        text.setSize(actualSize);

+    }

+

+    public BitmapText createText() {

+      return new BitmapText(font);

+    }

+

+    public BitmapText getText(){

+        return text;

+    }

+

+    /**

+     * get font height.

+     * @return height

+     */

+    public int getHeight() {

+        return (int) text.getLineHeight();

+    }

+

+    /**

+     * get font width of the given string.

+     * @param str text

+     * @return width of the given text for the current font

+     */

+    public int getWidth(final String str) {

+        if (str.length() == 0)

+            return 0;

+ 

+        // Note: BitmapFont is now fixed to return the proper line width

+        //       at least for now.  The older commented out (by someone else, not me)

+        //       code below is arguably 'more accurate' if BitmapFont gets

+        //       buggy again.  The issue is that the BitmapText and BitmapFont

+        //       use a different algorithm for calculating size and both must

+        //       be modified in sync.       

+        int result = (int) font.getLineWidth(str);

+//        text.setText(str);

+//        text.updateLogicalState(0);

+//        int result = (int) text.getLineWidth();

+

+        return result;

+    }

+

+    public int getWidth(final String str, final float size) {

+      // Note: This is supposed to return the width of the String when scaled

+      //       with the size factor. Since I don't know how to do that with

+      //       the font rendering in jme this will only work correctly with

+      //       a size value of 1.f and will return inaccurate values otherwise.

+      return getWidth(str);

+    }

+

+    /**

+     * Return the width of the given character including kerning information.

+     * @param currentCharacter current character

+     * @param nextCharacter next character

+     * @param size font size

+     * @return width of the character or null when no information for the character is available

+     */

+    public int getCharacterAdvance(final char currentCharacter, final char nextCharacter, final float size) {

+        return Math.round(font.getCharacterAdvance(currentCharacter, nextCharacter, size));

+    }

+

+    public void dispose() {

+    }

+}

diff --git a/engine/src/niftygui/com/jme3/niftygui/RenderImageJme.java b/engine/src/niftygui/com/jme3/niftygui/RenderImageJme.java
new file mode 100644
index 0000000..e871e8a
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/RenderImageJme.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import de.lessvoid.nifty.spi.render.RenderImage;
+
+public class RenderImageJme implements RenderImage {
+
+    private Texture2D texture;
+    private Image image;
+    private int width;
+    private int height;
+
+    public RenderImageJme(String filename, boolean linear, NiftyJmeDisplay display){
+        TextureKey key = new TextureKey(filename, true);
+
+        key.setAnisotropy(0);
+        key.setAsCube(false);
+        key.setGenerateMips(false);
+        
+        texture = (Texture2D) display.getAssetManager().loadTexture(key);
+        texture.setMagFilter(linear ? MagFilter.Bilinear : MagFilter.Nearest);
+        texture.setMinFilter(linear ? MinFilter.BilinearNoMipMaps : MinFilter.NearestNoMipMaps);
+        image = texture.getImage();
+
+        width = image.getWidth();
+        height = image.getHeight();
+    }
+
+    public RenderImageJme(Texture2D texture){
+        if (texture.getImage() == null)
+            throw new IllegalArgumentException("texture.getImage() cannot be null");
+        
+        this.texture = texture;
+        this.image = texture.getImage();
+        width = image.getWidth();
+        height = image.getHeight();
+    }
+
+    public Texture2D getTexture(){
+        return texture;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public void dispose() {
+    }
+}
diff --git a/engine/src/niftygui/com/jme3/niftygui/SoundDeviceJme.java b/engine/src/niftygui/com/jme3/niftygui/SoundDeviceJme.java
new file mode 100644
index 0000000..a936e03
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/SoundDeviceJme.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.AudioRenderer;
+import de.lessvoid.nifty.sound.SoundSystem;
+import de.lessvoid.nifty.spi.sound.SoundDevice;
+import de.lessvoid.nifty.spi.sound.SoundHandle;
+import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader;
+
+public class SoundDeviceJme implements SoundDevice {
+
+    protected AssetManager assetManager;
+    protected AudioRenderer ar;
+
+    public SoundDeviceJme(AssetManager assetManager, AudioRenderer ar){
+        this.assetManager = assetManager;
+        this.ar = ar;
+    }
+
+    public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) {
+    }
+
+    public SoundHandle loadSound(SoundSystem soundSystem, String filename) {
+        AudioNode an = new AudioNode(assetManager, filename, false);
+        an.setPositional(false);
+        return new SoundHandleJme(ar, an);
+    }
+
+    public SoundHandle loadMusic(SoundSystem soundSystem, String filename) {
+        return new SoundHandleJme(ar, assetManager, filename);
+    }
+
+    public void update(int delta) {
+    }
+    
+}
diff --git a/engine/src/niftygui/com/jme3/niftygui/SoundHandleJme.java b/engine/src/niftygui/com/jme3/niftygui/SoundHandleJme.java
new file mode 100644
index 0000000..04ce42a
--- /dev/null
+++ b/engine/src/niftygui/com/jme3/niftygui/SoundHandleJme.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.AudioNode.Status;
+import com.jme3.audio.AudioRenderer;
+import de.lessvoid.nifty.spi.sound.SoundHandle;
+
+public class SoundHandleJme implements SoundHandle {
+
+    private AudioNode node;
+    private AssetManager am;
+    private String fileName;
+    private float volume = 1;
+
+    public SoundHandleJme(AudioRenderer ar, AudioNode node){
+        if (ar == null || node == null)
+            throw new NullPointerException();
+
+        this.node = node;
+    }
+
+    /**
+     * For streaming music only. (May need to loop..)
+     * @param ar
+     * @param am
+     * @param fileName
+     */
+    public SoundHandleJme(AudioRenderer ar, AssetManager am, String fileName){
+        if (ar == null || am == null)
+            throw new NullPointerException();
+
+        this.am = am;
+        if (fileName == null)
+            throw new NullPointerException();
+        
+        this.fileName = fileName;
+    }
+
+    public void play() {
+        if (fileName != null){
+            if (node != null){
+                node.stop();
+            }
+
+            node = new AudioNode(am, fileName, true);
+            node.setPositional(false);
+            node.setVolume(volume);
+            node.play();
+        }else{
+            node.playInstance();
+        }
+    }
+
+    public void stop() {
+        if (node != null){
+            node.stop();
+            node = null;
+        }
+    }
+
+    public void setVolume(float f) {
+        if (node != null) {
+            node.setVolume(f);
+        }
+        volume = f;
+    }
+
+    public float getVolume() {
+        return volume;
+    }
+
+    public boolean isPlaying() {
+        return node != null && node.getStatus() == Status.Playing;
+    }
+
+    public void dispose() {
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/AnimData.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/AnimData.java
new file mode 100644
index 0000000..f8d99aa
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/AnimData.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.animation.Animation;
+import com.jme3.animation.Skeleton;
+import java.util.ArrayList;
+
+public class AnimData {
+
+    public final Skeleton skeleton;
+    public final ArrayList<Animation> anims;
+
+    public AnimData(Skeleton skeleton, ArrayList<Animation> anims) {
+        this.skeleton = skeleton;
+        this.anims = anims;
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/MaterialLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/MaterialLoader.java
new file mode 100644
index 0000000..887ba5e
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MaterialLoader.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.plugins.ogre.matext.MaterialExtensionLoader;
+import com.jme3.scene.plugins.ogre.matext.MaterialExtensionSet;
+import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.PlaceholderAssets;
+import com.jme3.util.blockparser.BlockLanguageParser;
+import com.jme3.util.blockparser.Statement;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MaterialLoader implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(MaterialLoader.class.getName());
+
+    private String folderName;
+    private AssetManager assetManager;
+    private ColorRGBA ambient, diffuse, specular, emissive;
+    private Texture[] textures = new Texture[4];
+    private String texName;
+    private String matName;
+    private float shinines;
+    private boolean vcolor = false;
+    private boolean blend = false;
+    private boolean twoSide = false;
+    private boolean noLight = false;
+    private boolean separateTexCoord = false;
+    private int texUnit = 0;
+
+    private ColorRGBA readColor(String content){
+        String[] split = content.split("\\s");
+        
+        ColorRGBA color = new ColorRGBA();
+        color.r = Float.parseFloat(split[0]);
+        color.g = Float.parseFloat(split[1]);
+        color.b = Float.parseFloat(split[2]);
+        if (split.length >= 4){
+            color.a = Float.parseFloat(split[3]);
+        }
+        return color;
+    }
+
+    private void readTextureImage(String content){
+        // texture image def
+        String path = null;
+
+        // find extension
+        int extStart = content.lastIndexOf(".");
+        for (int i = extStart; i < content.length(); i++){
+            char c = content.charAt(i);
+            if (Character.isWhitespace(c)){
+                // extension ends here
+                path = content.substring(0, i).trim();
+                content   = content.substring(i+1).trim();
+                break;
+            }
+        }
+        if (path == null){
+            path = content.trim();
+            content = "";
+        }
+
+        Scanner lnScan = new Scanner(content);
+        String mips = null;
+        String type = null;
+        if (lnScan.hasNext()){
+            // more params
+            type = lnScan.next();
+//            if (!lnScan.hasNext("\n") && lnScan.hasNext()){
+//                mips = lnScan.next();
+//                if (lnScan.hasNext()){
+                    // even more params..
+                    // will have to ignore
+//                }
+//            }
+        }
+
+        boolean genMips = true;
+        boolean cubic = false;
+        if (type != null && type.equals("0"))
+            genMips = false;
+
+        if (type != null && type.equals("cubic")){
+            cubic = true;
+        }
+
+        TextureKey texKey = new TextureKey(folderName + path, false);
+        texKey.setGenerateMips(genMips);
+        texKey.setAsCube(cubic);
+
+        try {
+            Texture loadedTexture = assetManager.loadTexture(texKey);
+            
+            textures[texUnit].setImage(loadedTexture.getImage());
+            textures[texUnit].setMinFilter(loadedTexture.getMinFilter());
+            textures[texUnit].setKey(loadedTexture.getKey());
+
+            // XXX: Is this really neccessary?
+            textures[texUnit].setWrap(WrapMode.Repeat);
+            if (texName != null){
+                textures[texUnit].setName(texName);
+                texName = null;
+            }else{
+                textures[texUnit].setName(texKey.getName());
+            }
+        } catch (AssetNotFoundException ex){
+            logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, matName});
+            textures[texUnit].setImage(PlaceholderAssets.getPlaceholderImage());
+        }
+    }
+
+    private void readTextureUnitStatement(Statement statement){
+        String[] split = statement.getLine().split(" ", 2);
+        String keyword = split[0];
+        if (keyword.equals("texture")){
+            readTextureImage(split[1]);
+        }else if (keyword.equals("tex_address_mode")){
+            String mode = split[1];
+            if (mode.equals("wrap")){
+                textures[texUnit].setWrap(WrapMode.Repeat);
+            }else if (mode.equals("clamp")){
+                textures[texUnit].setWrap(WrapMode.Clamp);
+            }else if (mode.equals("mirror")){
+                textures[texUnit].setWrap(WrapMode.MirroredRepeat);
+            }else if (mode.equals("border")){
+                textures[texUnit].setWrap(WrapMode.BorderClamp);
+            }
+        }else if (keyword.equals("filtering")){
+            // ignored.. only anisotropy is considered
+        }else if (keyword.equals("tex_coord_set")){
+            int texCoord = Integer.parseInt(split[1]);
+            if (texCoord == 1){
+                separateTexCoord = true;
+            }
+        }else if (keyword.equals("max_anisotropy")){
+            int amount = Integer.parseInt(split[1]);
+            textures[texUnit].setAnisotropicFilter(amount);
+        }else{
+            logger.log(Level.WARNING, "Unsupported texture_unit directive: {0}", keyword);
+        }
+    }
+
+    private void readTextureUnit(Statement statement){
+        String[] split = statement.getLine().split(" ", 2); 
+        // name is optional
+        if (split.length == 2){
+            texName = split[1];
+        }else{
+            texName = null;
+        }
+
+        textures[texUnit] = new Texture2D();
+        for (Statement texUnitStat : statement.getContents()){
+            readTextureUnitStatement(texUnitStat);
+        }
+        if (textures[texUnit].getImage() != null){
+            texUnit++;
+        }else{
+            // no image was loaded, ignore
+            textures[texUnit] = null;
+        }
+    }
+
+    private void readPassStatement(Statement statement){
+        // read until newline
+        String[] split = statement.getLine().split(" ", 2);
+        String keyword = split[0];
+        if (keyword.equals("diffuse")){
+            if (split[1].equals("vertexcolour")){
+                // use vertex colors
+                diffuse = ColorRGBA.White;
+                vcolor = true;
+            }else{
+                diffuse = readColor(split[1]);
+            }
+        }else if(keyword.equals("ambient")) {
+           if (split[1].equals("vertexcolour")){
+                // use vertex colors
+               ambient = ColorRGBA.White;
+            }else{
+               ambient = readColor(split[1]);
+            }
+        }else if (keyword.equals("emissive")){
+            emissive = readColor(split[1]);
+        }else if (keyword.equals("specular")){
+            String[] subsplit = split[1].split("\\s");
+            specular = new ColorRGBA();
+            specular.r = Float.parseFloat(subsplit[0]);
+            specular.g = Float.parseFloat(subsplit[1]);
+            specular.b = Float.parseFloat(subsplit[2]);
+            float unknown = Float.parseFloat(subsplit[3]);
+            if (subsplit.length >= 5){
+                // using 5 float values
+                specular.a = unknown;
+                shinines = Float.parseFloat(subsplit[4]);
+            }else{
+                // using 4 float values
+                specular.a = 1f;
+                shinines = unknown;
+            }
+        }else if (keyword.equals("texture_unit")){
+            readTextureUnit(statement);
+        }else if (keyword.equals("scene_blend")){
+            String mode = split[1];
+            if (mode.equals("alpha_blend")){
+                blend = true;
+            }
+        }else if (keyword.equals("cull_hardware")){
+            String mode = split[1];
+            if (mode.equals("none")){
+                twoSide = true;
+            }
+        }else if (keyword.equals("cull_software")){
+            // ignore
+        }else if (keyword.equals("lighting")){
+            String isOn = split[1];
+            if (isOn.equals("on")){
+                noLight = false;
+            }else if (isOn.equals("off")){
+                noLight = true;
+            }
+        }else{
+            logger.log(Level.WARNING, "Unsupported pass directive: {0}", keyword);
+        }
+    }
+
+    private void readPass(Statement statement){
+        String name;
+        String[] split = statement.getLine().split(" ", 2);
+        if (split.length == 1){
+            // no name
+            name = null;
+        }else{
+            name = split[1];
+        }
+        
+        for (Statement passStat : statement.getContents()){
+            readPassStatement(passStat);
+        }
+        
+        texUnit = 0;
+    }
+
+    private void readTechnique(Statement statement){
+        String[] split = statement.getLine().split(" ", 2);
+        String name;
+        if (split.length == 1){
+            // no name
+            name = null;
+        }else{
+            name = split[1];
+        }
+        for (Statement techStat : statement.getContents()){
+            readPass(techStat);
+        }
+    }
+
+    private void readMaterialStatement(Statement statement){
+        if (statement.getLine().startsWith("technique")){
+            readTechnique(statement);
+        }else if (statement.getLine().startsWith("receive_shadows")){
+            String isOn = statement.getLine().split("\\s")[1];
+            if (isOn != null && isOn.equals("true")){
+            }
+        }
+    }
+
+    @SuppressWarnings("empty-statement")
+    private void readMaterial(Statement statement){
+        for (Statement materialStat : statement.getContents()){
+            readMaterialStatement(materialStat);
+        }
+    }
+
+    private Material compileMaterial(){
+        Material mat;
+        if (noLight){
+           mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        }else{
+           mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        }
+        if (blend){
+            RenderState rs = mat.getAdditionalRenderState();
+            rs.setAlphaTest(true);
+            rs.setAlphaFallOff(0.01f);
+            rs.setBlendMode(RenderState.BlendMode.Alpha);
+            
+            if (twoSide){
+                rs.setFaceCullMode(RenderState.FaceCullMode.Off);
+            }
+            
+//            rs.setDepthWrite(false);
+            mat.setTransparent(true);
+            if (!noLight){
+                mat.setBoolean("UseAlpha", true);
+            }
+        }else{
+            if (twoSide){
+                RenderState rs = mat.getAdditionalRenderState();
+                rs.setFaceCullMode(RenderState.FaceCullMode.Off);
+            }
+        }
+
+        if (!noLight){
+            if (shinines > 0f) {
+                mat.setFloat("Shininess", shinines);
+            } else {
+                mat.setFloat("Shininess", 16f); // set shininess to some value anyway..
+            }
+            
+            if (vcolor)
+                mat.setBoolean("UseVertexColor", true);
+
+            if (textures[0] != null)
+                mat.setTexture("DiffuseMap", textures[0]);
+
+            mat.setBoolean("UseMaterialColors", true);
+            if(diffuse != null){
+                mat.setColor("Diffuse",  diffuse);
+            }else{
+                mat.setColor("Diffuse", ColorRGBA.White);
+            }
+
+            if(ambient != null){
+                mat.setColor("Ambient",  ambient);
+            }else{
+                mat.setColor("Ambient", ColorRGBA.DarkGray);
+            }
+
+            if(specular != null){
+                mat.setColor("Specular", specular);
+            }else{
+                mat.setColor("Specular", ColorRGBA.Black);
+            }
+            
+            if (emissive != null){
+                mat.setColor("GlowColor", emissive);
+            }
+        }else{
+            if (vcolor) {
+                mat.setBoolean("VertexColor", true);
+            }
+
+            if (textures[0] != null && textures[1] == null){
+                if (separateTexCoord){
+                    mat.setTexture("LightMap", textures[0]);
+                    mat.setBoolean("SeparateTexCoord", true);
+                }else{
+                    mat.setTexture("ColorMap", textures[0]);
+                }
+            }else if (textures[1] != null){
+                mat.setTexture("ColorMap", textures[0]);
+                mat.setTexture("LightMap", textures[1]);
+                if (separateTexCoord){
+                    mat.setBoolean("SeparateTexCoord", true);
+                }
+            }
+                 
+            if(diffuse != null){
+                mat.setColor("Color", diffuse);
+            }
+            
+            if (emissive != null){
+                mat.setColor("GlowColor", emissive);
+            }
+        }
+
+        noLight = false;
+        Arrays.fill(textures, null);
+        diffuse = null;
+        specular = null;
+        shinines = 0f;
+        vcolor = false;
+        blend = false;
+        texUnit = 0;
+        separateTexCoord = false;
+        return mat;
+    }
+    
+    private MaterialList load(AssetManager assetManager, AssetKey key, InputStream in) throws IOException{
+        folderName = key.getFolder();
+        this.assetManager = assetManager;
+        
+        MaterialList list = null;
+        List<Statement> statements = BlockLanguageParser.parse(in);
+        
+        for (Statement statement : statements){
+            if (statement.getLine().startsWith("import")){
+                MaterialExtensionSet matExts = null;
+                if (key instanceof OgreMaterialKey){
+                     matExts = ((OgreMaterialKey)key).getMaterialExtensionSet();
+                }
+
+                if (matExts == null){
+                    throw new IOException("Must specify MaterialExtensionSet when loading\n"+
+                                          "Ogre3D materials with extended materials");
+                }
+
+                list = new MaterialExtensionLoader().load(assetManager, key, matExts, statements);
+                break;
+            }else if (statement.getLine().startsWith("material")){
+                if (list == null){
+                    list = new MaterialList();
+                }
+                String[] split = statement.getLine().split(" ", 2);
+                matName = split[1].trim();
+                readMaterial(statement);
+                Material mat = compileMaterial();
+                list.put(matName, mat);
+            }
+        }
+        
+        return list;
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            return load(info.getManager(), info.getKey(), in);
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+    }
+
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java
new file mode 100644
index 0000000..75ff865
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre;
+
+//import static com.jmex.model.XMLUtil.getAttribute;
+//import static com.jmex.model.XMLUtil.getIntAttribute;
+//
+//import java.util.ArrayList;
+//import java.util.List;
+//import java.util.Map;
+//
+//import org.w3c.dom.Node;
+//
+//import com.jme.math.Vector3f;
+//import com.jmex.model.XMLUtil;
+//import com.jmex.model.ogrexml.anim.PoseTrack.PoseFrame;
+
+/**
+ * Utility class used by OgreLoader to load poses and mesh animations.
+ */
+public class MeshAnimationLoader {
+
+//    public static void loadMeshAnimations(Node animationsNode, List<Pose> poseList, OgreMesh sharedgeom, List<OgreMesh> submeshes, Map<String, Animation> animations){
+//        Node animationNode = animationsNode.getFirstChild();
+//        while (animationNode != null){
+//            if (animationNode.getNodeName().equals("animation")){
+//                MeshAnimation mAnim =
+//                        loadMeshAnimation(animationNode, poseList, sharedgeom, submeshes);
+//
+//                Animation anim = animations.get(mAnim.getName());
+//                if (anim != null){
+//                    anim.setMeshAnimation(mAnim);
+//                }else{
+//                    anim = new Animation(null, mAnim);
+//                    animations.put(anim.getName(), anim);
+//                }
+//            }
+//            animationNode = animationNode.getNextSibling();
+//        }
+//
+////            Map<TriMesh, List<Pose>> trimeshPoses = new HashMap<TriMesh, List<Pose>>();
+////
+////            // find the poses for each mesh
+////            for (Pose p : poses){
+////                List<Pose> poseList = trimeshPoses.get(p.getTarget());
+////                if (poseList == null){
+////                    poseList = new ArrayList<Pose>();
+////                    trimeshPoses.put(p.getTarget(), poseList);
+////                }
+////
+////                poseList.add(p);
+////            }
+////
+////            for (Map.Entry<TriMesh, List<Pose>> poseEntry: trimeshPoses){
+////                PoseController
+////            }
+//    }
+//
+//    public static MeshAnimation loadMeshAnimation(Node animationNode, List<Pose> poseList, OgreMesh sharedgeom, List<OgreMesh> submeshes){
+//        String name =  XMLUtil.getAttribute(animationNode, "name");
+//        float length = XMLUtil.getFloatAttribute(animationNode, "length");
+//
+//        MeshAnimation anim = new MeshAnimation(name, length);
+//        List<Track> tracks = new ArrayList<Track>();
+//
+//        Node tracksNode = XMLUtil.getChildNode(animationNode, "tracks");
+//        if (tracksNode != null){
+//            Node trackNode = tracksNode.getFirstChild();
+//            while (trackNode != null){
+//                if (trackNode.getNodeName().equals("track")){
+//                    int targetMeshIndex;
+//                    if (XMLUtil.getAttribute(trackNode, "target").equals("mesh")){
+//                        targetMeshIndex = -1;
+//                    }else{
+//                        if (XMLUtil.getAttribute(trackNode, "index") == null)
+//                            targetMeshIndex = 0;
+//                        else
+//                            targetMeshIndex = getIntAttribute(trackNode, "index");
+//                    }
+//
+//                    if (XMLUtil.getAttribute(trackNode, "type").equals("pose")){
+//                        PoseTrack pt = loadPoseTrack(trackNode, targetMeshIndex, poseList);
+//                        tracks.add(pt);
+//                    }else{
+//                        throw new UnsupportedOperationException("Morph animations not supported!");
+//                    }
+//                }
+//
+//                trackNode = trackNode.getNextSibling();
+//            }
+//        }
+//
+//        anim.setTracks(tracks.toArray(new Track[0]));
+//
+//        return anim;
+//    }
+//
+//    public static List<Pose> loadPoses(Node posesNode, OgreMesh sharedgeom, List<OgreMesh> submeshes){
+//        List<Pose> poses = new ArrayList<Pose>();
+//        Node poseNode = posesNode.getFirstChild();
+//        while (poseNode != null){
+//            if (poseNode.getNodeName().equals("pose")){
+//                int targetMeshIndex = 0;
+//                if (getAttribute(poseNode, "target").equals("mesh")){
+//                    targetMeshIndex = -1;
+//                }else{
+//                    if (getAttribute(poseNode, "index") == null)
+//                        targetMeshIndex = 0;
+//                    else
+//                        targetMeshIndex = getIntAttribute(poseNode, "index");
+//                }
+//
+//                Pose p = MeshAnimationLoader.loadPose(poseNode, targetMeshIndex);
+//                poses.add(p);
+//            }
+//
+//            poseNode = poseNode.getNextSibling();
+//        }
+//
+//        return poses;
+//    }
+//
+//    public static Pose loadPose(Node poseNode, int targetMeshIndex){
+//        String name = XMLUtil.getAttribute(poseNode, "name");
+//
+//        List<Vector3f> offsets = new ArrayList<Vector3f>();
+//        List<Integer>  indices = new ArrayList<Integer>();
+//
+//        Node poseoffsetNode = poseNode.getFirstChild();
+//        while (poseoffsetNode != null){
+//            if (poseoffsetNode.getNodeName().equals("poseoffset")){
+//                int vertIndex = XMLUtil.getIntAttribute(poseoffsetNode, "index");
+//                Vector3f offset = new Vector3f();
+//                offset.x = XMLUtil.getFloatAttribute(poseoffsetNode, "x");
+//                offset.y = XMLUtil.getFloatAttribute(poseoffsetNode, "y");
+//                offset.z = XMLUtil.getFloatAttribute(poseoffsetNode, "z");
+//
+//                offsets.add(offset);
+//                indices.add(vertIndex);
+//            }
+//
+//            poseoffsetNode = poseoffsetNode.getNextSibling();
+//        }
+//
+//        int[] indicesArray = new int[indices.size()];
+//        for (int i = 0; i < indicesArray.length; i++){
+//            indicesArray[i] = indices.get(i);
+//        }
+//
+//        Pose pose = new Pose(name,
+//                             targetMeshIndex,
+//                             offsets.toArray(new Vector3f[0]),
+//                             indicesArray);
+//
+//        return pose;
+//    }
+//
+//    public static PoseTrack loadPoseTrack(Node trackNode, int targetMeshIndex, List<Pose> posesList){
+//        List<Float> times = new ArrayList<Float>();
+//        List<PoseFrame> frames = new ArrayList<PoseFrame>();
+//
+//        Node keyframesNode = XMLUtil.getChildNode(trackNode, "keyframes");
+//        Node keyframeNode = keyframesNode.getFirstChild();
+//        while (keyframeNode != null){
+//            if (keyframeNode.getNodeName().equals("keyframe")){
+//                float time = XMLUtil.getFloatAttribute(keyframeNode, "time");
+//                List<Pose> poses = new ArrayList<Pose>();
+//                List<Float> weights = new ArrayList<Float>();
+//
+//                Node poserefNode = keyframeNode.getFirstChild();
+//                while (poserefNode != null){
+//                    if (poserefNode.getNodeName().equals("poseref")){
+//                        int poseindex = XMLUtil.getIntAttribute(poserefNode, "poseindex");
+//                        poses.add(posesList.get(poseindex));
+//                        float weight = XMLUtil.getFloatAttribute(poserefNode, "influence");
+//                        weights.add(weight);
+//                    }
+//
+//                    poserefNode = poserefNode.getNextSibling();
+//                }
+//
+//                // convert poses and weights to arrays and create a PoseFrame
+//                float[] weightsArray = new float[weights.size()];
+//                for (int i = 0; i < weightsArray.length; i++){
+//                    weightsArray[i] = weights.get(i);
+//                }
+//                PoseFrame frame = new PoseFrame(poses.toArray(new Pose[0]), weightsArray);
+//
+//                times.add(time);
+//                frames.add(frame);
+//            }
+//
+//            keyframeNode = keyframeNode.getNextSibling();
+//        }
+//
+//        // convert times and frames to arrays and write to the track
+//        float[] timesArray = new float[times.size()];
+//        for (int i = 0; i < timesArray.length; i++){
+//            timesArray[i] = times.get(i);
+//        }
+//
+//        PoseTrack track = new PoseTrack(targetMeshIndex,
+//                                        timesArray,
+//                                        frames.toArray(new PoseFrame[0]));
+//
+//        return track;
+//    }
+
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java
new file mode 100644
index 0000000..e7f4b10
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java
@@ -0,0 +1,892 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Animation;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.asset.*;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.PlaceholderAssets;
+import static com.jme3.util.xml.SAXUtil.*;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Loads Ogre3D mesh.xml files.
+ */
+public class MeshLoader extends DefaultHandler implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
+    public static boolean AUTO_INTERLEAVE = true;
+    public static boolean HARDWARE_SKINNING = false;
+    private static final Type[] TEXCOORD_TYPES =
+            new Type[]{
+        Type.TexCoord,
+        Type.TexCoord2,
+        Type.TexCoord3,
+        Type.TexCoord4,
+        Type.TexCoord5,
+        Type.TexCoord6,
+        Type.TexCoord7,
+        Type.TexCoord8,};
+    private AssetKey key;
+    private String meshName;
+    private String folderName;
+    private AssetManager assetManager;
+    private MaterialList materialList;
+    // Data per submesh/sharedgeom
+    private ShortBuffer sb;
+    private IntBuffer ib;
+    private FloatBuffer fb;
+    private VertexBuffer vb;
+    private Mesh mesh;
+    private Geometry geom;
+    private ByteBuffer indicesData;
+    private FloatBuffer weightsFloatData;
+    private boolean actuallyHasWeights = false;
+    private int vertCount;
+    private boolean usesSharedVerts;
+    private boolean usesBigIndices;
+    // Global data
+    private Mesh sharedMesh;
+    private int meshIndex = 0;
+    private int texCoordIndex = 0;
+    private String ignoreUntilEnd = null;
+    private List<Geometry> geoms = new ArrayList<Geometry>();
+    private ArrayList<Boolean> usesSharedMesh = new ArrayList<Boolean>();
+    private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
+    private AnimData animData;
+
+    public MeshLoader() {
+        super();
+    }
+
+    @Override
+    public void startDocument() {
+        geoms.clear();
+        lodLevels.clear();
+
+        sb = null;
+        ib = null;
+        fb = null;
+        vb = null;
+        mesh = null;
+        geom = null;
+        sharedMesh = null;
+
+        usesSharedMesh.clear();
+        usesSharedVerts = false;
+        vertCount = 0;
+        meshIndex = 0;
+        texCoordIndex = 0;
+        ignoreUntilEnd = null;
+
+        animData = null;
+
+        actuallyHasWeights = false;
+        indicesData = null;
+        weightsFloatData = null;
+    }
+
+    @Override
+    public void endDocument() {
+    }
+
+    private void pushIndex(int index){
+        if (ib != null){
+            ib.put(index);
+        }else{
+            sb.put((short)index);
+        }
+    }
+    
+    private void pushFace(String v1, String v2, String v3) throws SAXException {
+        // TODO: fan/strip support
+        switch (mesh.getMode()){
+            case Triangles:
+                pushIndex(parseInt(v1));
+                pushIndex(parseInt(v2));
+                pushIndex(parseInt(v3));
+                break;
+            case Lines:
+                pushIndex(parseInt(v1));
+                pushIndex(parseInt(v2));
+                break;
+            case Points:
+                pushIndex(parseInt(v1));
+                break;
+        }
+    }
+
+//    private boolean isUsingSharedVerts(Geometry geom) {
+        // Old code for buffer sharer
+        //return geom.getUserData(UserData.JME_SHAREDMESH) != null;
+//    }
+
+    private void startFaces(String count) throws SAXException {
+        int numFaces = parseInt(count);
+        int indicesPerFace = 0;
+
+        switch (mesh.getMode()){
+            case Triangles:
+                indicesPerFace = 3;
+                break;
+            case Lines:
+                indicesPerFace = 2;
+                break;
+            case Points:
+                indicesPerFace = 1;
+                break;
+            default:
+                throw new SAXException("Strips or fans not supported!");
+        }
+
+        int numIndices = indicesPerFace * numFaces;
+        
+        vb = new VertexBuffer(VertexBuffer.Type.Index);
+        if (!usesBigIndices) {
+            sb = BufferUtils.createShortBuffer(numIndices);
+            ib = null;
+            vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedShort, sb);
+        } else {
+            ib = BufferUtils.createIntBuffer(numIndices);
+            sb = null;
+            vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedInt, ib);
+        }
+        mesh.setBuffer(vb);
+    }
+
+    private void applyMaterial(Geometry geom, String matName) {
+        Material mat = null;
+        if (matName.endsWith(".j3m")) {
+            // load as native jme3 material instance
+            try {
+                mat = assetManager.loadMaterial(matName);
+            } catch (AssetNotFoundException ex){
+                // Warning will be raised (see below)
+                if (!ex.getMessage().equals(matName)){
+                    throw ex;
+                }
+            }
+        } else {
+            if (materialList != null) {
+                mat = materialList.get(matName);
+            }
+        }
+        
+        if (mat == null) {
+            logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{matName, key});
+            mat = PlaceholderAssets.getPlaceholderMaterial(assetManager);
+        }
+
+        if (mat.isTransparent()) {
+            geom.setQueueBucket(Bucket.Transparent);
+        }
+
+        geom.setMaterial(mat);
+    }
+
+    private void startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
+        mesh = new Mesh();
+        if (opType == null || opType.equals("triangle_list")) {
+            mesh.setMode(Mesh.Mode.Triangles);
+        //} else if (opType.equals("triangle_strip")) {
+        //    mesh.setMode(Mesh.Mode.TriangleStrip);
+        //} else if (opType.equals("triangle_fan")) {
+        //    mesh.setMode(Mesh.Mode.TriangleFan);
+        } else if (opType.equals("line_list")) {
+            mesh.setMode(Mesh.Mode.Lines);
+        } else {
+            throw new SAXException("Unsupported operation type: " + opType);
+        }
+
+        usesBigIndices = parseBool(use32bitIndices, false);
+        usesSharedVerts = parseBool(usesharedvertices, false);
+        if (usesSharedVerts) {
+            usesSharedMesh.add(true);
+            
+            // Old code for buffer sharer
+            // import vertexbuffers from shared geom
+//            IntMap<VertexBuffer> sharedBufs = sharedMesh.getBuffers();
+//            for (Entry<VertexBuffer> entry : sharedBufs) {
+//                mesh.setBuffer(entry.getValue());
+//            }
+        }else{
+            usesSharedMesh.add(false);
+        }
+
+        if (meshName == null) {
+            geom = new Geometry("OgreSubmesh-" + (++meshIndex), mesh);
+        } else {
+            geom = new Geometry(meshName + "-geom-" + (++meshIndex), mesh);
+        }
+
+        if (usesSharedVerts) {
+            // Old code for buffer sharer
+            // this mesh is shared!
+            //geom.setUserData(UserData.JME_SHAREDMESH, sharedMesh);
+        }
+
+        applyMaterial(geom, matName);
+        geoms.add(geom);
+    }
+
+    private void startSharedGeom(String vertexcount) throws SAXException {
+        sharedMesh = new Mesh();
+        vertCount = parseInt(vertexcount);
+        usesSharedVerts = false;
+
+        geom = null;
+        mesh = sharedMesh;
+    }
+
+    private void startGeometry(String vertexcount) throws SAXException {
+        vertCount = parseInt(vertexcount);
+    }
+
+    /**
+     * Normalizes weights if needed and finds largest amount of weights used
+     * for all vertices in the buffer.
+     */
+    private void endBoneAssigns() {
+//        if (mesh != sharedMesh && isUsingSharedVerts(geom)) {
+//            return;
+//        }
+
+        if (!actuallyHasWeights){
+            // No weights were actually written (the tag didn't have any entries)
+            // remove those buffers
+            mesh.clearBuffer(Type.BoneIndex);
+            mesh.clearBuffer(Type.BoneWeight);
+            
+            weightsFloatData = null;
+            indicesData = null;
+            
+            return;
+        }
+        
+        //int vertCount = mesh.getVertexCount();
+        int maxWeightsPerVert = 0;
+        weightsFloatData.rewind();
+        for (int v = 0; v < vertCount; v++) {
+            float w0 = weightsFloatData.get(),
+                    w1 = weightsFloatData.get(),
+                    w2 = weightsFloatData.get(),
+                    w3 = weightsFloatData.get();
+
+            if (w3 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
+            } else if (w2 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
+            } else if (w1 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
+            } else if (w0 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
+            }
+
+            float sum = w0 + w1 + w2 + w3;
+            if (sum != 1f) {
+                weightsFloatData.position(weightsFloatData.position() - 4);
+                // compute new vals based on sum
+                float sumToB = 1f / sum;
+                weightsFloatData.put(w0 * sumToB);
+                weightsFloatData.put(w1 * sumToB);
+                weightsFloatData.put(w2 * sumToB);
+                weightsFloatData.put(w3 * sumToB);
+            }
+        }
+        weightsFloatData.rewind();
+
+        actuallyHasWeights = false;
+        weightsFloatData = null;
+        indicesData = null;
+
+        mesh.setMaxNumWeights(maxWeightsPerVert);
+    }
+
+    private void startBoneAssigns() {
+        if (mesh != sharedMesh && usesSharedVerts) {
+            // will use bone assignments from shared mesh (?)
+            return;
+        }
+
+        // current mesh will have bone assigns
+        //int vertCount = mesh.getVertexCount();
+        // each vertex has
+        // - 4 bone weights
+        // - 4 bone indices
+        if (HARDWARE_SKINNING) {
+            weightsFloatData = BufferUtils.createFloatBuffer(vertCount * 4);
+            indicesData = BufferUtils.createByteBuffer(vertCount * 4);
+        } else {
+            // create array-backed buffers if software skinning for access speed
+            weightsFloatData = FloatBuffer.allocate(vertCount * 4);
+            indicesData = ByteBuffer.allocate(vertCount * 4);
+        }
+
+        VertexBuffer weights = new VertexBuffer(Type.BoneWeight);
+        VertexBuffer indices = new VertexBuffer(Type.BoneIndex);
+
+        Usage usage = HARDWARE_SKINNING ? Usage.Static : Usage.CpuOnly;
+        weights.setupData(usage, 4, Format.Float, weightsFloatData);
+        indices.setupData(usage, 4, Format.UnsignedByte, indicesData);
+
+        mesh.setBuffer(weights);
+        mesh.setBuffer(indices);
+    }
+
+    private void startVertexBuffer(Attributes attribs) throws SAXException {
+        if (parseBool(attribs.getValue("positions"), false)) {
+            vb = new VertexBuffer(Type.Position);
+            fb = BufferUtils.createFloatBuffer(vertCount * 3);
+            vb.setupData(Usage.Static, 3, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+        if (parseBool(attribs.getValue("normals"), false)) {
+            vb = new VertexBuffer(Type.Normal);
+            fb = BufferUtils.createFloatBuffer(vertCount * 3);
+            vb.setupData(Usage.Static, 3, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+        if (parseBool(attribs.getValue("colours_diffuse"), false)) {
+            vb = new VertexBuffer(Type.Color);
+            fb = BufferUtils.createFloatBuffer(vertCount * 4);
+            vb.setupData(Usage.Static, 4, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+        if (parseBool(attribs.getValue("tangents"), false)) {
+            int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3);
+            vb = new VertexBuffer(Type.Tangent);
+            fb = BufferUtils.createFloatBuffer(vertCount * dimensions);
+            vb.setupData(Usage.Static, dimensions, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+        if (parseBool(attribs.getValue("binormals"), false)) {
+            vb = new VertexBuffer(Type.Binormal);
+            fb = BufferUtils.createFloatBuffer(vertCount * 3);
+            vb.setupData(Usage.Static, 3, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+
+        int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
+        for (int i = 0; i < texCoords; i++) {
+            int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2);
+            if (dims < 1 || dims > 4) {
+                throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4");
+            }
+
+            if (i <= 7) {
+                vb = new VertexBuffer(TEXCOORD_TYPES[i]);
+            } else {
+                // more than 8 texture coordinates are not supported by ogre.
+                throw new SAXException("More than 8 texture coordinates not supported");
+            }
+            fb = BufferUtils.createFloatBuffer(vertCount * dims);
+            vb.setupData(Usage.Static, dims, Format.Float, fb);
+            mesh.setBuffer(vb);
+        }
+    }
+
+    private void startVertex() {
+        texCoordIndex = 0;
+    }
+
+    private void pushAttrib(Type type, Attributes attribs) throws SAXException {
+        try {
+            FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
+            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
+        } catch (Exception ex) {
+            throw new SAXException("Failed to push attrib", ex);
+        }
+    }
+
+    private void pushTangent(Attributes attribs) throws SAXException {
+        try {
+            VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
+            FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
+            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
+            if (tangentBuf.getNumComponents() == 4) {
+                buf.put(parseFloat(attribs.getValue("w")));
+            }
+        } catch (Exception ex) {
+            throw new SAXException("Failed to push attrib", ex);
+        }
+    }
+
+    private void pushTexCoord(Attributes attribs) throws SAXException {
+        if (texCoordIndex >= 8) {
+            return; // More than 8 not supported by ogre.
+        }
+        Type type = TEXCOORD_TYPES[texCoordIndex];
+
+        VertexBuffer tcvb = mesh.getBuffer(type);
+        FloatBuffer buf = (FloatBuffer) tcvb.getData();
+
+        buf.put(parseFloat(attribs.getValue("u")));
+        if (tcvb.getNumComponents() >= 2) {
+            buf.put(parseFloat(attribs.getValue("v")));
+            if (tcvb.getNumComponents() >= 3) {
+                buf.put(parseFloat(attribs.getValue("w")));
+                if (tcvb.getNumComponents() == 4) {
+                    buf.put(parseFloat(attribs.getValue("x")));
+                }
+            }
+        }
+
+        texCoordIndex++;
+    }
+
+    private void pushColor(Attributes attribs) throws SAXException {
+        FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
+        String value = parseString(attribs.getValue("value"));
+        String[] vals = value.split("\\s");
+        if (vals.length != 3 && vals.length != 4) {
+            throw new SAXException("Color value must contain 3 or 4 components");
+        }
+
+        ColorRGBA color = new ColorRGBA();
+        color.r = parseFloat(vals[0]);
+        color.g = parseFloat(vals[1]);
+        color.b = parseFloat(vals[2]);
+        if (vals.length == 3) {
+            color.a = 1f;
+        } else {
+            color.a = parseFloat(vals[3]);
+        }
+
+        buf.put(color.r).put(color.g).put(color.b).put(color.a);
+    }
+
+    private void startLodFaceList(String submeshindex, String numfaces) {
+        int index = Integer.parseInt(submeshindex);
+        mesh = geoms.get(index).getMesh();
+        int faceCount = Integer.parseInt(numfaces);
+        
+        VertexBuffer originalIndexBuffer = mesh.getBuffer(Type.Index);
+        vb = new VertexBuffer(VertexBuffer.Type.Index);
+        if (originalIndexBuffer.getFormat() == Format.UnsignedInt){
+            // LOD buffer should also be integer
+            ib = BufferUtils.createIntBuffer(faceCount * 3);
+            sb = null;
+            vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib);
+        }else{
+            sb = BufferUtils.createShortBuffer(faceCount * 3);
+            ib = null;
+            vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
+        }
+
+        List<VertexBuffer> levels = lodLevels.get(index);
+        if (levels == null) {
+            // Create the LOD levels list
+            levels = new ArrayList<VertexBuffer>();
+            
+            // Add the first LOD level (always the original index buffer)
+            levels.add(originalIndexBuffer);
+            lodLevels.put(index, levels);
+        }
+        levels.add(vb);
+    }
+
+    private void startLevelOfDetail(String numlevels) {
+//        numLevels = Integer.parseInt(numlevels);
+    }
+
+    private void endLevelOfDetail() {
+        // set the lod data for each mesh
+        for (Entry<List<VertexBuffer>> entry : lodLevels) {
+            Mesh m = geoms.get(entry.getKey()).getMesh();
+            List<VertexBuffer> levels = entry.getValue();
+            VertexBuffer[] levelArray = new VertexBuffer[levels.size()];
+            levels.toArray(levelArray);
+            m.setLodLevels(levelArray);
+        }
+    }
+
+    private void startLodGenerated(String depthsqr) {
+    }
+
+    private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
+        int vert = parseInt(vertIndex);
+        float w = parseFloat(weight);
+        byte bone = (byte) parseInt(boneIndex);
+
+        assert bone >= 0;
+        assert vert >= 0 && vert < mesh.getVertexCount();
+
+        int i;
+        float v = 0;
+        // see which weights are unused for a given bone
+        for (i = vert * 4; i < vert * 4 + 4; i++) {
+            v = weightsFloatData.get(i);
+            if (v == 0) {
+                break;
+            }
+        }
+        if (v != 0) {
+            logger.log(Level.WARNING, "Vertex {0} has more than 4 weights per vertex! Ignoring..", vert);
+            return;
+        }
+
+        weightsFloatData.put(i, w);
+        indicesData.put(i, bone);
+        actuallyHasWeights = true;
+    }
+
+    private void startSkeleton(String name) {
+        AssetKey assetKey = new AssetKey(folderName + name + ".xml");
+        try {
+            animData = (AnimData) assetManager.loadAsset(assetKey);
+        } catch (AssetNotFoundException ex){
+            logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{assetKey, key});
+            animData = null;
+        }
+    }
+
+    private void startSubmeshName(String indexStr, String nameStr) {
+        int index = Integer.parseInt(indexStr);
+        geoms.get(index).setName(nameStr);
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
+        if (ignoreUntilEnd != null) {
+            return;
+        }
+
+        if (qName.equals("texcoord")) {
+            pushTexCoord(attribs);
+        } else if (qName.equals("vertexboneassignment")) {
+            pushBoneAssign(attribs.getValue("vertexindex"),
+                    attribs.getValue("boneindex"),
+                    attribs.getValue("weight"));
+        } else if (qName.equals("face")) {
+            pushFace(attribs.getValue("v1"),
+                    attribs.getValue("v2"),
+                    attribs.getValue("v3"));
+        } else if (qName.equals("position")) {
+            pushAttrib(Type.Position, attribs);
+        } else if (qName.equals("normal")) {
+            pushAttrib(Type.Normal, attribs);
+        } else if (qName.equals("tangent")) {
+            pushTangent(attribs);
+        } else if (qName.equals("binormal")) {
+            pushAttrib(Type.Binormal, attribs);
+        } else if (qName.equals("colour_diffuse")) {
+            pushColor(attribs);
+        } else if (qName.equals("vertex")) {
+            startVertex();
+        } else if (qName.equals("faces")) {
+            startFaces(attribs.getValue("count"));
+        } else if (qName.equals("geometry")) {
+            String count = attribs.getValue("vertexcount");
+            if (count == null) {
+                count = attribs.getValue("count");
+            }
+            startGeometry(count);
+        } else if (qName.equals("vertexbuffer")) {
+            startVertexBuffer(attribs);
+        } else if (qName.equals("lodfacelist")) {
+            startLodFaceList(attribs.getValue("submeshindex"),
+                    attribs.getValue("numfaces"));
+        } else if (qName.equals("lodgenerated")) {
+            startLodGenerated(attribs.getValue("fromdepthsquared"));
+        } else if (qName.equals("levelofdetail")) {
+            startLevelOfDetail(attribs.getValue("numlevels"));
+        } else if (qName.equals("boneassignments")) {
+            startBoneAssigns();
+        } else if (qName.equals("submesh")) {
+            startSubMesh(attribs.getValue("material"),
+                    attribs.getValue("usesharedvertices"),
+                    attribs.getValue("use32bitindexes"),
+                    attribs.getValue("operationtype"));
+        } else if (qName.equals("sharedgeometry")) {
+            String count = attribs.getValue("vertexcount");
+            if (count == null) {
+                count = attribs.getValue("count");
+            }
+
+            if (count != null && !count.equals("0")) {
+                startSharedGeom(count);
+            }
+        } else if (qName.equals("submeshes")) {
+            // ok
+        } else if (qName.equals("skeletonlink")) {
+            startSkeleton(attribs.getValue("name"));
+        } else if (qName.equals("submeshnames")) {
+            // ok
+        } else if (qName.equals("submeshname")) {
+            startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
+        } else if (qName.equals("mesh")) {
+            // ok
+        } else {
+            logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName);
+            ignoreUntilEnd = qName;
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String name, String qName) {
+        if (ignoreUntilEnd != null) {
+            if (ignoreUntilEnd.equals(qName)) {
+                ignoreUntilEnd = null;
+            }
+            return;
+        }
+
+        if (qName.equals("submesh")) {
+            usesBigIndices = false;
+            geom = null;
+            mesh = null;
+        } else if (qName.equals("submeshes")) {
+            // IMPORTANT: restore sharedmesh, for use with shared boneweights
+            geom = null;
+            mesh = sharedMesh;
+            usesSharedVerts = false;
+        } else if (qName.equals("faces")) {
+            if (ib != null) {
+                ib.flip();
+            } else {
+                sb.flip();
+            }
+
+            vb = null;
+            ib = null;
+            sb = null;
+        } else if (qName.equals("vertexbuffer")) {
+            fb = null;
+            vb = null;
+        } else if (qName.equals("geometry")
+                || qName.equals("sharedgeometry")) {
+            // finish writing to buffers
+            IntMap<VertexBuffer> bufs = mesh.getBuffers();
+            for (Entry<VertexBuffer> entry : bufs) {
+                Buffer data = entry.getValue().getData();
+                if (data.position() != 0) {
+                    data.flip();
+                }
+            }
+            mesh.updateBound();
+            mesh.setStatic();
+
+            if (qName.equals("sharedgeometry")) {
+                geom = null;
+                mesh = null;
+            }
+        } else if (qName.equals("lodfacelist")) {
+            sb.flip();
+            vb = null;
+            sb = null;
+        } else if (qName.equals("levelofdetail")) {
+            endLevelOfDetail();
+        } else if (qName.equals("boneassignments")) {
+            endBoneAssigns();
+        }
+    }
+
+    @Override
+    public void characters(char ch[], int start, int length) {
+    }
+
+    private Node compileModel() {
+        Node model = new Node(meshName + "-ogremesh");
+
+        for (int i = 0; i < geoms.size(); i++) {
+            Geometry g = geoms.get(i);
+            Mesh m = g.getMesh();
+            
+            // New code for buffer extract
+            if (sharedMesh != null && usesSharedMesh.get(i)) {
+                m.extractVertexData(sharedMesh);
+            }
+            
+            // Old code for buffer sharer
+            //if (sharedMesh != null && isUsingSharedVerts(g)) {
+            //    m.setBound(sharedMesh.getBound().clone());
+            //}
+            model.attachChild(geoms.get(i));
+        }
+
+        // Do not attach shared geometry to the node!
+
+        if (animData != null) {
+            // This model uses animation
+
+            // Old code for buffer sharer
+            // generate bind pose for mesh
+            // ONLY if not using shared geometry
+            // This includes the shared geoemtry itself actually
+            //if (sharedMesh != null) {
+            //    sharedMesh.generateBindPose(!HARDWARE_SKINNING);
+            //}
+
+            for (int i = 0; i < geoms.size(); i++) {
+                Geometry g = geoms.get(i);
+                Mesh m = geoms.get(i).getMesh();
+                
+                m.generateBindPose(!HARDWARE_SKINNING);
+                
+                // Old code for buffer sharer
+                //boolean useShared = isUsingSharedVerts(g);
+                //if (!useShared) {
+                    // create bind pose
+                    //m.generateBindPose(!HARDWARE_SKINNING);
+                //}
+            }
+
+            // Put the animations in the AnimControl
+            HashMap<String, Animation> anims = new HashMap<String, Animation>();
+            ArrayList<Animation> animList = animData.anims;
+            for (int i = 0; i < animList.size(); i++) {
+                Animation anim = animList.get(i);
+                anims.put(anim.getName(), anim);
+            }
+
+            AnimControl ctrl = new AnimControl(animData.skeleton);
+            ctrl.setAnimations(anims);
+            model.addControl(ctrl);
+
+            // Put the skeleton in the skeleton control
+            SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton);
+
+            // This will acquire the targets from the node
+            model.addControl(skeletonControl);
+        }
+
+        return model;
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        try {
+            key = info.getKey();
+            meshName = key.getName();
+            folderName = key.getFolder();
+            String ext = key.getExtension();
+            meshName = meshName.substring(0, meshName.length() - ext.length() - 1);
+            if (folderName != null && folderName.length() > 0) {
+                meshName = meshName.substring(folderName.length());
+            }
+            assetManager = info.getManager();
+
+            if (key instanceof OgreMeshKey) {
+                // OgreMeshKey is being used, try getting the material list
+                // from it
+                OgreMeshKey meshKey = (OgreMeshKey) key;
+                materialList = meshKey.getMaterialList();
+                String materialName = meshKey.getMaterialName();
+                
+                // Material list not set but material name is available
+                if (materialList == null && materialName != null) {
+                    OgreMaterialKey materialKey = new OgreMaterialKey(folderName + materialName + ".material");
+                    try {
+                        materialList = (MaterialList) assetManager.loadAsset(materialKey);
+                    } catch (AssetNotFoundException e) {
+                        logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key});
+                    }
+                }
+            }else{
+                // Make sure to reset it to null so that previous state
+                // doesn't leak onto this one
+                materialList = null;
+            }
+
+            // If for some reason material list could not be found through
+            // OgreMeshKey, or if regular ModelKey specified, load using 
+            // default method.
+            if (materialList == null){
+                OgreMaterialKey materialKey = new OgreMaterialKey(folderName + meshName + ".material");
+                try {
+                    materialList = (MaterialList) assetManager.loadAsset(materialKey);
+                } catch (AssetNotFoundException e) {
+                    logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{ materialKey, key });
+                }
+            }
+            
+            // Added by larynx 25.06.2011
+            // Android needs the namespace aware flag set to true                 
+            // Kirill 30.06.2011
+            // Now, hack is applied for both desktop and android to avoid
+            // checking with JmeSystem.
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            
+            XMLReader xr = factory.newSAXParser().getXMLReader();
+            xr.setContentHandler(this);
+            xr.setErrorHandler(this);
+            
+            InputStreamReader r = null;
+            try {
+                r = new InputStreamReader(info.openStream());
+                xr.parse(new InputSource(r));
+            } finally {
+                if (r != null){
+                    r.close();
+                }
+            }
+            
+            return compileModel();
+        } catch (SAXException ex) {
+            IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
+            ioEx.initCause(ex);
+            throw ioEx;
+        } catch (ParserConfigurationException ex) {
+            IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
+            ioEx.initCause(ex);
+            throw ioEx;
+        }
+
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/OgreMeshKey.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/OgreMeshKey.java
new file mode 100644
index 0000000..1141944
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/OgreMeshKey.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.asset.ModelKey;
+import com.jme3.material.MaterialList;
+
+/**
+ * OgreMeshKey is used to load Ogre3D mesh.xml models with a specific
+ * material file or list. This allows customizing from where the materials
+ * are retrieved, instead of loading the material file as the same
+ * name as the model (the default).
+ * 
+ * @author Kirill Vainer
+ */
+public class OgreMeshKey extends ModelKey {
+
+    private MaterialList materialList;
+    private String materialName;
+
+    public OgreMeshKey(){
+        super();
+    }
+
+    public OgreMeshKey(String name){
+        super(name);
+    }
+    
+    public OgreMeshKey(String name, MaterialList materialList){
+        super(name);
+        this.materialList = materialList;
+    }
+    
+    public OgreMeshKey(String name, String materialName){
+        super(name);
+        this.materialName = materialName;
+    }
+
+    public MaterialList getMaterialList() {
+        return materialList;
+    }
+    
+    public void setMaterialList(MaterialList materialList){
+        this.materialList = materialList;
+    }
+    
+    public String getMaterialName() {
+        return materialName;
+    }
+    
+    public void setMaterialName(String name) {
+        materialName = name;
+    }
+
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java
new file mode 100644
index 0000000..b59275e
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.asset.*;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.MaterialList;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
+import com.jme3.util.PlaceholderAssets;
+import com.jme3.util.xml.SAXUtil;
+import static com.jme3.util.xml.SAXUtil.*;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class SceneLoader extends DefaultHandler implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
+
+    private Stack<String> elementStack = new Stack<String>();
+    private AssetKey key;
+    private String sceneName;
+    private String folderName;
+    private AssetManager assetManager;
+    private MaterialList materialList;
+    private com.jme3.scene.Node root;
+    private com.jme3.scene.Node node;
+    private com.jme3.scene.Node entityNode;
+    private Light light;
+    private int nodeIdx = 0;
+    private static volatile int sceneIdx = 0;
+
+    public SceneLoader(){
+        super();
+    }
+
+    @Override
+    public void startDocument() {
+    }
+
+    @Override
+    public void endDocument() {
+    }
+    
+    private void reset(){
+        elementStack.clear();
+        nodeIdx = 0;
+        
+        // NOTE: Setting some of those to null is only needed
+        // if the parsed file had an error e.g. startElement was called
+        // but not endElement
+        root = null;
+        node = null;
+        entityNode = null;
+        light = null;
+    }
+
+    private void checkTopNode(String topNode) throws SAXException{
+        if (!elementStack.peek().equals(topNode)){
+            throw new SAXException("dotScene parse error: Expected parent node to be " + topNode);
+        }
+    }
+    
+    private Quaternion parseQuat(Attributes attribs) throws SAXException{
+        if (attribs.getValue("x") != null){
+            // defined as quaternion
+            float x = parseFloat(attribs.getValue("x"));
+            float y = parseFloat(attribs.getValue("y"));
+            float z = parseFloat(attribs.getValue("z"));
+            float w = parseFloat(attribs.getValue("w"));
+            return new Quaternion(x,y,z,w);
+        }else if (attribs.getValue("qx") != null){
+            // defined as quaternion with prefix "q"
+            float x = parseFloat(attribs.getValue("qx"));
+            float y = parseFloat(attribs.getValue("qy"));
+            float z = parseFloat(attribs.getValue("qz"));
+            float w = parseFloat(attribs.getValue("qw"));
+            return new Quaternion(x,y,z,w);
+        }else if (attribs.getValue("angle") != null){
+            // defined as angle + axis
+            float angle = parseFloat(attribs.getValue("angle"));
+            float axisX = parseFloat(attribs.getValue("axisX"));
+            float axisY = parseFloat(attribs.getValue("axisY"));
+            float axisZ = parseFloat(attribs.getValue("axisZ"));
+            Quaternion q = new Quaternion();
+            q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ));
+            return q;
+        }else{
+            // defines as 3 angles along XYZ axes
+            float angleX = parseFloat(attribs.getValue("angleX"));
+            float angleY = parseFloat(attribs.getValue("angleY"));
+            float angleZ = parseFloat(attribs.getValue("angleZ"));
+            Quaternion q = new Quaternion();
+            q.fromAngles(angleX, angleY, angleZ);
+            return q;
+        }
+    }
+
+    private void parseLightNormal(Attributes attribs) throws SAXException {
+        checkTopNode("light");
+        
+        // SpotLight will be supporting a direction-normal, too.
+        if (light instanceof DirectionalLight)
+            ((DirectionalLight) light).setDirection(parseVector3(attribs));
+        else if (light instanceof SpotLight){
+            ((SpotLight) light).setDirection(parseVector3(attribs));
+        }
+    }
+
+    private void parseLightAttenuation(Attributes attribs) throws SAXException {
+        // NOTE: Derives range based on "linear" if it is used solely
+        // for the attenuation. Otherwise derives it from "range"
+        checkTopNode("light");
+
+        if (light instanceof PointLight || light instanceof SpotLight){
+            float range = parseFloat(attribs.getValue("range"));
+            float constant = parseFloat(attribs.getValue("constant"));
+            float linear = parseFloat(attribs.getValue("linear"));
+
+            String quadraticStr = attribs.getValue("quadratic");
+            if (quadraticStr == null)
+                quadraticStr = attribs.getValue("quadric");
+
+            float quadratic = parseFloat(quadraticStr);
+            
+            if (constant == 1 && quadratic == 0 && linear > 0){
+                range = 1f / linear;
+            }
+            
+            if (light instanceof PointLight){
+                ((PointLight) light).setRadius(range);
+            }else{
+                ((SpotLight)light).setSpotRange(range);
+            }
+        }
+    }
+
+    private void parseLightSpotLightRange(Attributes attribs) throws SAXException{
+        checkTopNode("light");
+        
+        float outer = SAXUtil.parseFloat(attribs.getValue("outer"));
+        float inner = SAXUtil.parseFloat(attribs.getValue("inner"));
+        
+        if (!(light instanceof SpotLight)){
+            throw new SAXException("dotScene parse error: spotLightRange "
+                    + "can only appear under 'spot' light elements");
+        }
+        
+        SpotLight sl = (SpotLight) light;
+        sl.setSpotInnerAngle(inner * 0.5f);
+        sl.setSpotOuterAngle(outer * 0.5f);
+    }
+    
+    private void parseLight(Attributes attribs) throws SAXException {
+        if (node == null || node.getParent() == null)
+            throw new SAXException("dotScene parse error: light can only appear under a node");
+        
+        checkTopNode("node");
+        
+        String lightType = parseString(attribs.getValue("type"), "point");
+        if(lightType.equals("point")) {
+            light = new PointLight();
+        } else if(lightType.equals("directional") || lightType.equals("sun")) {
+            light = new DirectionalLight();
+            // Assuming "normal" property is not provided
+            ((DirectionalLight)light).setDirection(Vector3f.UNIT_Z);
+        } else if(lightType.equals("spotLight") || lightType.equals("spot")) {
+            light = new SpotLight();
+        } else {
+            logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType);
+        }
+        logger.log(Level.FINEST, "{0} created.", light);
+
+        if (!parseBool(attribs.getValue("visible"), true)){
+            // set to disabled
+        }
+
+        // "attach" it to the parent of this node
+        if (light != null)
+            node.getParent().addLight(light);
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
+        if (qName.equals("scene")){
+            if (elementStack.size() != 0){
+                throw new SAXException("dotScene parse error: 'scene' element must be the root XML element");
+            }
+            
+            String version = attribs.getValue("formatVersion");
+            if (version == null || (!version.equals("1.0.0") && !version.equals("1.0.1")))
+                logger.log(Level.WARNING, "Unrecognized version number"
+                        + " in dotScene file: {0}", version);
+            
+        }else if (qName.equals("nodes")){
+            if (root != null){
+                throw new SAXException("dotScene parse error: nodes element was specified twice");
+            }
+            if (sceneName == null)
+                root = new com.jme3.scene.Node("OgreDotScene"+(++sceneIdx));
+            else
+                root = new com.jme3.scene.Node(sceneName+"-scene_node");
+            
+            node = root;
+        }else if (qName.equals("externals")){
+            checkTopNode("scene");
+            // Not loaded currently
+        }else if (qName.equals("item")){
+            checkTopNode("externals");
+        }else if (qName.equals("file")){
+            checkTopNode("item");
+            
+            // XXX: Currently material file name is based
+            // on the scene's filename. THIS IS NOT CORRECT.
+            // To solve, port SceneLoader to use DOM instead of SAX
+            
+            //String matFile = folderName+attribs.getValue("name");
+            //try {
+            //    materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
+            //} catch (AssetNotFoundException ex){
+            //    materialList = null;
+            //    logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
+            //}
+        }else if (qName.equals("node")){
+            String curElement = elementStack.peek();
+            if (!curElement.equals("node") && !curElement.equals("nodes")){
+                throw new SAXException("dotScene parse error: "
+                        + "node element can only appear under 'node' or 'nodes'");
+            }
+            
+            String name = attribs.getValue("name");
+            if (name == null)
+                name = "OgreNode-" + (++nodeIdx);
+
+            com.jme3.scene.Node newNode = new com.jme3.scene.Node(name);
+            if (node != null){
+                node.attachChild(newNode);
+            }
+            node = newNode;
+        }else if (qName.equals("property")){
+            if (node != null){
+                String type = attribs.getValue("type");
+                String name = attribs.getValue("name");
+                String data = attribs.getValue("data");
+                if (type.equals("BOOL")){
+                    node.setUserData(name, Boolean.parseBoolean(data)||data.equals("1"));
+                }else if (type.equals("FLOAT")){
+                    node.setUserData(name, Float.parseFloat(data));
+                }else if (type.equals("STRING")){
+                    node.setUserData(name, data);
+                }else if (type.equals("INT")){
+                    node.setUserData(name, Integer.parseInt(data));
+                }
+            }
+        }else if (qName.equals("entity")){
+            checkTopNode("node");
+            
+            String name = attribs.getValue("name");
+            if (name == null)
+                name = "OgreEntity-" + (++nodeIdx);
+            else
+                name += "-entity";
+
+            String meshFile = attribs.getValue("meshFile");
+            if (meshFile == null) {
+                throw new SAXException("Required attribute 'meshFile' missing for 'entity' node");
+            }
+
+            // TODO: Not currently used
+            String materialName = attribs.getValue("materialName");
+
+            if (folderName != null) {
+                meshFile = folderName + meshFile;
+            }
+            
+            // NOTE: append "xml" since its assumed mesh files are binary in dotScene
+            meshFile += ".xml";
+            
+            entityNode = new com.jme3.scene.Node(name);
+            OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList);
+            try {
+                Spatial ogreMesh = assetManager.loadModel(meshKey);
+                entityNode.attachChild(ogreMesh);
+            } catch (AssetNotFoundException ex){
+                logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key});
+                // Attach placeholder asset.
+                entityNode.attachChild(PlaceholderAssets.getPlaceholderModel(assetManager));
+            }
+            
+            node.attachChild(entityNode);
+            node = null;
+        }else if (qName.equals("position")){
+            if (elementStack.peek().equals("node")){
+                node.setLocalTranslation(SAXUtil.parseVector3(attribs));
+            }
+        }else if (qName.equals("quaternion") || qName.equals("rotation")){
+            node.setLocalRotation(parseQuat(attribs));
+        }else if (qName.equals("scale")){
+            node.setLocalScale(SAXUtil.parseVector3(attribs));
+        } else if (qName.equals("light")) {
+            parseLight(attribs);
+        } else if (qName.equals("colourDiffuse") || qName.equals("colorDiffuse")) {
+            if (elementStack.peek().equals("light")){
+                if (light != null){
+                    light.setColor(parseColor(attribs));
+                }
+            }else{
+                checkTopNode("environment");
+            }
+        } else if (qName.equals("normal") || qName.equals("direction")) {
+            checkTopNode("light");
+            parseLightNormal(attribs);
+        } else if (qName.equals("lightAttenuation")) {
+            parseLightAttenuation(attribs);
+        } else if (qName.equals("spotLightRange") || qName.equals("lightRange")) {
+            parseLightSpotLightRange(attribs);
+        }
+
+        elementStack.push(qName);
+    }
+
+    @Override
+    public void endElement(String uri, String name, String qName) throws SAXException {
+        if (qName.equals("node")){
+            node = node.getParent();
+        }else if (qName.equals("nodes")){
+            node = null;
+        }else if (qName.equals("entity")){
+            node = entityNode.getParent();
+            entityNode = null;
+        }else if (qName.equals("light")){
+            // apply the node's world transform on the light..
+            root.updateGeometricState();
+            if (light != null){
+                if (light instanceof DirectionalLight){
+                    DirectionalLight dl = (DirectionalLight) light;
+                    Quaternion q = node.getWorldRotation();
+                    Vector3f dir = dl.getDirection();
+                    q.multLocal(dir);
+                    dl.setDirection(dir);
+                }else if (light instanceof PointLight){
+                    PointLight pl = (PointLight) light;
+                    Vector3f pos = node.getWorldTranslation();
+                    pl.setPosition(pos);
+                }else if (light instanceof SpotLight){
+                    SpotLight sl = (SpotLight) light;
+                    
+                    Vector3f pos = node.getWorldTranslation();
+                    sl.setPosition(pos);
+                    
+                    Quaternion q = node.getWorldRotation();
+                    Vector3f dir = sl.getDirection();
+                    q.multLocal(dir);
+                    sl.setDirection(dir);
+                }
+            }
+            light = null;
+        }
+        checkTopNode(qName);
+        elementStack.pop();
+    }
+
+    @Override
+    public void characters(char ch[], int start, int length) {
+    }
+
+    
+    
+    public Object load(AssetInfo info) throws IOException {
+        try{
+            key = info.getKey();
+            assetManager = info.getManager();
+            sceneName = key.getName();
+            String ext = key.getExtension();
+            folderName = key.getFolder();
+            sceneName = sceneName.substring(0, sceneName.length() - ext.length() - 1);
+
+            OgreMaterialKey materialKey = new OgreMaterialKey(sceneName+".material");
+            try {
+                materialList = (MaterialList) assetManager.loadAsset(materialKey);
+            } catch (AssetNotFoundException ex){
+                logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{materialKey, key});
+                materialList = null;
+            }
+
+            reset();
+            
+            // Added by larynx 25.06.2011
+            // Android needs the namespace aware flag set to true 
+            // Kirill 30.06.2011
+            // Now, hack is applied for both desktop and android to avoid
+            // checking with JmeSystem.
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            XMLReader xr = factory.newSAXParser().getXMLReader();  
+            
+            xr.setContentHandler(this);
+            xr.setErrorHandler(this);
+            
+            InputStreamReader r = null;
+            
+            try {
+                r = new InputStreamReader(info.openStream());
+                xr.parse(new InputSource(r));
+            } finally {
+                if (r != null){
+                    r.close();
+                }
+            }
+            
+            return root;
+        }catch (SAXException ex){
+            IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
+            ioEx.initCause(ex);
+            throw ioEx;
+        } catch (ParserConfigurationException ex) {
+            IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
+            ioEx.initCause(ex);
+            throw ioEx;
+        }
+    }
+
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/SkeletonLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/SkeletonLoader.java
new file mode 100644
index 0000000..c96a7b7
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/SkeletonLoader.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.ogre;
+
+import com.jme3.animation.Animation;
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.util.xml.SAXUtil;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+import java.util.logging.Logger;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class SkeletonLoader extends DefaultHandler implements AssetLoader {
+
+    private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
+    private AssetManager assetManager;
+    private Stack<String> elementStack = new Stack<String>();
+    private HashMap<Integer, Bone> indexToBone = new HashMap<Integer, Bone>();
+    private HashMap<String, Bone> nameToBone = new HashMap<String, Bone>();
+    private BoneTrack track;
+    private ArrayList<BoneTrack> tracks = new ArrayList<BoneTrack>();
+    private Animation animation;
+    private ArrayList<Animation> animations;
+    private Bone bone;
+    private Skeleton skeleton;
+    private ArrayList<Float> times = new ArrayList<Float>();
+    private ArrayList<Vector3f> translations = new ArrayList<Vector3f>();
+    private ArrayList<Quaternion> rotations = new ArrayList<Quaternion>();
+    private ArrayList<Vector3f> scales = new ArrayList<Vector3f>();
+    private float time = -1;
+    private Vector3f position;
+    private Quaternion rotation;
+    private Vector3f scale;
+    private float angle;
+    private Vector3f axis;
+
+    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
+        if (qName.equals("position") || qName.equals("translate")) {
+            position = SAXUtil.parseVector3(attribs);
+        } else if (qName.equals("rotation") || qName.equals("rotate")) {
+            angle = SAXUtil.parseFloat(attribs.getValue("angle"));
+        } else if (qName.equals("axis")) {
+            assert elementStack.peek().equals("rotation")
+                    || elementStack.peek().equals("rotate");
+            axis = SAXUtil.parseVector3(attribs);
+        } else if (qName.equals("scale")) {
+            scale = SAXUtil.parseVector3(attribs);
+        } else if (qName.equals("keyframe")) {
+            assert elementStack.peek().equals("keyframes");
+            time = SAXUtil.parseFloat(attribs.getValue("time"));
+        } else if (qName.equals("keyframes")) {
+            assert elementStack.peek().equals("track");
+        } else if (qName.equals("track")) {
+            assert elementStack.peek().equals("tracks");
+            String boneName = SAXUtil.parseString(attribs.getValue("bone"));
+            Bone bone = nameToBone.get(boneName);
+            int index = skeleton.getBoneIndex(bone);
+            track = new BoneTrack(index);
+        } else if (qName.equals("boneparent")) {
+            assert elementStack.peek().equals("bonehierarchy");
+            String boneName = attribs.getValue("bone");
+            String parentName = attribs.getValue("parent");
+            Bone bone = nameToBone.get(boneName);
+            Bone parent = nameToBone.get(parentName);
+            parent.addChild(bone);
+        } else if (qName.equals("bone")) {
+            assert elementStack.peek().equals("bones");
+
+            // insert bone into indexed map
+            bone = new Bone(attribs.getValue("name"));
+            int id = SAXUtil.parseInt(attribs.getValue("id"));
+            indexToBone.put(id, bone);
+            nameToBone.put(bone.getName(), bone);
+        } else if (qName.equals("tracks")) {
+            assert elementStack.peek().equals("animation");
+            tracks.clear();
+        } else if (qName.equals("animation")) {
+            assert elementStack.peek().equals("animations");
+            String name = SAXUtil.parseString(attribs.getValue("name"));
+            float length = SAXUtil.parseFloat(attribs.getValue("length"));
+            animation = new Animation(name, length);
+        } else if (qName.equals("bonehierarchy")) {
+            assert elementStack.peek().equals("skeleton");
+        } else if (qName.equals("animations")) {
+            assert elementStack.peek().equals("skeleton");
+            animations = new ArrayList<Animation>();
+        } else if (qName.equals("bones")) {
+            assert elementStack.peek().equals("skeleton");
+        } else if (qName.equals("skeleton")) {
+            assert elementStack.size() == 0;
+        }
+        elementStack.add(qName);
+    }
+
+    public void endElement(String uri, String name, String qName) {
+        if (qName.equals("translate") || qName.equals("position") || qName.equals("scale")) {
+        } else if (qName.equals("axis")) {
+        } else if (qName.equals("rotate") || qName.equals("rotation")) {
+            rotation = new Quaternion();
+            axis.normalizeLocal();
+            rotation.fromAngleNormalAxis(angle, axis);
+            angle = 0;
+            axis = null;
+        } else if (qName.equals("bone")) {
+            bone.setBindTransforms(position, rotation, scale);
+            bone = null;
+            position = null;
+            rotation = null;
+            scale = null;
+        } else if (qName.equals("bonehierarchy")) {
+            Bone[] bones = new Bone[indexToBone.size()];
+            // find bones without a parent and attach them to the skeleton
+            // also assign the bones to the bonelist
+            for (Map.Entry<Integer, Bone> entry : indexToBone.entrySet()) {
+                Bone bone = entry.getValue();
+                bones[entry.getKey()] = bone;
+            }
+            indexToBone.clear();
+            skeleton = new Skeleton(bones);
+        } else if (qName.equals("animation")) {
+            animations.add(animation);
+            animation = null;
+        } else if (qName.equals("track")) {
+            if (track != null) { // if track has keyframes
+                tracks.add(track);
+                track = null;
+            }
+        } else if (qName.equals("tracks")) {
+            BoneTrack[] trackList = tracks.toArray(new BoneTrack[tracks.size()]);
+            animation.setTracks(trackList);
+            tracks.clear();
+        } else if (qName.equals("keyframe")) {
+            assert time >= 0;
+            assert position != null;
+            assert rotation != null;
+
+            times.add(time);
+            translations.add(position);
+            rotations.add(rotation);
+            if (scale != null) {
+                scales.add(scale);
+            }else{
+                scales.add(new Vector3f(1,1,1));
+            }
+
+            time = -1;
+            position = null;
+            rotation = null;
+            scale = null;
+        } else if (qName.equals("keyframes")) {
+            if (times.size() > 0) {
+                float[] timesArray = new float[times.size()];
+                for (int i = 0; i < timesArray.length; i++) {
+                    timesArray[i] = times.get(i);
+                }
+
+                Vector3f[] transArray = translations.toArray(new Vector3f[translations.size()]);
+                Quaternion[] rotArray = rotations.toArray(new Quaternion[rotations.size()]);
+                Vector3f[] scalesArray = scales.toArray(new Vector3f[scales.size()]);
+                
+                track.setKeyframes(timesArray, transArray, rotArray, scalesArray);
+                //track.setKeyframes(timesArray, transArray, rotArray);
+            } else {
+                track = null;
+            }
+
+            times.clear();
+            translations.clear();
+            rotations.clear();
+            scales.clear();
+        } else if (qName.equals("skeleton")) {
+            nameToBone.clear();
+        }
+        assert elementStack.peek().equals(qName);
+        elementStack.pop();
+    }
+
+    /**
+     * Reset the SkeletonLoader in case an error occured while parsing XML.
+     * This allows future use of the loader even after an error.
+     */
+    private void fullReset() {
+        elementStack.clear();
+        indexToBone.clear();
+        nameToBone.clear();
+        track = null;
+        tracks.clear();
+        animation = null;
+        if (animations != null) {
+            animations.clear();
+        }
+
+        bone = null;
+        skeleton = null;
+        times.clear();
+        rotations.clear();
+        translations.clear();
+        time = -1;
+        position = null;
+        rotation = null;
+        scale = null;
+        angle = 0;
+        axis = null;
+    }
+
+    public Object load(InputStream in) throws IOException {
+        try {
+            
+            // Added by larynx 25.06.2011
+            // Android needs the namespace aware flag set to true 
+            // Kirill 30.06.2011
+            // Now, hack is applied for both desktop and android to avoid
+            // checking with JmeSystem.
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            XMLReader xr = factory.newSAXParser().getXMLReader();  
+                         
+            xr.setContentHandler(this);
+            xr.setErrorHandler(this);
+            InputStreamReader r = new InputStreamReader(in);
+            xr.parse(new InputSource(r));
+            if (animations == null) {
+                animations = new ArrayList<Animation>();
+            }
+            AnimData data = new AnimData(skeleton, animations);
+            skeleton = null;
+            animations = null;
+            return data;
+        } catch (SAXException ex) {
+            IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
+            ioEx.initCause(ex);
+            fullReset();
+            throw ioEx;
+        } catch (ParserConfigurationException ex) {
+            IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
+            ioEx.initCause(ex);
+            fullReset();
+            throw ioEx;
+        }
+        
+    }
+
+    public Object load(AssetInfo info) throws IOException {
+        assetManager = info.getManager();
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            return load(in);
+        } finally {
+            if (in != null){
+                in.close();
+            }
+        }
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java
new file mode 100644
index 0000000..d68b7ae
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre.matext;
+
+import java.util.HashMap;
+
+/**
+ * <code>MaterialExtension</code> defines a mapping from an Ogre3D "base" material
+ * to a jME3 material definition.
+ */
+public class MaterialExtension {
+
+    private String baseMatName;
+    private String jmeMatDefName;
+    private HashMap<String, String> textureMappings = new HashMap<String, String>();
+
+    /**
+     * Material extension defines a mapping from an Ogre3D "base" material
+     * to a jME3 material definition.
+     *
+     * @param baseMatName The base material name for Ogre3D
+     * @param jmeMatDefName The material definition name for jME3
+     */
+    public MaterialExtension(String baseMatName, String jmeMatDefName) {
+        this.baseMatName = baseMatName;
+        this.jmeMatDefName = jmeMatDefName;
+    }
+
+    public String getBaseMaterialName() {
+        return baseMatName;
+    }
+
+    public String getJmeMatDefName() {
+        return jmeMatDefName;
+    }
+
+    /**
+     * Set mapping from an Ogre3D base material texture alias to a
+     * jME3 texture param
+     * @param ogreTexAlias The texture alias in the Ogre3D base material
+     * @param jmeTexParam The texture param name in the jME3 material definition.
+     */
+    public void setTextureMapping(String ogreTexAlias, String jmeTexParam){
+        textureMappings.put(ogreTexAlias, jmeTexParam);
+    }
+
+    /**
+     * Retreives a mapping from an Ogre3D base material texture alias
+     * to a jME3 texture param
+     * @param ogreTexAlias The texture alias in the Ogre3D base material
+     * @return The texture alias in the Ogre3D base material
+     */
+    public String getTextureMapping(String ogreTexAlias){
+        return textureMappings.get(ogreTexAlias);
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java
new file mode 100644
index 0000000..d497d51
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre.matext;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.scene.plugins.ogre.MaterialLoader;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.PlaceholderAssets;
+import com.jme3.util.blockparser.Statement;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Used internally by {@link MaterialLoader}
+ */
+public class MaterialExtensionLoader {
+
+    private static final Logger logger = Logger.getLogger(MaterialExtensionLoader.class.getName());
+
+    private AssetKey key;
+    private AssetManager assetManager;
+    private MaterialList list;
+    private MaterialExtensionSet matExts;
+    private MaterialExtension matExt;
+    private String matName;
+    private Material material;
+
+    
+    private void readExtendingMaterialStatement(Statement statement) throws IOException {
+        if (statement.getLine().startsWith("set_texture_alias")){
+            String[] split = statement.getLine().split(" ", 3);
+            String aliasName = split[1];
+            String texturePath = split[2];
+
+            String jmeParamName = matExt.getTextureMapping(aliasName);
+
+            TextureKey texKey = new TextureKey(texturePath, false);
+            texKey.setGenerateMips(true);
+            texKey.setAsCube(false);
+            Texture tex;
+            
+            try {
+                tex = assetManager.loadTexture(texKey);
+                tex.setWrap(WrapMode.Repeat);
+            } catch (AssetNotFoundException ex){
+                logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
+                tex = new Texture2D( PlaceholderAssets.getPlaceholderImage() );
+            }
+            
+            material.setTexture(jmeParamName, tex);
+        }
+    }
+
+    private Material readExtendingMaterial(Statement statement) throws IOException{
+        String[] split = statement.getLine().split(" ", 2);
+        String[] subsplit = split[1].split(":");
+        matName = subsplit[0].trim();
+        String extendedMat = subsplit[1].trim();
+
+        matExt = matExts.getMaterialExtension(extendedMat);
+        if (matExt == null){
+            logger.log(Level.WARNING, "Cannot find MaterialExtension for: {0}. Ignoring material.", extendedMat);
+            matExt = null;
+            return null;
+        }
+
+        material = new Material(assetManager, matExt.getJmeMatDefName());
+        for (Statement extMatStat : statement.getContents()){
+            readExtendingMaterialStatement(extMatStat);
+        }
+        return material;
+    }
+
+    public MaterialList load(AssetManager assetManager, AssetKey key, MaterialExtensionSet matExts,
+            List<Statement> statements) throws IOException{
+        this.assetManager = assetManager;
+        this.matExts = matExts;
+        this.key = key;
+        
+        list = new MaterialList();
+        
+        for (Statement statement : statements){
+            if (statement.getLine().startsWith("import")){
+                // ignore
+                continue;
+            }else if (statement.getLine().startsWith("material")){
+                Material material = readExtendingMaterial(statement);
+                list.put(matName, material);
+                List<String> matAliases = matExts.getNameMappings(matName);
+                if(matAliases != null){
+                    for (String string : matAliases) {
+                        list.put(string, material);
+                    }
+                }
+            }
+        }
+        return list;
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java
new file mode 100644
index 0000000..2c81e7b
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre.matext;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * <code>MaterialExtensionSet</code> is simply a container for several
+ * {@link MaterialExtension}s so that it can be set globally for all
+ * {@link OgreMaterialKey}s used.
+ */
+public class MaterialExtensionSet {
+    private HashMap<String, MaterialExtension> extensions
+            = new HashMap<String, MaterialExtension>();
+    private HashMap<String, List<String>> nameMappings = new HashMap<String, List<String>>();
+
+    /**
+     * Adds a new material extension to the set of extensions.
+     * @param extension The {@link MaterialExtension} to add.
+     */
+    public void addMaterialExtension(MaterialExtension extension){
+        extensions.put(extension.getBaseMaterialName(), extension);
+    }
+
+    /**
+     * Returns the {@link MaterialExtension} for a given Ogre3D base
+     * material name.
+     *
+     * @param baseMatName The ogre3D base material name.
+     * @return {@link MaterialExtension} that is set, or null if not set.
+     */
+    public MaterialExtension getMaterialExtension(String baseMatName){
+        return extensions.get(baseMatName);
+    }
+    
+    /**
+     * Adds an alternative name for a material
+     * @param name The material name to be found in a .mesh.xml file
+     * @param alias The material name to be found in a .material file
+     */
+    public void setNameMapping(String name, String alias){
+        List<String> list = nameMappings.get(name);
+        if(list==null){
+            list = new ArrayList<String>();
+            nameMappings.put(name, list);
+        }
+        list.add(alias);
+    }
+    
+    public List<String> getNameMappings(String name){
+        return nameMappings.get(name);
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java
new file mode 100644
index 0000000..fa57d48
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.ogre.matext;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.material.MaterialList;
+
+/**
+ * <code>OgreMaterialKey</code> allows specifying material extensions,
+ * which map from Ogre3D base materials to jME3 materials
+ */
+public class OgreMaterialKey extends AssetKey<MaterialList> {
+
+    private MaterialExtensionSet matExts;
+
+    public OgreMaterialKey(String name){
+        super(name);
+    }
+
+    public OgreMaterialKey(){
+        super();
+    }
+
+    /**
+     * Set the {@link MaterialExtensionSet} to use for mapping
+     * base materials to jME3 matdefs when loading.
+     * Set to <code>null</code> to disable this functionality.
+     *
+     * @param matExts The {@link MaterialExtensionSet} to use
+     */
+    public void setMaterialExtensionSet(MaterialExtensionSet matExts){
+        this.matExts = matExts;
+    }
+
+    /**
+     * Returns the {@link MaterialExtensionSet} previously set using
+     * {@link OgreMaterialKey#setMaterialExtensionSet(com.jme3.scene.plugins.ogre.matext.MaterialExtensionSet) } method.
+     * @return
+     */
+    public MaterialExtensionSet getMaterialExtensionSet() {
+        return matExts;
+    }
+}
diff --git a/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/package.html b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/package.html
new file mode 100644
index 0000000..0b2725c
--- /dev/null
+++ b/engine/src/ogre/com/jme3/scene/plugins/ogre/matext/package.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+<code>com.jme3.scene.plugins.ogre.matext</code> allows loading of more advanced
+Ogre3D materials that use "base" materials to abstract functionality.
+<br/><br/>
+E.g. example of an Ogre3D material instance:<br/>
+<code>
+import * from "materials/baselighting.material"
+
+material MyMaterial : BaseLightingMaterial
+{
+    set_texture_alias MyTexture textures/mytex.png
+}
+</code>
+
+<h3>Usage</h3>
+
+<p>
+Example code of loading the above material:<br/>
+<code>
+MaterialExtensionSet matExts = new MaterialExtensionSet();<br/>
+MaterialExtension baseLightExt = new MaterialExtension("BaseLightingMaterial", <br/>
+                                                       "Common/MatDefs/Light/Lighting.j3md");<br/>
+baseLightExt.setTextureMapping("MyTexture", "m_DiffuseMap");<br/>
+matExts.addMaterialExtension(baseLightExt);<br/>
+<br/>
+OgreMaterialKey matKey = new OgreMaterialKey("materials/mymaterial.material");<br/>
+matKey.setMaterialExtensionSet(matExts);<br/>
+MaterialList ogreMats = assetManager.loadAsset(matKey);<br/>
+</code>
+
+</body>
+</html>
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.frag b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.frag
new file mode 100644
index 0000000..b0b594c
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.frag
@@ -0,0 +1,76 @@
+uniform vec3 m_region1;

+uniform vec3 m_region2;

+uniform vec3 m_region3;

+uniform vec3 m_region4;

+

+uniform sampler2D m_region1ColorMap;

+uniform sampler2D m_region2ColorMap;

+uniform sampler2D m_region3ColorMap;

+uniform sampler2D m_region4ColorMap;

+uniform sampler2D m_slopeColorMap;

+

+uniform float m_slopeTileFactor;

+uniform float m_terrainSize;

+

+varying vec3 normal;

+varying vec4 position;

+

+vec4 GenerateTerrainColor() {

+    float height = position.y;

+    vec4 p = position / m_terrainSize;

+

+    vec3 blend = abs( normal );

+    blend = (blend -0.2) * 0.7;

+    blend = normalize(max(blend, 0.00001));      // Force weights to sum to 1.0 (very important!)

+    float b = (blend.x + blend.y + blend.z);

+    blend /= vec3(b, b, b);

+

+    vec4 terrainColor = vec4(0.0, 0.0, 0.0, 1.0);

+

+    float m_regionMin = 0.0;

+    float m_regionMax = 0.0;

+    float m_regionRange = 0.0;

+    float m_regionWeight = 0.0;

+

+ 	vec4 slopeCol1 = texture2D(m_slopeColorMap, p.yz * m_slopeTileFactor);

+ 	vec4 slopeCol2 = texture2D(m_slopeColorMap, p.xy * m_slopeTileFactor);

+

+    // Terrain m_region 1.

+    m_regionMin = m_region1.x;

+    m_regionMax = m_region1.y;

+    m_regionRange = m_regionMax - m_regionMin;

+    m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;

+    m_regionWeight = max(0.0, m_regionWeight);

+  	terrainColor += m_regionWeight * texture2D(m_region1ColorMap, p.xz * m_region1.z);

+

+    // Terrain m_region 2.

+    m_regionMin = m_region2.x;

+    m_regionMax = m_region2.y;

+    m_regionRange = m_regionMax - m_regionMin;

+    m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;

+    m_regionWeight = max(0.0, m_regionWeight);

+    terrainColor += m_regionWeight * (texture2D(m_region2ColorMap, p.xz * m_region2.z));

+

+    // Terrain m_region 3.

+    m_regionMin = m_region3.x;

+    m_regionMax = m_region3.y;

+    m_regionRange = m_regionMax - m_regionMin;

+    m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;

+    m_regionWeight = max(0.0, m_regionWeight);

+	terrainColor += m_regionWeight * texture2D(m_region3ColorMap, p.xz * m_region3.z);

+

+    // Terrain m_region 4.

+    m_regionMin = m_region4.x;

+    m_regionMax = m_region4.y;

+    m_regionRange = m_regionMax - m_regionMin;

+    m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange;

+    m_regionWeight = max(0.0, m_regionWeight);

+    terrainColor += m_regionWeight * texture2D(m_region4ColorMap, p.xz * m_region4.z);

+

+    return (blend.y * terrainColor + blend.x * slopeCol1 + blend.z * slopeCol2);

+}

+

+void main() {

+	vec4 color = GenerateTerrainColor();

+    gl_FragColor = color;

+}

diff --git a/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.j3md
new file mode 100644
index 0000000..4b836f0
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.j3md
@@ -0,0 +1,41 @@
+MaterialDef Terrain {

+

+        // Parameters to material:

+        // regionXColorMap: X = 1..4 the texture that should be appliad to state X

+        // regionX: a Vector3f containing the following information:

+        //      regionX.x: the start height of the region

+        //      regionX.y: the end height of the region

+        //      regionX.z: the texture scale for the region

+        //  it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)

+        // slopeColorMap: the texture to be used for cliffs, and steep mountain sites

+        // slopeTileFactor: the texture scale for slopes

+        // terrainSize: the total size of the terrain (used for scaling the texture)

+	MaterialParameters {

+		Texture2D region1ColorMap

+		Texture2D region2ColorMap

+		Texture2D region3ColorMap

+		Texture2D region4ColorMap

+		Texture2D slopeColorMap

+		Float slopeTileFactor

+		Float terrainSize

+		Vector3 region1

+		Vector3 region2

+		Vector3 region3

+		Vector3 region4

+	}

+

+	Technique {

+		VertexShader GLSL100:   Common/MatDefs/Terrain/HeightBasedTerrain.vert

+		FragmentShader GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.frag

+

+		WorldParameters {

+			WorldViewProjectionMatrix

+			WorldMatrix

+			NormalMatrix

+		}

+	}

+

+	Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.vert b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.vert
new file mode 100644
index 0000000..8260d8f
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/HeightBasedTerrain.vert
@@ -0,0 +1,22 @@
+uniform float m_tilingFactor;

+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldMatrix;

+uniform mat3 g_NormalMatrix;

+

+uniform float m_terrainSize;

+

+attribute vec4 inTexCoord;

+attribute vec3 inNormal;

+attribute vec3 inPosition;

+

+varying vec3 normal;

+varying vec4 position;

+

+void main()

+{

+ 	normal = normalize(inNormal);

+ 	position = g_WorldMatrix * vec4(inPosition, 0.0);

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1);

+}

+

+

diff --git a/engine/src/terrain/Common/MatDefs/Terrain/Terrain.frag b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.frag
new file mode 100644
index 0000000..7ae56cb
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.frag
@@ -0,0 +1,63 @@
+uniform sampler2D m_Alpha;

+uniform sampler2D m_Tex1;

+uniform sampler2D m_Tex2;

+uniform sampler2D m_Tex3;

+uniform float m_Tex1Scale;

+uniform float m_Tex2Scale;

+uniform float m_Tex3Scale;

+

+varying vec2 texCoord;

+

+#ifdef TRI_PLANAR_MAPPING

+  varying vec4 vVertex;

+  varying vec3 vNormal;

+#endif

+

+void main(void)

+{

+

+    // get the alpha value at this 2D texture coord

+    vec4 alpha   = texture2D( m_Alpha, texCoord.xy );

+

+#ifdef TRI_PLANAR_MAPPING

+    // tri-planar texture bending factor for this fragment's normal

+    vec3 blending = abs( vNormal );

+    blending = (blending -0.2) * 0.7;

+    blending = normalize(max(blending, 0.00001));      // Force weights to sum to 1.0 (very important!)

+    float b = (blending.x + blending.y + blending.z);

+    blending /= vec3(b, b, b);

+

+    // texture coords

+    vec4 coords = vVertex;

+

+    vec4 col1 = texture2D( m_Tex1, coords.yz * m_Tex1Scale );

+    vec4 col2 = texture2D( m_Tex1, coords.xz * m_Tex1Scale );

+    vec4 col3 = texture2D( m_Tex1, coords.xy * m_Tex1Scale );

+    // blend the results of the 3 planar projections.

+    vec4 tex1 = col1 * blending.x + col2 * blending.y + col3 * blending.z;

+

+    col1 = texture2D( m_Tex2, coords.yz * m_Tex2Scale );

+    col2 = texture2D( m_Tex2, coords.xz * m_Tex2Scale );

+    col3 = texture2D( m_Tex2, coords.xy * m_Tex2Scale );

+    // blend the results of the 3 planar projections.

+    vec4 tex2 = col1 * blending.x + col2 * blending.y + col3 * blending.z;

+

+    col1 = texture2D( m_Tex3, coords.yz * m_Tex3Scale );

+    col2 = texture2D( m_Tex3, coords.xz * m_Tex3Scale );

+    col3 = texture2D( m_Tex3, coords.xy * m_Tex3Scale );

+    // blend the results of the 3 planar projections.

+    vec4 tex3 = col1 * blending.x + col2 * blending.y + col3 * blending.z;

+

+#else

+	vec4 tex1    = texture2D( m_Tex1, texCoord.xy * m_Tex1Scale ); // Tile

+	vec4 tex2    = texture2D( m_Tex2, texCoord.xy * m_Tex2Scale ); // Tile

+	vec4 tex3    = texture2D( m_Tex3, texCoord.xy * m_Tex3Scale ); // Tile

+	

+#endif

+

+    vec4 outColor = tex1 * alpha.r; // Red channel

+	outColor = mix( outColor, tex2, alpha.g ); // Green channel

+	outColor = mix( outColor, tex3, alpha.b ); // Blue channel

+	gl_FragColor = outColor;

+}

+

diff --git a/engine/src/terrain/Common/MatDefs/Terrain/Terrain.j3md b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.j3md
new file mode 100644
index 0000000..152f511
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.j3md
@@ -0,0 +1,33 @@
+MaterialDef Terrain {

+

+	MaterialParameters {

+

+        // use tri-planar mapping

+        Boolean useTriPlanarMapping

+

+		Texture2D Alpha

+		Texture2D Tex1

+		Texture2D Tex2

+		Texture2D Tex3

+		Float Tex1Scale

+		Float Tex2Scale

+		Float Tex3Scale

+	}

+

+	Technique {

+		VertexShader GLSL100:   Common/MatDefs/Terrain/Terrain.vert

+		FragmentShader GLSL100: Common/MatDefs/Terrain/Terrain.frag

+		

+		WorldParameters {

+			WorldViewProjectionMatrix

+		}

+

+        Defines {

+            TRI_PLANAR_MAPPING : useTriPlanarMapping

+        }

+	}

+	

+	Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/Terrain.vert b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.vert
new file mode 100644
index 0000000..ddb40a9
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/Terrain.vert
@@ -0,0 +1,23 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inPosition;

+attribute vec3 inNormal;

+attribute vec2 inTexCoord;

+

+varying vec2 texCoord;

+

+#ifdef TRI_PLANAR_MAPPING

+  varying vec4 vVertex;

+  varying vec3 vNormal;

+#endif

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+    texCoord = inTexCoord;

+

+#ifdef TRI_PLANAR_MAPPING

+    vVertex = vec4(inPosition,0.0);

+    vNormal = inNormal;

+#endif

+

+}
\ No newline at end of file
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag
new file mode 100644
index 0000000..4d1d41a
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.frag
@@ -0,0 +1,625 @@
+

+uniform float m_Shininess;

+uniform vec4 g_LightDirection;

+

+varying vec4 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec4 SpecularSum;

+

+varying vec3 vNormal;

+varying vec2 texCoord;

+varying vec3 vPosition;

+varying vec3 vnPosition;

+varying vec3 vViewDir;

+varying vec4 vLightDir;

+varying vec4 vnLightDir;

+varying vec3 lightVec;

+

+

+#ifdef DIFFUSEMAP

+  uniform sampler2D m_DiffuseMap;

+#endif

+#ifdef DIFFUSEMAP_1

+  uniform sampler2D m_DiffuseMap_1;

+#endif

+#ifdef DIFFUSEMAP_2

+  uniform sampler2D m_DiffuseMap_2;

+#endif

+#ifdef DIFFUSEMAP_3

+  uniform sampler2D m_DiffuseMap_3;

+#endif

+#ifdef DIFFUSEMAP_4

+  uniform sampler2D m_DiffuseMap_4;

+#endif

+#ifdef DIFFUSEMAP_5

+  uniform sampler2D m_DiffuseMap_5;

+#endif

+#ifdef DIFFUSEMAP_6

+  uniform sampler2D m_DiffuseMap_6;

+#endif

+#ifdef DIFFUSEMAP_7

+  uniform sampler2D m_DiffuseMap_7;

+#endif

+#ifdef DIFFUSEMAP_8

+  uniform sampler2D m_DiffuseMap_8;

+#endif

+#ifdef DIFFUSEMAP_9

+  uniform sampler2D m_DiffuseMap_9;

+#endif

+#ifdef DIFFUSEMAP_10

+  uniform sampler2D m_DiffuseMap_10;

+#endif

+#ifdef DIFFUSEMAP_11

+  uniform sampler2D m_DiffuseMap_11;

+#endif

+

+

+#ifdef DIFFUSEMAP_0_SCALE

+  uniform float m_DiffuseMap_0_scale;

+#endif

+#ifdef DIFFUSEMAP_1_SCALE

+  uniform float m_DiffuseMap_1_scale;

+#endif

+#ifdef DIFFUSEMAP_2_SCALE

+  uniform float m_DiffuseMap_2_scale;

+#endif

+#ifdef DIFFUSEMAP_3_SCALE

+  uniform float m_DiffuseMap_3_scale;

+#endif

+#ifdef DIFFUSEMAP_4_SCALE

+  uniform float m_DiffuseMap_4_scale;

+#endif

+#ifdef DIFFUSEMAP_5_SCALE

+  uniform float m_DiffuseMap_5_scale;

+#endif

+#ifdef DIFFUSEMAP_6_SCALE

+  uniform float m_DiffuseMap_6_scale;

+#endif

+#ifdef DIFFUSEMAP_7_SCALE

+  uniform float m_DiffuseMap_7_scale;

+#endif

+#ifdef DIFFUSEMAP_8_SCALE

+  uniform float m_DiffuseMap_8_scale;

+#endif

+#ifdef DIFFUSEMAP_9_SCALE

+  uniform float m_DiffuseMap_9_scale;

+#endif

+#ifdef DIFFUSEMAP_10_SCALE

+  uniform float m_DiffuseMap_10_scale;

+#endif

+#ifdef DIFFUSEMAP_11_SCALE

+  uniform float m_DiffuseMap_11_scale;

+#endif

+

+

+#ifdef ALPHAMAP

+  uniform sampler2D m_AlphaMap;

+#endif

+#ifdef ALPHAMAP_1

+  uniform sampler2D m_AlphaMap_1;

+#endif

+#ifdef ALPHAMAP_2

+  uniform sampler2D m_AlphaMap_2;

+#endif

+

+#ifdef NORMALMAP

+  uniform sampler2D m_NormalMap;

+#endif

+#ifdef NORMALMAP_1

+  uniform sampler2D m_NormalMap_1;

+#endif

+#ifdef NORMALMAP_2

+  uniform sampler2D m_NormalMap_2;

+#endif

+#ifdef NORMALMAP_3

+  uniform sampler2D m_NormalMap_3;

+#endif

+#ifdef NORMALMAP_4

+  uniform sampler2D m_NormalMap_4;

+#endif

+#ifdef NORMALMAP_5

+  uniform sampler2D m_NormalMap_5;

+#endif

+#ifdef NORMALMAP_6

+  uniform sampler2D m_NormalMap_6;

+#endif

+#ifdef NORMALMAP_7

+  uniform sampler2D m_NormalMap_7;

+#endif

+#ifdef NORMALMAP_8

+  uniform sampler2D m_NormalMap_8;

+#endif

+#ifdef NORMALMAP_9

+  uniform sampler2D m_NormalMap_9;

+#endif

+#ifdef NORMALMAP_10

+  uniform sampler2D m_NormalMap_10;

+#endif

+#ifdef NORMALMAP_11

+  uniform sampler2D m_NormalMap_11;

+#endif

+

+

+#ifdef TRI_PLANAR_MAPPING

+  varying vec4 wVertex;

+  varying vec3 wNormal;

+#endif

+

+

+

+float tangDot(in vec3 v1, in vec3 v2){

+    float d = dot(v1,v2);

+    #ifdef V_TANGENT

+        d = 1.0 - d*d;

+        return step(0.0, d) * sqrt(d);

+    #else

+        return d;

+    #endif

+}

+

+

+float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){

+    return max(0.0, dot(norm, lightdir));

+}

+

+float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){

+    #ifdef WARDISO

+        // Isotropic Ward

+        vec3 halfVec = normalize(viewdir + lightdir);

+        float NdotH  = max(0.001, tangDot(norm, halfVec));

+        float NdotV  = max(0.001, tangDot(norm, viewdir));

+        float NdotL  = max(0.001, tangDot(norm, lightdir));

+        float a      = tan(acos(NdotH));

+        float p      = max(shiny/128.0, 0.001);

+        return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL)));

+    #else

+       // Standard Phong

+       vec3 R = reflect(-lightdir, norm);

+       return pow(max(tangDot(R, viewdir), 0.0), shiny);

+    #endif

+}

+

+vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){

+   float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);

+   float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);

+   specularFactor *= step(1.0, m_Shininess);

+

+   float att = vLightDir.w;

+

+   return vec2(diffuseFactor, specularFactor) * vec2(att);

+}

+

+

+#ifdef ALPHAMAP

+

+  vec4 calculateDiffuseBlend(in vec2 texCoord) {

+    vec4 alphaBlend   = texture2D( m_AlphaMap, texCoord.xy );

+    

+    #ifdef ALPHAMAP_1

+      vec4 alphaBlend1   = texture2D( m_AlphaMap_1, texCoord.xy );

+    #endif

+    #ifdef ALPHAMAP_2

+      vec4 alphaBlend2   = texture2D( m_AlphaMap_2, texCoord.xy );

+    #endif

+

+    vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord * m_DiffuseMap_0_scale);

+    diffuseColor *= alphaBlend.r;

+    #ifdef DIFFUSEMAP_1

+      vec4 diffuseColor1 = texture2D(m_DiffuseMap_1, texCoord * m_DiffuseMap_1_scale);

+      diffuseColor = mix( diffuseColor, diffuseColor1, alphaBlend.g );

+      #ifdef DIFFUSEMAP_2

+        vec4 diffuseColor2 = texture2D(m_DiffuseMap_2, texCoord * m_DiffuseMap_2_scale);

+        diffuseColor = mix( diffuseColor, diffuseColor2, alphaBlend.b );

+        #ifdef DIFFUSEMAP_3

+          vec4 diffuseColor3 = texture2D(m_DiffuseMap_3, texCoord * m_DiffuseMap_3_scale);

+          diffuseColor = mix( diffuseColor, diffuseColor3, alphaBlend.a );

+          #ifdef ALPHAMAP_1

+              #ifdef DIFFUSEMAP_4

+                vec4 diffuseColor4 = texture2D(m_DiffuseMap_4, texCoord * m_DiffuseMap_4_scale);

+                diffuseColor = mix( diffuseColor, diffuseColor4, alphaBlend1.r );

+                #ifdef DIFFUSEMAP_5

+                  vec4 diffuseColor5 = texture2D(m_DiffuseMap_5, texCoord * m_DiffuseMap_5_scale);

+                  diffuseColor = mix( diffuseColor, diffuseColor5, alphaBlend1.g );

+                  #ifdef DIFFUSEMAP_6

+                    vec4 diffuseColor6 = texture2D(m_DiffuseMap_6, texCoord * m_DiffuseMap_6_scale);

+                    diffuseColor = mix( diffuseColor, diffuseColor6, alphaBlend1.b );

+                    #ifdef DIFFUSEMAP_7

+                      vec4 diffuseColor7 = texture2D(m_DiffuseMap_7, texCoord * m_DiffuseMap_7_scale);

+                      diffuseColor = mix( diffuseColor, diffuseColor7, alphaBlend1.a );

+                      #ifdef ALPHAMAP_2

+                          #ifdef DIFFUSEMAP_8

+                            vec4 diffuseColor8 = texture2D(m_DiffuseMap_8, texCoord * m_DiffuseMap_8_scale);

+                            diffuseColor = mix( diffuseColor, diffuseColor8, alphaBlend2.r );

+                            #ifdef DIFFUSEMAP_9

+                              vec4 diffuseColor9 = texture2D(m_DiffuseMap_9, texCoord * m_DiffuseMap_9_scale);

+                              diffuseColor = mix( diffuseColor, diffuseColor9, alphaBlend2.g );

+                              #ifdef DIFFUSEMAP_10

+                                vec4 diffuseColor10 = texture2D(m_DiffuseMap_10, texCoord * m_DiffuseMap_10_scale);

+                                diffuseColor = mix( diffuseColor, diffuseColor10, alphaBlend2.b );

+                                #ifdef DIFFUSEMAP_11

+                                  vec4 diffuseColor11 = texture2D(m_DiffuseMap_11, texCoord * m_DiffuseMap_11_scale);

+                                  diffuseColor = mix( diffuseColor, diffuseColor11, alphaBlend2.a );

+                                #endif

+                              #endif

+                            #endif

+                          #endif

+                      #endif

+                    #endif

+                  #endif

+                #endif

+              #endif

+          #endif

+        #endif

+      #endif

+    #endif

+    return diffuseColor;

+  }

+

+  vec3 calculateNormal(in vec2 texCoord) {

+    vec3 normal = vec3(0,0,1);

+    vec3 n = vec3(0,0,0);

+

+    vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy );

+

+    #ifdef ALPHAMAP_1

+      vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy );

+    #endif

+    #ifdef ALPHAMAP_2

+      vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy );

+    #endif

+

+    #ifdef NORMALMAP

+      n = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale).xyz;

+      normal += n * alphaBlend.r;

+    #endif

+

+    #ifdef NORMALMAP_1

+      n = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale).xyz;

+      normal += n * alphaBlend.g;

+    #endif

+

+    #ifdef NORMALMAP_2

+      n = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale).xyz;

+      normal += n * alphaBlend.b;

+    #endif

+

+    #ifdef NORMALMAP_3

+      n = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale).xyz;

+      normal += n * alphaBlend.a;

+    #endif

+

+    #ifdef ALPHAMAP_1

+        #ifdef NORMALMAP_4

+          n = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale).xyz;

+          normal += n * alphaBlend1.r;

+        #endif

+

+        #ifdef NORMALMAP_5

+          n = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale).xyz;

+          normal += n * alphaBlend1.g;

+        #endif

+

+        #ifdef NORMALMAP_6

+          n = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale).xyz;

+          normal += n * alphaBlend1.b;

+        #endif

+

+        #ifdef NORMALMAP_7

+          n = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale).xyz;

+          normal += n * alphaBlend1.a;

+        #endif

+    #endif

+

+    #ifdef ALPHAMAP_2

+        #ifdef NORMALMAP_8

+          n = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale).xyz;

+          normal += n * alphaBlend2.r;

+        #endif

+

+        #ifdef NORMALMAP_9

+          n = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale);

+          normal += n * alphaBlend2.g;

+        #endif

+

+        #ifdef NORMALMAP_10

+          n = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale);

+          normal += n * alphaBlend2.b;

+        #endif

+

+        #ifdef NORMALMAP_11

+          n = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale);

+          normal += n * alphaBlend2.a;

+        #endif

+    #endif

+

+    normal = (normal.xyz * vec3(2.0) - vec3(1.0));

+    return normalize(normal);

+  }

+

+  #ifdef TRI_PLANAR_MAPPING

+

+    vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in float scale) {

+      vec4 col1 = texture2D( map, coords.yz * scale);

+      vec4 col2 = texture2D( map, coords.xz * scale);

+      vec4 col3 = texture2D( map, coords.xy * scale);

+      // blend the results of the 3 planar projections.

+      vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z;

+      return tex;

+    }

+

+    vec4 calculateTriPlanarDiffuseBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord) {

+        // tri-planar texture bending factor for this fragment's normal

+        vec3 blending = abs( wNorm );

+        blending = (blending -0.2) * 0.7;

+        blending = normalize(max(blending, 0.00001));      // Force weights to sum to 1.0 (very important!)

+        float b = (blending.x + blending.y + blending.z);

+        blending /= vec3(b, b, b);

+

+        // texture coords

+        vec4 coords = wVert;

+

+        // blend the results of the 3 planar projections.

+        vec4 tex0 = getTriPlanarBlend(coords, blending, m_DiffuseMap, m_DiffuseMap_0_scale);

+

+        #ifdef DIFFUSEMAP_1

+          // blend the results of the 3 planar projections.

+          vec4 tex1 = getTriPlanarBlend(coords, blending, m_DiffuseMap_1, m_DiffuseMap_1_scale);

+        #endif

+        #ifdef DIFFUSEMAP_2

+          // blend the results of the 3 planar projections.

+          vec4 tex2 = getTriPlanarBlend(coords, blending, m_DiffuseMap_2, m_DiffuseMap_2_scale);

+        #endif

+        #ifdef DIFFUSEMAP_3

+          // blend the results of the 3 planar projections.

+          vec4 tex3 = getTriPlanarBlend(coords, blending, m_DiffuseMap_3, m_DiffuseMap_3_scale);

+        #endif

+        #ifdef DIFFUSEMAP_4

+          // blend the results of the 3 planar projections.

+          vec4 tex4 = getTriPlanarBlend(coords, blending, m_DiffuseMap_4, m_DiffuseMap_4_scale);

+        #endif

+        #ifdef DIFFUSEMAP_5

+          // blend the results of the 3 planar projections.

+          vec4 tex5 = getTriPlanarBlend(coords, blending, m_DiffuseMap_5, m_DiffuseMap_5_scale);

+        #endif

+        #ifdef DIFFUSEMAP_6

+          // blend the results of the 3 planar projections.

+          vec4 tex6 = getTriPlanarBlend(coords, blending, m_DiffuseMap_6, m_DiffuseMap_6_scale);

+        #endif

+        #ifdef DIFFUSEMAP_7

+          // blend the results of the 3 planar projections.

+          vec4 tex7 = getTriPlanarBlend(coords, blending, m_DiffuseMap_7, m_DiffuseMap_7_scale);

+        #endif

+        #ifdef DIFFUSEMAP_8

+          // blend the results of the 3 planar projections.

+          vec4 tex8 = getTriPlanarBlend(coords, blending, m_DiffuseMap_8, m_DiffuseMap_8_scale);

+        #endif

+        #ifdef DIFFUSEMAP_9

+          // blend the results of the 3 planar projections.

+          vec4 tex9 = getTriPlanarBlend(coords, blending, m_DiffuseMap_9, m_DiffuseMap_9_scale);

+        #endif

+        #ifdef DIFFUSEMAP_10

+          // blend the results of the 3 planar projections.

+          vec4 tex10 = getTriPlanarBlend(coords, blending, m_DiffuseMap_10, m_DiffuseMap_10_scale);

+        #endif

+        #ifdef DIFFUSEMAP_11

+          // blend the results of the 3 planar projections.

+          vec4 tex11 = getTriPlanarBlend(coords, blending, m_DiffuseMap_11, m_DiffuseMap_11_scale);

+        #endif

+

+        vec4 alphaBlend   = texture2D( m_AlphaMap, texCoord.xy );

+

+        #ifdef ALPHAMAP_1

+          vec4 alphaBlend1   = texture2D( m_AlphaMap_1, texCoord.xy );

+        #endif

+        #ifdef ALPHAMAP_2

+          vec4 alphaBlend2   = texture2D( m_AlphaMap_2, texCoord.xy );

+        #endif

+

+        vec4 diffuseColor = tex0 * alphaBlend.r;

+        #ifdef DIFFUSEMAP_1

+          diffuseColor = mix( diffuseColor, tex1, alphaBlend.g );

+          #ifdef DIFFUSEMAP_2

+            diffuseColor = mix( diffuseColor, tex2, alphaBlend.b );

+            #ifdef DIFFUSEMAP_3

+              diffuseColor = mix( diffuseColor, tex3, alphaBlend.a );

+              #ifdef ALPHAMAP_1

+                  #ifdef DIFFUSEMAP_4

+                    diffuseColor = mix( diffuseColor, tex4, alphaBlend1.r );

+                    #ifdef DIFFUSEMAP_5

+                      diffuseColor = mix( diffuseColor, tex5, alphaBlend1.g );

+                      #ifdef DIFFUSEMAP_6

+                        diffuseColor = mix( diffuseColor, tex6, alphaBlend1.b );

+                        #ifdef DIFFUSEMAP_7

+                          diffuseColor = mix( diffuseColor, tex7, alphaBlend1.a );

+                          #ifdef ALPHAMAP_2

+                              #ifdef DIFFUSEMAP_8

+                                diffuseColor = mix( diffuseColor, tex8, alphaBlend2.r );

+                                #ifdef DIFFUSEMAP_9

+                                  diffuseColor = mix( diffuseColor, tex9, alphaBlend2.g );

+                                  #ifdef DIFFUSEMAP_10

+                                    diffuseColor = mix( diffuseColor, tex10, alphaBlend2.b );

+                                    #ifdef DIFFUSEMAP_11

+                                      diffuseColor = mix( diffuseColor, tex11, alphaBlend2.a );

+                                    #endif

+                                  #endif

+                                #endif

+                              #endif

+                          #endif

+                        #endif

+                      #endif

+                    #endif

+                  #endif

+              #endif

+            #endif

+          #endif

+        #endif

+

+        return diffuseColor;

+    }

+

+    vec3 calculateNormalTriPlanar(in vec3 wNorm, in vec4 wVert,in vec2 texCoord) {

+      // tri-planar texture bending factor for this fragment's world-space normal

+      vec3 blending = abs( wNorm );

+      blending = (blending -0.2) * 0.7;

+      blending = normalize(max(blending, 0.00001));      // Force weights to sum to 1.0 (very important!)

+      float b = (blending.x + blending.y + blending.z);

+      blending /= vec3(b, b, b);

+

+      // texture coords

+      vec4 coords = wVert;

+      vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy );

+

+    #ifdef ALPHAMAP_1

+      vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy );

+    #endif

+    #ifdef ALPHAMAP_2

+      vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy );

+    #endif

+

+      vec3 normal = vec3(0,0,1);

+      vec3 n = vec3(0,0,0);

+

+      #ifdef NORMALMAP

+          n = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale).xyz;

+          normal += n * alphaBlend.r;

+      #endif

+

+      #ifdef NORMALMAP_1

+          n = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale).xyz;

+          normal += n * alphaBlend.g;

+      #endif

+

+      #ifdef NORMALMAP_2

+          n = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale).xyz;

+          normal += n * alphaBlend.b;

+      #endif

+

+      #ifdef NORMALMAP_3

+          n = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale).xyz;

+          normal += n * alphaBlend.a;

+      #endif

+

+      #ifdef ALPHAMAP_1

+          #ifdef NORMALMAP_4

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale).xyz;

+              normal += n * alphaBlend1.r;

+          #endif

+

+          #ifdef NORMALMAP_5

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale).xyz;

+              normal += n * alphaBlend1.g;

+          #endif

+

+          #ifdef NORMALMAP_6

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale).xyz;

+              normal += n * alphaBlend1.b;

+          #endif

+

+          #ifdef NORMALMAP_7

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale).xyz;

+              normal += n * alphaBlend1.a;

+          #endif

+      #endif

+

+      #ifdef ALPHAMAP_2

+          #ifdef NORMALMAP_8

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale).xyz;

+              normal += n * alphaBlend2.r;

+          #endif

+

+          #ifdef NORMALMAP_9

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale).xyz;

+              normal += n * alphaBlend2.g;

+          #endif

+

+          #ifdef NORMALMAP_10

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale).xyz;

+              normal += n * alphaBlend2.b;

+          #endif

+

+          #ifdef NORMALMAP_11

+              n = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale).xyz;

+              normal += n * alphaBlend2.a;

+          #endif

+      #endif

+

+      normal = (normal.xyz * vec3(2.0) - vec3(1.0));

+      return normalize(normal);

+    }

+  #endif

+

+#endif

+

+

+

+void main(){

+

+    //----------------------

+    // diffuse calculations

+    //----------------------

+    #ifdef DIFFUSEMAP

+      #ifdef ALPHAMAP

+        #ifdef TRI_PLANAR_MAPPING

+            vec4 diffuseColor = calculateTriPlanarDiffuseBlend(wNormal, wVertex, texCoord);

+        #else

+            vec4 diffuseColor = calculateDiffuseBlend(texCoord);

+        #endif

+      #else

+        vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord);

+      #endif

+    #else

+      vec4 diffuseColor = vec4(1.0);

+    #endif

+

+        float spotFallOff = 1.0;

+        if(g_LightDirection.w!=0.0){

+              vec3 L=normalize(lightVec.xyz);

+              vec3 spotdir = normalize(g_LightDirection.xyz);

+              float curAngleCos = dot(-L, spotdir);             

+              float innerAngleCos = floor(g_LightDirection.w) * 0.001;

+              float outerAngleCos = fract(g_LightDirection.w);

+              float innerMinusOuter = innerAngleCos - outerAngleCos;

+

+              spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;

+

+              if(spotFallOff <= 0.0){

+                  gl_FragColor = AmbientSum * diffuseColor;

+                  return;

+              }else{

+                  spotFallOff = clamp(spotFallOff, 0.0, 1.0);

+              }

+        }

+    

+    //---------------------

+    // normal calculations

+    //---------------------

+    #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11)

+      #ifdef TRI_PLANAR_MAPPING

+        vec3 normal = calculateNormalTriPlanar(wNormal, wVertex, texCoord);

+      #else

+        vec3 normal = calculateNormal(texCoord);

+      #endif

+    #else

+      vec3 normal = vNormal;

+    #endif

+

+

+    //-----------------------

+    // lighting calculations

+    //-----------------------

+    vec4 lightDir = vLightDir;

+    lightDir.xyz = normalize(lightDir.xyz);

+

+    vec2 light = computeLighting(vPosition, normal, vViewDir.xyz, lightDir.xyz)*spotFallOff;

+

+    vec4 specularColor = vec4(1.0);

+

+    //--------------------------

+    // final color calculations

+    //--------------------------

+    gl_FragColor =  AmbientSum * diffuseColor +

+                    DiffuseSum * diffuseColor  * light.x +

+                    SpecularSum * specularColor * light.y;

+

+    //gl_FragColor.a = alpha;

+}
\ No newline at end of file
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.j3md b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.j3md
new file mode 100644
index 0000000..0c48999
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.j3md
@@ -0,0 +1,254 @@
+// NOTE: Doesn't support OpenGL1

+MaterialDef Terrain Lighting {

+

+    MaterialParameters {

+

+        // use tri-planar mapping

+        Boolean useTriPlanarMapping

+

+        // Use ward specular instead of phong

+        Boolean WardIso

+

+        // Are we rendering TerrainGrid

+        Boolean isTerrainGrid

+

+        // Ambient color

+        Color Ambient

+

+        // Diffuse color

+        Color Diffuse

+

+        // Specular color

+        Color Specular

+

+        // Specular power/shininess

+        Float Shininess : 1

+

+        // Texture map #0

+        Texture2D DiffuseMap

+        Float DiffuseMap_0_scale

+        Texture2D NormalMap

+

+        // Texture map #1

+        Texture2D DiffuseMap_1

+        Float DiffuseMap_1_scale

+        Texture2D NormalMap_1

+

+        // Texture map #2

+        Texture2D DiffuseMap_2

+        Float DiffuseMap_2_scale

+        Texture2D NormalMap_2

+

+        // Texture map #3

+        Texture2D DiffuseMap_3

+        Float DiffuseMap_3_scale

+        Texture2D NormalMap_3

+

+        // Texture map #4

+        Texture2D DiffuseMap_4

+        Float DiffuseMap_4_scale

+        Texture2D NormalMap_4

+

+        // Texture map #5

+        Texture2D DiffuseMap_5

+        Float DiffuseMap_5_scale

+        Texture2D NormalMap_5

+

+        // Texture map #6

+        Texture2D DiffuseMap_6

+        Float DiffuseMap_6_scale

+        Texture2D NormalMap_6

+

+        // Texture map #7

+        Texture2D DiffuseMap_7

+        Float DiffuseMap_7_scale

+        Texture2D NormalMap_7

+

+        // Texture map #8

+        Texture2D DiffuseMap_8

+        Float DiffuseMap_8_scale

+        Texture2D NormalMap_8

+

+        // Texture map #9

+        Texture2D DiffuseMap_9

+        Float DiffuseMap_9_scale

+        Texture2D NormalMap_9

+

+        // Texture map #10

+        Texture2D DiffuseMap_10

+        Float DiffuseMap_10_scale

+        Texture2D NormalMap_10

+

+        // Texture map #11

+        Texture2D DiffuseMap_11

+        Float DiffuseMap_11_scale

+        Texture2D NormalMap_11

+

+

+        // Specular/gloss map

+        Texture2D SpecularMap

+

+

+        // Texture that specifies alpha values

+        Texture2D AlphaMap

+        Texture2D AlphaMap_1

+        Texture2D AlphaMap_2

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+

+        // The glow color of the object

+        Color GlowColor

+    }

+

+    Technique {

+

+        LightMode MultiPass

+

+        VertexShader GLSL100:   Common/MatDefs/Terrain/TerrainLighting.vert

+        FragmentShader GLSL100: Common/MatDefs/Terrain/TerrainLighting.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            NormalMatrix

+            WorldViewMatrix

+            ViewMatrix

+        }

+

+        Defines {

+            TRI_PLANAR_MAPPING : useTriPlanarMapping

+            TERRAIN_GRID : isTerrainGrid

+            WARDISO   : WardIso

+

+            DIFFUSEMAP : DiffuseMap

+            DIFFUSEMAP_1 : DiffuseMap_1

+            DIFFUSEMAP_2 : DiffuseMap_2

+            DIFFUSEMAP_3 : DiffuseMap_3

+            DIFFUSEMAP_4 : DiffuseMap_4

+            DIFFUSEMAP_5 : DiffuseMap_5

+            DIFFUSEMAP_6 : DiffuseMap_6

+            DIFFUSEMAP_7 : DiffuseMap_7

+            DIFFUSEMAP_8 : DiffuseMap_8

+            DIFFUSEMAP_9 : DiffuseMap_9

+            DIFFUSEMAP_10 : DiffuseMap_10

+            DIFFUSEMAP_11 : DiffuseMap_11

+            NORMALMAP : NormalMap

+            NORMALMAP_1 : NormalMap_1

+            NORMALMAP_2 : NormalMap_2

+            NORMALMAP_3 : NormalMap_3

+            NORMALMAP_4 : NormalMap_4

+            NORMALMAP_5 : NormalMap_5

+            NORMALMAP_6 : NormalMap_6

+            NORMALMAP_7 : NormalMap_7

+            NORMALMAP_8 : NormalMap_8

+            NORMALMAP_9 : NormalMap_9

+            NORMALMAP_10 : NormalMap_10

+            NORMALMAP_11 : NormalMap_11

+            SPECULARMAP : SpecularMap

+            ALPHAMAP : AlphaMap

+            ALPHAMAP_1 : AlphaMap_1

+            ALPHAMAP_2 : AlphaMap_2

+            DIFFUSEMAP_0_SCALE : DiffuseMap_0_scale

+            DIFFUSEMAP_1_SCALE : DiffuseMap_1_scale

+            DIFFUSEMAP_2_SCALE : DiffuseMap_2_scale

+            DIFFUSEMAP_3_SCALE : DiffuseMap_3_scale

+            DIFFUSEMAP_4_SCALE : DiffuseMap_4_scale

+            DIFFUSEMAP_5_SCALE : DiffuseMap_5_scale

+            DIFFUSEMAP_6_SCALE : DiffuseMap_6_scale

+            DIFFUSEMAP_7_SCALE : DiffuseMap_7_scale

+            DIFFUSEMAP_8_SCALE : DiffuseMap_8_scale

+            DIFFUSEMAP_9_SCALE : DiffuseMap_9_scale

+            DIFFUSEMAP_10_SCALE : DiffuseMap_10_scale

+            DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale

+        }

+    }

+

+    Technique PreShadow {

+

+        VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert

+        FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+        }

+

+        Defines {

+            DIFFUSEMAP_ALPHA : DiffuseMap

+        }

+

+        RenderState {

+            FaceCull Off

+            DepthTest On

+            DepthWrite On

+            PolyOffset 5 0

+            ColorWrite Off

+        }

+

+    }

+

+  Technique PreNormalPass {

+

+        VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert

+        FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldViewMatrix

+            NormalMatrix

+        }

+

+        Defines {

+            DIFFUSEMAP_ALPHA : DiffuseMap

+        }

+

+        RenderState {

+

+        }

+

+    }

+

+    Technique GBuf {

+

+        VertexShader GLSL100:   Common/MatDefs/Light/GBuf.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+            WorldMatrix

+        }

+

+        Defines {

+            VERTEX_COLOR : UseVertexColor

+            MATERIAL_COLORS : UseMaterialColors

+            V_TANGENT : VTangent

+            MINNAERT  : Minnaert

+            WARDISO   : WardIso

+

+            DIFFUSEMAP : DiffuseMap

+            NORMALMAP : NormalMap

+            SPECULARMAP : SpecularMap

+            PARALLAXMAP : ParallaxMap

+        }

+    }

+

+    Technique FixedFunc {

+        LightMode FixedPipeline

+    }

+

+    Technique Glow {

+

+        VertexShader GLSL100:   Common/MatDefs/Misc/SimpleTextured.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+        }

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.vert b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.vert
new file mode 100644
index 0000000..a3a1cc2
--- /dev/null
+++ b/engine/src/terrain/Common/MatDefs/Terrain/TerrainLighting.vert
@@ -0,0 +1,107 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+uniform mat4 g_WorldViewMatrix;

+uniform mat3 g_NormalMatrix;

+uniform mat4 g_ViewMatrix;

+

+uniform vec4 g_LightColor;

+uniform vec4 g_LightPosition;

+uniform vec4 g_AmbientLightColor;

+

+uniform float m_Shininess;

+

+attribute vec3 inPosition;

+attribute vec3 inNormal;

+attribute vec2 inTexCoord;

+attribute vec4 inTangent;

+

+varying vec3 vNormal;

+varying vec2 texCoord;

+varying vec3 vPosition;

+varying vec3 vnPosition;

+varying vec3 vViewDir;

+varying vec3 vnViewDir;

+varying vec4 vLightDir;

+varying vec4 vnLightDir;

+

+varying vec3 lightVec;

+

+varying vec4 AmbientSum;

+varying vec4 DiffuseSum;

+varying vec4 SpecularSum;

+

+#ifdef TRI_PLANAR_MAPPING

+  varying vec4 wVertex;

+  varying vec3 wNormal;

+#endif

+

+// JME3 lights in world space

+void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){

+    float posLight = step(0.5, color.w);

+    vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);

+    lightVec.xyz = tempVec;  

+    float dist = length(tempVec);

+    lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);

+    lightDir.xyz = tempVec / vec3(dist);

+}

+

+

+void main(){

+    vec4 pos = vec4(inPosition, 1.0);

+    gl_Position = g_WorldViewProjectionMatrix * pos;

+    #ifdef TERRAIN_GRID

+    texCoord = inTexCoord * 2.0;

+    #else

+    texCoord = inTexCoord;

+    #endif

+

+    vec3 wvPosition = (g_WorldViewMatrix * pos).xyz;

+    vec3 wvNormal  = normalize(g_NormalMatrix * inNormal);

+    vec3 viewDir = normalize(-wvPosition);

+

+    vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0)));

+    wvLightPos.w = g_LightPosition.w;

+    vec4 lightColor = g_LightColor;

+

+    //--------------------------

+    // specific to normal maps:

+    //--------------------------

+    #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11)

+      vec3 wvTangent = normalize(g_NormalMatrix * inTangent.xyz);

+      vec3 wvBinormal = cross(wvNormal, wvTangent);

+

+      mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal);

+

+      vPosition = wvPosition * tbnMat;

+      vViewDir  = viewDir * tbnMat;

+      lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);

+      vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz;

+    #else

+

+    //-------------------------

+    // general to all lighting

+    //-------------------------

+    vNormal = wvNormal;

+

+    vPosition = wvPosition;

+    vViewDir = viewDir;

+

+    lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);

+

+    #endif

+   

+      //computing spot direction in view space and unpacking spotlight cos

+  // spotVec=(g_ViewMatrix *vec4(g_LightDirection.xyz,0.0) );

+  // spotVec.w=floor(g_LightDirection.w)*0.001;

+  // lightVec.w = fract(g_LightDirection.w);

+

+    AmbientSum  = vec4(0.2, 0.2, 0.2, 1.0) * g_AmbientLightColor; // Default: ambient color is dark gray

+    DiffuseSum  = lightColor;

+    SpecularSum = lightColor;

+

+

+#ifdef TRI_PLANAR_MAPPING

+    wVertex = vec4(inPosition,0.0);

+    wNormal = inNormal;

+#endif

+

+}
\ No newline at end of file
diff --git a/engine/src/terrain/com/jme3/terrain/GeoMap.java b/engine/src/terrain/com/jme3/terrain/GeoMap.java
new file mode 100644
index 0000000..dc9264f
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/GeoMap.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Constructs heightfields to be used in Terrain.
+ */
+public class GeoMap implements Savable {
+    
+    protected float[] hdata;
+    protected int width, height, maxval;
+    
+    public GeoMap() {}
+    
+    @Deprecated
+    public GeoMap(FloatBuffer heightData, int width, int height, int maxval){
+        hdata = new float[heightData.limit()];
+        heightData.get(hdata);
+        this.width = width;
+        this.height = height;
+        this.maxval = maxval;
+    }
+    
+    public GeoMap(float[] heightData, int width, int height, int maxval){
+        this.hdata = heightData;
+        this.width = width;
+        this.height = height;
+        this.maxval = maxval;
+    }
+
+    @Deprecated
+    public FloatBuffer getHeightData(){
+        if (!isLoaded())
+            return null;
+        return BufferUtils.createFloatBuffer(hdata);
+    }
+    
+    public float[] getHeightArray(){
+        if (!isLoaded())
+            return null;
+        return hdata;
+    }
+
+    /**
+     * @return The maximum possible value that <code>getValue()</code> can 
+     * return. Mostly depends on the source data format (byte, short, int, etc).
+     */
+    public int getMaximumValue(){
+        return maxval;
+    }
+
+    /**
+     * Returns the height value for a given point.
+     *
+     * MUST return the same value as getHeight(y*getWidth()+x)
+     *
+     * @param x the X coordinate
+     * @param y the Y coordinate
+     * @returns an arbitrary height looked up from the heightmap
+     *
+     * @throws NullPointerException If isLoaded() is false
+     */
+    public float getValue(int x, int y) {
+        return hdata[y*width+x];
+    }
+
+    /**
+     * Returns the height value at the given index.
+     *
+     * zero index is top left of map,
+     * getWidth()*getHeight() index is lower right
+     *
+     * @param i The index
+     * @returns an arbitrary height looked up from the heightmap
+     *
+     * @throws NullPointerException If isLoaded() is false
+     */
+    public float getValue(int i) {
+        return hdata[i];
+    }
+
+
+    /**
+     * Returns the width of this Geomap
+     *
+     * @returns the width of this Geomap
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * Returns the height of this Geomap
+     *
+     * @returns the height of this Geomap
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    /**
+     * Returns true if the Geomap data is loaded in memory
+     * If false, then the data is unavailable- must be loaded with load()
+     * before the methods getHeight/getNormal can be used
+     *
+     * @returns wether the geomap data is loaded in system memory
+     */
+    public boolean isLoaded() {
+        return true;
+    }
+
+    /**
+     * Creates a normal array from the normal data in this Geomap
+     *
+     * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
+     * @returns store, or a new FloatBuffer if store is null
+     *
+     * @throws NullPointerException If isLoaded() or hasNormalmap() is false
+     */
+    public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {
+        
+        if (store!=null){
+            if (store.remaining() < getWidth()*getHeight()*3)
+                throw new BufferUnderflowException();
+        }else{
+            store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3);
+        }
+        store.rewind();
+
+        Vector3f oppositePoint = new Vector3f();
+        Vector3f adjacentPoint = new Vector3f();
+        Vector3f rootPoint = new Vector3f();
+        Vector3f tempNorm = new Vector3f();
+        int normalIndex = 0;
+
+        for (int y = 0; y < getHeight(); y++) {
+            for (int x = 0; x < getWidth(); x++) {
+                rootPoint.set(x, getValue(x,y), y);
+                if (y == getHeight() - 1) {
+                    if (x == getWidth() - 1) {  // case #4 : last row, last col
+                        // left cross up
+//                            adj = normalIndex - getWidth();
+//                            opp = normalIndex - 1;
+                        adjacentPoint.set(x, getValue(x,y-1), y-1);
+                        oppositePoint.set(x-1, getValue(x-1, y), y);
+                    } else {                    // case #3 : last row, except for last col
+                        // right cross up
+//                            adj = normalIndex + 1;
+//                            opp = normalIndex - getWidth();
+                        adjacentPoint.set(x+1, getValue(x+1,y), y);
+                        oppositePoint.set(x, getValue(x,y-1), y-1);
+                    }
+                } else {
+                    if (x == getWidth() - 1) {  // case #2 : last column except for last row
+                        // left cross down
+                        adjacentPoint.set(x-1, getValue(x-1,y), y);
+                        oppositePoint.set(x, getValue(x,y+1), y+1);
+//                            adj = normalIndex - 1;
+//                            opp = normalIndex + getWidth();
+                    } else {                    // case #1 : most cases
+                        // right cross down
+                        adjacentPoint.set(x, getValue(x,y+1), y+1);
+                        oppositePoint.set(x+1, getValue(x+1,y), y);
+//                            adj = normalIndex + getWidth();
+//                            opp = normalIndex + 1;
+                    }
+                }
+
+
+
+                tempNorm.set(adjacentPoint).subtractLocal(rootPoint)
+                        .crossLocal(oppositePoint.subtractLocal(rootPoint));
+                tempNorm.multLocal(scale).normalizeLocal();
+//                    store.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+                BufferUtils.setInBuffer(tempNorm, store,
+                        normalIndex);
+                normalIndex++;
+            }
+        }
+
+        return store;
+    }
+    
+    /**
+     * Creates a vertex array from the height data in this Geomap
+     *
+     * The scale argument specifies the scale to use for the vertex buffer.
+     * For example, if scale is 10,1,10, then the greatest X value is getWidth()*10
+     *
+     * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3
+     * @param scale Created vertexes are scaled by this vector
+     *
+     * @returns store, or a new FloatBuffer if store is null
+     *
+     * @throws NullPointerException If isLoaded() is false
+     */
+    public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
+
+        if (store!=null){
+            if (store.remaining() < width*height*3)
+                throw new BufferUnderflowException();
+        }else{
+            store = BufferUtils.createFloatBuffer(width*height*3);
+        }
+
+        assert hdata.length == height*width;
+
+        Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f,
+                                       0,
+                                       -getWidth() * scale.z * 0.5f);
+        if (!center)
+            offset.zero();
+
+        int i = 0;
+        for (int z = 0; z < height; z++){
+            for (int x = 0; x < width; x++){
+                store.put( (float)x*scale.x + offset.x );
+                store.put( (float)hdata[i++]*scale.y );
+                store.put( (float)z*scale.z + offset.z );
+            }
+        }
+
+        return store;
+    }
+    
+    public Vector2f getUV(int x, int y, Vector2f store){
+        store.set( (float)x / (float)getWidth(),
+                   (float)y / (float)getHeight() );
+        return store;
+    }
+
+    public Vector2f getUV(int i, Vector2f store){
+        return store;
+    }
+    
+    public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale){
+        if (store!=null){
+            if (store.remaining() < getWidth()*getHeight()*2)
+                throw new BufferUnderflowException();
+        }else{
+            store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*2);
+        }
+
+        if (offset == null)
+            offset = new Vector2f();
+
+        Vector2f tcStore = new Vector2f();
+        for (int y = 0; y < getHeight(); y++){
+            for (int x = 0; x < getWidth(); x++){
+                getUV(x,y,tcStore);
+                store.put( offset.x + tcStore.x * scale.x );
+                store.put( offset.y + tcStore.y * scale.y );
+            }
+
+        }
+
+        return store;
+    }
+    
+    public IntBuffer writeIndexArray(IntBuffer store){
+        int faceN = (getWidth()-1)*(getHeight()-1)*2;
+
+        if (store!=null){
+            if (store.remaining() < faceN*3)
+                throw new BufferUnderflowException();
+        }else{
+            store = BufferUtils.createIntBuffer(faceN*3);
+        }
+
+        int i = 0;
+        for (int z = 0; z < getHeight()-1; z++){
+            for (int x = 0; x < getWidth()-1; x++){
+                store.put(i).put(i+getWidth()).put(i+getWidth()+1);
+                store.put(i+getWidth()+1).put(i+1).put(i);
+                i++;
+
+                // TODO: There's probably a better way to do this..
+                if (x==getWidth()-2) i++;
+            }
+        }
+        store.flip();
+
+        return store;
+    }
+    
+    public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center){
+        FloatBuffer pb = writeVertexArray(null, scale, center);
+        FloatBuffer tb = writeTexCoordArray(null, Vector2f.ZERO, tcScale);
+        FloatBuffer nb = writeNormalArray(null, scale);
+        IntBuffer ib = writeIndexArray(null);
+        Mesh m = new Mesh();
+        m.setBuffer(Type.Position, 3, pb);
+        m.setBuffer(Type.Normal, 3, nb);
+        m.setBuffer(Type.TexCoord, 2, tb);
+        m.setBuffer(Type.Index, 3, ib);
+        m.setStatic();
+        m.updateBound();
+        return m;
+    }
+    
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(hdata, "hdataarray", null);
+        oc.write(width, "width", 0);
+        oc.write(height, "height", 0);
+        oc.write(maxval, "maxval", 0);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        hdata = ic.readFloatArray("hdataarray", null);
+        if (hdata == null) {
+            FloatBuffer buf = ic.readFloatBuffer("hdata", null);
+            if (buf != null) {
+                hdata = new float[buf.limit()];
+                buf.get(hdata);
+            }
+        }
+        width = ic.readInt("width", 0);
+        height = ic.readInt("height", 0);
+        maxval = ic.readInt("maxval", 0);
+    }
+
+    
+}
diff --git a/engine/src/terrain/com/jme3/terrain/ProgressMonitor.java b/engine/src/terrain/com/jme3/terrain/ProgressMonitor.java
new file mode 100644
index 0000000..4664231
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/ProgressMonitor.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+/**
+ * Monitor the progress of an expensive terrain operation.
+ *
+ * Monitors are passed into the expensive operations, and those operations
+ * call the incrementProgress method whenever they determine that progress
+ * has changed. It is up to the monitor to determine if the increment is
+ * percentage or a unit of another measure, but anything calling it should
+ * use the setMonitorMax() method and make sure incrementProgress() match up
+ * in terms of units.
+ *
+ * @author Brent Owens
+ */
+public interface ProgressMonitor {
+
+    /**
+     * Increment the progress by a unit.
+     */
+    public void incrementProgress(float increment);
+
+    /**
+     * The max value that when reached, the progress is at 100%.
+     */
+    public void setMonitorMax(float max);
+
+    /**
+     * The max value of the progress. When incrementProgress()
+     * reaches this value, progress is complete
+     */
+    public float getMonitorMax();
+
+    /**
+     * The progress has completed
+     */
+    public void progressComplete();
+}
diff --git a/engine/src/terrain/com/jme3/terrain/Terrain.java b/engine/src/terrain/com/jme3/terrain/Terrain.java
new file mode 100644
index 0000000..6f3872e
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/Terrain.java
@@ -0,0 +1,209 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.terrain;

+

+import com.jme3.material.Material;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;

+import java.util.List;

+

+/**

+ * Terrain can be one or many meshes comprising of a, probably large, piece of land.

+ * Terrain is Y-up in the grid axis, meaning gravity acts in the -Y direction.

+ * Level of Detail (LOD) is supported and expected as terrains can get very large. LOD can

+ * also be disabled if you so desire, however some terrain implementations can choose to ignore

+ * useLOD(boolean).

+ * Terrain implementations should extend Node, or at least Spatial.

+ *

+ * @author bowens

+ */

+public interface Terrain {

+

+    /**

+     * Get the real-world height of the terrain at the specified X-Z coorindate.

+     * @param xz the X-Z world coordinate

+     * @return the height at the given point

+     */

+    public float getHeight(Vector2f xz);

+    

+    /**

+     * Get the normal vector for the surface of the terrain at the specified

+     * X-Z coordinate. This normal vector can be a close approximation. It does not

+     * take into account any normal maps on the material.

+     * @param xz the X-Z world coordinate

+     * @return the normal vector at the given point

+     */

+    public Vector3f getNormal(Vector2f xz);

+

+    /**

+     * Get the heightmap height at the specified X-Z coordinate. This does not

+     * count scaling and snaps the XZ coordinate to the nearest (rounded) heightmap grid point.

+     * @param xz world coordinate

+     * @return the height, unscaled and uninterpolated

+     */

+    public float getHeightmapHeight(Vector2f xz);

+

+    /**

+     * Set the height at the specified X-Z coordinate.

+     * To set the height of the terrain and see it, you will have

+     * to unlock the terrain meshes by calling terrain.setLocked(false) before

+     * you call setHeight().

+     * @param xzCoordinate coordinate to set the height

+     * @param height that will be set at the coordinate

+     */

+    public void setHeight(Vector2f xzCoordinate, float height);

+

+    /**

+     * Set the height at many points. The two lists must be the same size.

+     * Each xz coordinate entry matches to a height entry, 1 for 1. So the 

+     * first coordinate matches to the first height value, the last to the 

+     * last etc.

+     * @param xz a list of coordinates where the hight will be set

+     * @param height the heights that match the xz coordinates

+     */

+    public void setHeight(List<Vector2f> xz, List<Float> height);

+

+    /**

+     * Raise/lower the height in one call (instead of getHeight then setHeight).

+     * @param xzCoordinate world coordinate to adjust the terrain height

+     * @param delta +- value to adjust the height by

+     */

+    public void adjustHeight(Vector2f xzCoordinate, float delta);

+

+    /**

+     * Raise/lower the height at many points. The two lists must be the same size.

+     * Each xz coordinate entry matches to a height entry, 1 for 1. So the

+     * first coordinate matches to the first height value, the last to the

+     * last etc.

+     * @param xz a list of coordinates where the hight will be adjusted

+     * @param height +- value to adjust the height by, that matches the xz coordinates

+     */

+    public void adjustHeight(List<Vector2f> xz, List<Float> height);

+

+    /**

+     * Get the heightmap of the entire terrain.

+     * This can return null if that terrain object does not store the height data.

+     * Infinite or "paged" terrains will not be able to support this, so use with caution.

+     */

+    public float[] getHeightMap();

+    

+    /**

+     * This is calculated by the specific LOD algorithm.

+     * A value of one means that the terrain is showing full detail.

+     * The higher the value, the more the terrain has been generalized

+     * and the less detailed it will be.

+     */

+    public int getMaxLod();

+

+    /**

+     * Called by an LodControl.

+     * Calculates the level of detail of the terrain and adjusts its geometry.

+     * This is where the Terrain's LOD algorithm will change the detail of

+     * the terrain based on how far away this position is from the particular

+     * terrain patch.

+     * @param location the Camera's location. A list of one camera location is normal 

+     *  if you just have one camera in your scene.

+     */

+    public void update(List<Vector3f> location, LodCalculator lodCalculator);

+

+    /**

+     * Lock or unlock the meshes of this terrain.

+     * Locked meshes are un-editable but have better performance.

+     * This should call the underlying getMesh().setStatic()/setDynamic() methods.

+     * @param locked or unlocked

+     */

+    public void setLocked(boolean locked);

+

+    /**

+     * Pre-calculate entropy values.

+     * Some terrain systems support entropy calculations to determine LOD

+     * changes. Often these entropy calculations are expensive and can be

+     * cached ahead of time. Use this method to do that.

+     */

+    public void generateEntropy(ProgressMonitor monitor);

+

+    /**

+     * Returns the material that this terrain uses.

+     * If it uses many materials, just return the one you think is best.

+     * For TerrainQuads this is sufficient. For TerrainGrid you want to call

+     * getMaterial(Vector3f) instead.

+     */

+    public Material getMaterial();

+    

+    /**

+     * Returns the material that this terrain uses.

+     * Terrain can have different materials in different locations.

+     * In general, the TerrainQuad will only have one material. But 

+     * TerrainGrid will have a different material per tile.

+     * 

+     * It could be possible to pass in null for the location, some Terrain

+     * implementations might just have the one material and not care where

+     * you are looking. So implementations must handle null being supplied.

+     * 

+     * @param worldLocation the location, in world coordinates, of where 

+     * we are interested in the underlying texture.

+     */

+    public Material getMaterial(Vector3f worldLocation);

+

+    /**

+     * Used for painting to get the number of vertices along the edge of the

+     * terrain.

+     * This is an un-scaled size, and should represent the vertex count (ie. the

+     * texture coord count) along an edge of a square terrain.

+     * 

+     * In the standard TerrainQuad default implementation, this will return

+     * the "totalSize" of the terrain (512 or so).

+     */

+    public int getTerrainSize();

+

+    /**

+     * Get the scale of the texture coordinates. Normally if the texture is

+     * laid on the terrain and not scaled so that the texture does not repeat,

+     * then each texture coordinate (on a vertex) will be 1/(terrain size).

+     * That is: the coverage between each consecutive texture coordinate will

+     * be a percentage of the total terrain size.

+     * So if the terrain is 512 vertexes wide, then each texture coord will cover

+     * 1/512 (or 0.00195) percent of the texture.

+     * This is used for converting between tri-planar texture scales and regular

+     * texture scales.

+     * 

+     * not needed

+     */

+    //public float getTextureCoordinateScale();

+    

+    /**

+     * 

+     * 

+     */

+    public int getNumMajorSubdivisions();

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java b/engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java
new file mode 100644
index 0000000..3944b98
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/LODGeomap.java
@@ -0,0 +1,1110 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.geomipmap;

+

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.math.FastMath;

+import com.jme3.math.Triangle;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.Mesh.Mode;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.terrain.GeoMap;

+import com.jme3.util.BufferUtils;

+import com.jme3.util.TempVars;

+import java.io.IOException;

+import java.nio.BufferOverflowException;

+import java.nio.BufferUnderflowException;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+

+/**

+ * Produces the mesh for the TerrainPatch.

+ * This LOD algorithm generates a single triangle strip by first building the center of the

+ * mesh, minus one outer edge around it. Then it builds the edges in counter-clockwise order,

+ * starting at the bottom right and working up, then left across the top, then down across the

+ * left, then right across the bottom.

+ * It needs to know what its neighbour's LOD's are so it can stitch the edges.

+ * It creates degenerate polygons in order to keep the winding order of the polygons and to move

+ * the strip to a new position while still maintaining the continuity of the overall mesh. These

+ * degenerates are removed quickly by the video card.

+ *

+ * @author Brent Owens

+ */

+public class LODGeomap extends GeoMap {

+

+    public LODGeomap() {

+    }

+

+    @Deprecated

+    public LODGeomap(int size, FloatBuffer heightMap) {

+        super(heightMap, size, size, 1);

+    }

+    

+    public LODGeomap(int size, float[] heightMap) {

+        super(heightMap, size, size, 1);

+    }

+

+    public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center) {

+        return this.createMesh(scale, tcScale, tcOffset, offsetAmount, totalSize, center, 1, false, false, false, false);

+    }

+

+    public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {

+        FloatBuffer pb = writeVertexArray(null, scale, center);

+        FloatBuffer texb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize);

+        FloatBuffer nb = writeNormalArray(null, scale);

+        IntBuffer ib = writeIndexArrayLodDiff(null, lod, rightLod, topLod, leftLod, bottomLod);

+        FloatBuffer bb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);

+        FloatBuffer tanb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);

+        writeTangentArray(tanb, bb, texb, scale);

+        Mesh m = new Mesh();

+        m.setMode(Mode.TriangleStrip);

+        m.setBuffer(Type.Position, 3, pb);

+        m.setBuffer(Type.Normal, 3, nb);

+        m.setBuffer(Type.Tangent, 3, tanb);

+        m.setBuffer(Type.Binormal, 3, bb);

+        m.setBuffer(Type.TexCoord, 2, texb);

+        m.setBuffer(Type.Index, 3, ib);

+        m.setStatic();

+        m.updateBound();

+        return m;

+    }

+

+    public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale, float offsetAmount, int totalSize) {

+        if (store != null) {

+            if (store.remaining() < getWidth() * getHeight() * 2) {

+                throw new BufferUnderflowException();

+            }

+        } else {

+            store = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 2);

+        }

+

+        if (offset == null) {

+            offset = new Vector2f();

+        }

+

+        Vector2f tcStore = new Vector2f();

+

+        // work from bottom of heightmap up, so we don't flip the coords

+        for (int y = getHeight() - 1; y >= 0; y--) {

+            for (int x = 0; x < getWidth(); x++) {

+                getUV(x, y, tcStore, offset, offsetAmount, totalSize);

+                float tx = tcStore.x * scale.x;

+                float ty = tcStore.y * scale.y;

+                store.put(tx);

+                store.put(ty);

+            }

+        }

+

+        return store;

+    }

+

+    public Vector2f getUV(int x, int y, Vector2f store, Vector2f offset, float offsetAmount, int totalSize) {

+        float offsetX = offset.x + (offsetAmount * 1.0f);

+        float offsetY = -offset.y + (offsetAmount * 1.0f);//note the -, we flip the tex coords

+

+        store.set((((float) x) + offsetX) / (float) (totalSize - 1), // calculates percentage of texture here

+                (((float) y) + offsetY) / (float) (totalSize - 1));

+        return store;

+    }

+

+    /**

+     * Create the LOD index array that will seam its edges with its neighbour's LOD.

+     * This is a scary method!!! It will break your mind.

+     *

+     * @param store to store the index buffer

+     * @param lod level of detail of the mesh

+     * @param rightLod LOD of the right neighbour

+     * @param topLod LOD of the top neighbour

+     * @param leftLod LOD of the left neighbour

+     * @param bottomLod LOD of the bottom neighbour

+     * @return the LOD-ified index buffer

+     */

+    public IntBuffer writeIndexArrayLodDiff(IntBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {

+

+        IntBuffer buffer2 = store;

+        int numIndexes = calculateNumIndexesLodDiff(lod);

+        if (store == null) {

+            buffer2 = BufferUtils.createIntBuffer(numIndexes);

+        }

+        VerboseIntBuffer buffer = new VerboseIntBuffer(buffer2);

+

+

+        // generate center squares minus the edges

+        //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")");

+        //System.out.println("	for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")");

+        for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row

+            int rowIdx = r * getWidth();

+            int nextRowIdx = (r + 1 * lod) * getWidth();

+            for (int c = lod; c < getWidth() - (1 * lod); c += lod) { // column

+                int idx = rowIdx + c;

+                buffer.put(idx);

+                idx = nextRowIdx + c;

+                buffer.put(idx);

+            }

+

+            // add degenerate triangles

+            if (r < getWidth() - (3 * lod)) {

+                int idx = nextRowIdx + getWidth() - (1 * lod) - 1;

+                buffer.put(idx);

+                idx = nextRowIdx + (1 * lod); // inset by 1

+                buffer.put(idx);

+                //System.out.println("");

+            }

+        }

+        //System.out.println("\nright:");

+

+        //int runningBufferCount = buffer.getCount();

+        //System.out.println("buffer start: "+runningBufferCount);

+

+

+        // right

+        int br = getWidth() * (getWidth() - lod) - 1 - lod;

+        buffer.put(br); // bottom right -1

+        int corner = getWidth() * getWidth() - 1;

+        buffer.put(corner);	// bottom right corner

+        if (rightLod) { // if lower LOD

+            for (int row = getWidth() - lod; row >= 1 + lod; row -= 2 * lod) {

+                int idx = (row) * getWidth() - 1 - lod;

+                buffer.put(idx);

+                idx = (row - lod) * getWidth() - 1;

+                buffer.put(idx);

+                if (row > lod + 1) { //if not the last one

+                    idx = (row - lod) * getWidth() - 1 - lod;

+                    buffer.put(idx);

+                    idx = (row - lod) * getWidth() - 1;

+                    buffer.put(idx);

+                } else {

+                }

+            }

+        } else {

+            buffer.put(corner);//br+1);//degenerate to flip winding order

+            for (int row = getWidth() - lod; row > lod; row -= lod) {

+                int idx = row * getWidth() - 1; // mult to get row

+                buffer.put(idx);

+                buffer.put(idx - lod);

+            }

+

+        }

+

+        buffer.put(getWidth() - 1);

+

+

+        //System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+

+        //System.out.println("\ntop:");

+

+        // top 			(the order gets reversed here so the diagonals line up)

+        if (topLod) { // if lower LOD

+            if (rightLod) {

+                buffer.put(getWidth() - 1);

+            }

+            for (int col = getWidth() - 1; col >= lod; col -= 2 * lod) {

+                int idx = (lod * getWidth()) + col - lod; // next row

+                buffer.put(idx);

+                idx = col - 2 * lod;

+                buffer.put(idx);

+                if (col > lod * 2) { //if not the last one

+                    idx = (lod * getWidth()) + col - 2 * lod;

+                    buffer.put(idx);

+                    idx = col - 2 * lod;

+                    buffer.put(idx);

+                } else {

+                }

+            }

+        } else {

+            if (rightLod) {

+                buffer.put(getWidth() - 1);

+            }

+            for (int col = getWidth() - 1 - lod; col > 0; col -= lod) {

+                int idx = col + (lod * getWidth());

+                buffer.put(idx);

+                idx = col;

+                buffer.put(idx);

+            }

+            buffer.put(0);

+        }

+        buffer.put(0);

+

+        //System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //System.out.println("\nleft:");

+

+        // left

+        if (leftLod) { // if lower LOD

+            if (topLod) {

+                buffer.put(0);

+            }

+            for (int row = 0; row < getWidth() - lod; row += 2 * lod) {

+                int idx = (row + lod) * getWidth() + lod;

+                buffer.put(idx);

+                idx = (row + 2 * lod) * getWidth();

+                buffer.put(idx);

+                if (row < getWidth() - lod - 2 - 1) { //if not the last one

+                    idx = (row + 2 * lod) * getWidth() + lod;

+                    buffer.put(idx);

+                    idx = (row + 2 * lod) * getWidth();

+                    buffer.put(idx);

+                } else {

+                }

+            }

+        } else {

+            if (!topLod) {

+                buffer.put(0);

+            }

+            //buffer.put(getWidth()+1); // degenerate

+            //buffer.put(0); // degenerate winding-flip

+            for (int row = lod; row < getWidth() - lod; row += lod) {

+                int idx = row * getWidth();

+                buffer.put(idx);

+                idx = row * getWidth() + lod;

+                buffer.put(idx);

+            }

+

+        }

+        buffer.put(getWidth() * (getWidth() - 1));

+

+

+        //System.out.println("\nbuffer left: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //if (true) return buffer.delegate;

+        //System.out.println("\nbottom");

+

+        // bottom

+        if (bottomLod) { // if lower LOD

+            if (leftLod) {

+                buffer.put(getWidth() * (getWidth() - 1));

+            }

+            // there was a slight bug here when really high LOD near maxLod

+            // far right has extra index one row up and all the way to the right, need to skip last index entered

+            // seemed to be fixed by making "getWidth()-1-2-lod" this: "getWidth()-1-2*lod", which seems more correct

+            for (int col = 0; col < getWidth() - lod; col += 2 * lod) {

+                int idx = getWidth() * (getWidth() - 1 - lod) + col + lod;

+                buffer.put(idx);

+                idx = getWidth() * (getWidth() - 1) + col + 2 * lod;

+                buffer.put(idx);

+                if (col < getWidth() - 1 - 2 * lod) { //if not the last one

+                    idx = getWidth() * (getWidth() - 1 - lod) + col + 2 * lod;

+                    buffer.put(idx);

+                    idx = getWidth() * (getWidth() - 1) + col + 2 * lod;

+                    buffer.put(idx);

+                } else {

+                }

+            }

+        } else {

+            if (leftLod) {

+                buffer.put(getWidth() * (getWidth() - 1));

+            }

+            for (int col = lod; col < getWidth() - lod; col += lod) {

+                int idx = getWidth() * (getWidth() - 1 - lod) + col; // up

+                buffer.put(idx);

+                idx = getWidth() * (getWidth() - 1) + col; // down

+                buffer.put(idx);

+            }

+            //buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end!

+        }

+

+        buffer.put(getWidth() * getWidth() - 1);

+

+        //System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //System.out.println("\nBuffer size: "+buffer.getCount());

+

+        // fill in the rest of the buffer with degenerates, there should only be a couple

+        for (int i = buffer.getCount(); i < numIndexes; i++) {

+            buffer.put(getWidth() * getWidth() - 1);

+        }

+

+        return buffer.delegate;

+    }

+

+    public IntBuffer writeIndexArrayLodVariable(IntBuffer store, int lod, int rightLod, int topLod, int leftLod, int bottomLod) {

+

+        IntBuffer buffer2 = store;

+        int numIndexes = calculateNumIndexesLodDiff(lod);

+        if (store == null) {

+            buffer2 = BufferUtils.createIntBuffer(numIndexes);

+        }

+        VerboseIntBuffer buffer = new VerboseIntBuffer(buffer2);

+

+

+        // generate center squares minus the edges

+        //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")");

+        //System.out.println("	for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")");

+        for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row

+            int rowIdx = r * getWidth();

+            int nextRowIdx = (r + 1 * lod) * getWidth();

+            for (int c = lod; c < getWidth() - (1 * lod); c += lod) { // column

+                int idx = rowIdx + c;

+                buffer.put(idx);

+                idx = nextRowIdx + c;

+                buffer.put(idx);

+            }

+

+            // add degenerate triangles

+            if (r < getWidth() - (3 * lod)) {

+                int idx = nextRowIdx + getWidth() - (1 * lod) - 1;

+                buffer.put(idx);

+                idx = nextRowIdx + (1 * lod); // inset by 1

+                buffer.put(idx);

+                //System.out.println("");

+            }

+        }

+        //System.out.println("\nright:");

+

+        //int runningBufferCount = buffer.getCount();

+        //System.out.println("buffer start: "+runningBufferCount);

+

+

+        // right

+        int br = getWidth() * (getWidth() - lod) - 1 - lod;

+        buffer.put(br); // bottom right -1

+        int corner = getWidth() * getWidth() - 1;

+        buffer.put(corner);	// bottom right corner

+        if (rightLod > lod) { // if lower LOD

+            int idx = corner;

+            int it = (getWidth() - 1) / rightLod; // iterations

+            int lodDiff = rightLod / lod;

+            for (int i = it; i > 0; i--) { // for each lod level of the neighbour

+                idx = getWidth() * (i * rightLod + 1) - 1;

+                for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level

+                    int idxB = idx - (getWidth() * (j * lod)) - lod;

+

+                    if (j == lodDiff && i == 1) {// the last one

+                        buffer.put(getWidth() - 1);

+                    } else if (j == lodDiff) {

+                        buffer.put(idxB);

+                        buffer.put(idxB + lod);

+                    } else {

+                        buffer.put(idxB);

+                        buffer.put(idx);

+                    }

+                }

+            }

+            // reset winding order

+            buffer.put(getWidth() * (lod + 1) - lod - 1); // top-right +1row

+            buffer.put(getWidth() - 1);// top-right

+

+        } else {

+            buffer.put(corner);//br+1);//degenerate to flip winding order

+            for (int row = getWidth() - lod; row > lod; row -= lod) {

+                int idx = row * getWidth() - 1; // mult to get row

+                buffer.put(idx);

+                buffer.put(idx - lod);

+            }

+            buffer.put(getWidth() - 1);

+        }

+

+

+        //System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+

+        //System.out.println("\ntop:");

+

+        // top 			(the order gets reversed here so the diagonals line up)

+        if (topLod > lod) { // if lower LOD

+            if (rightLod > lod) {

+                // need to flip winding order

+                buffer.put(getWidth() - 1);

+                buffer.put(getWidth() * lod - 1);

+                buffer.put(getWidth() - 1);

+            }

+            int idx = getWidth() - 1;

+            int it = (getWidth() - 1) / topLod; // iterations

+            int lodDiff = topLod / lod;

+            for (int i = it; i > 0; i--) { // for each lod level of the neighbour

+                idx = (i * topLod);

+                for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level

+                    int idxB = lod * getWidth() + (i * topLod) - (j * lod);

+

+                    if (j == lodDiff && i == 1) {// the last one

+                        buffer.put(0);

+                    } else if (j == lodDiff) {

+                        buffer.put(idxB);

+                        buffer.put(idx - topLod);

+                    } else {

+                        buffer.put(idxB);

+                        buffer.put(idx);

+                    }

+                }

+            }

+        } else {

+            if (rightLod > lod) {

+                buffer.put(getWidth() - 1);

+            }

+            for (int col = getWidth() - 1 - lod; col > 0; col -= lod) {

+                int idx = col + (lod * getWidth());

+                buffer.put(idx);

+                idx = col;

+                buffer.put(idx);

+            }

+            buffer.put(0);

+        }

+        buffer.put(0);

+

+        //System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //System.out.println("\nleft:");

+

+        // left

+        if (leftLod > lod) { // if lower LOD

+

+            int idx = 0;

+            int it = (getWidth() - 1) / leftLod; // iterations

+            int lodDiff = leftLod / lod;

+            for (int i = 0; i < it; i++) { // for each lod level of the neighbour

+                idx = getWidth() * (i * leftLod);

+                for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level

+                    int idxB = idx + (getWidth() * (j * lod)) + lod;

+

+                    if (j == lodDiff && i == it - 1) {// the last one

+                        buffer.put(getWidth() * getWidth() - getWidth());

+                    } else if (j == lodDiff) {

+                        buffer.put(idxB);

+                        buffer.put(idxB - lod);

+                    } else {

+                        buffer.put(idxB);

+                        buffer.put(idx);

+                    }

+                }

+            }

+

+        } else {

+            buffer.put(0);

+            buffer.put(getWidth() * lod + lod);

+            buffer.put(0);

+            for (int row = lod; row < getWidth() - lod; row += lod) {

+                int idx = row * getWidth();

+                buffer.put(idx);

+                idx = row * getWidth() + lod;

+                buffer.put(idx);

+            }

+            buffer.put(getWidth() * (getWidth() - 1));

+        }

+        //buffer.put(getWidth()*(getWidth()-1));

+

+

+        //System.out.println("\nbuffer left: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //if (true) return buffer.delegate;

+        //System.out.println("\nbottom");

+

+        // bottom

+        if (bottomLod > lod) { // if lower LOD

+            if (leftLod > lod) {

+                buffer.put(getWidth() * (getWidth() - 1));

+                buffer.put(getWidth() * (getWidth() - lod));

+                buffer.put(getWidth() * (getWidth() - 1));

+            }

+

+            int idx = getWidth() * getWidth() - getWidth();

+            int it = (getWidth() - 1) / bottomLod; // iterations

+            int lodDiff = bottomLod / lod;

+            for (int i = 0; i < it; i++) { // for each lod level of the neighbour

+                idx = getWidth() * getWidth() - getWidth() + (i * bottomLod);

+                for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level

+                    int idxB = idx - (getWidth() * lod) + j * lod;

+

+                    if (j == lodDiff && i == it - 1) {// the last one

+                        buffer.put(getWidth() * getWidth() - 1);

+                    } else if (j == lodDiff) {

+                        buffer.put(idxB);

+                        buffer.put(idx + bottomLod);

+                    } else {

+                        buffer.put(idxB);

+                        buffer.put(idx);

+                    }

+                }

+            }

+        } else {

+            if (leftLod > lod) {

+                buffer.put(getWidth() * (getWidth() - 1));

+                buffer.put(getWidth() * getWidth() - (getWidth() * lod) + lod);

+                buffer.put(getWidth() * (getWidth() - 1));

+            }

+            for (int col = lod; col < getWidth() - lod; col += lod) {

+                int idx = getWidth() * (getWidth() - 1 - lod) + col; // up

+                buffer.put(idx);

+                idx = getWidth() * (getWidth() - 1) + col; // down

+                buffer.put(idx);

+            }

+            //buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end!

+        }

+

+        buffer.put(getWidth() * getWidth() - 1);

+

+        //System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount));

+        //runningBufferCount = buffer.getCount();

+

+        //System.out.println("\nBuffer size: "+buffer.getCount());

+

+        // fill in the rest of the buffer with degenerates, there should only be a couple

+        for (int i = buffer.getCount(); i < numIndexes; i++) {

+            buffer.put(getWidth() * getWidth() - 1);

+        }

+

+        return buffer.delegate;

+    }

+

+

+    /*private int calculateNumIndexesNormal(int lod) {

+    int length = getWidth()-1;

+    int num = ((length/lod)+1)*((length/lod)+1)*2;

+    System.out.println("num: "+num);

+    num -= 2*((length/lod)+1);

+    System.out.println("num2: "+num);

+    // now get the degenerate indexes that exist between strip rows

+    num += 2*(((length/lod)+1)-2); // every row except the first and last

+    System.out.println("Index buffer size: "+num);

+    return num;

+    }*/

+    /**

+     * calculate how many indexes there will be.

+     * This isn't that precise and there might be a couple extra.

+     */

+    private int calculateNumIndexesLodDiff(int lod) {

+        if (lod == 0) {

+            lod = 1;

+        }

+        int length = getWidth() - 1; // make it even for lod calc

+        int side = (length / lod) + 1 - (2);

+        //System.out.println("side: "+side);

+        int num = side * side * 2;

+        //System.out.println("num: "+num);

+        num -= 2 * side;	// remove one first row and one last row (they are only hit once each)

+        //System.out.println("num2: "+num);

+        // now get the degenerate indexes that exist between strip rows

+        int degenerates = 2 * (side - (2)); // every row except the first and last

+        num += degenerates;

+        //System.out.println("degenerates: "+degenerates);

+

+        //System.out.println("center, before edges: "+num);

+

+        num += (getWidth() / lod) * 2 * 4;

+        num++;

+

+        num += 10;// TODO remove me: extra

+        //System.out.println("Index buffer size: "+num);

+        return num;

+    }

+

+    public FloatBuffer[] writeTangentArray(FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) {

+        if (!isLoaded()) {

+            throw new NullPointerException();

+        }

+

+        if (tangentStore != null) {

+            if (tangentStore.remaining() < getWidth() * getHeight() * 3) {

+                throw new BufferUnderflowException();

+            }

+        } else {

+            tangentStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);

+        }

+        tangentStore.rewind();

+

+        if (binormalStore != null) {

+            if (binormalStore.remaining() < getWidth() * getHeight() * 3) {

+                throw new BufferUnderflowException();

+            }

+        } else {

+            binormalStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);

+        }

+        binormalStore.rewind();

+

+        Vector3f tangent = new Vector3f();

+        Vector3f binormal = new Vector3f();

+        Vector3f v1 = new Vector3f();

+        Vector3f v2 = new Vector3f();

+        Vector3f v3 = new Vector3f();

+        Vector2f t1 = new Vector2f();

+        Vector2f t2 = new Vector2f();

+        Vector2f t3 = new Vector2f();

+

+        //scale = Vector3f.UNIT_XYZ;

+

+        for (int r = 0; r < getHeight(); r++) {

+            for (int c = 0; c < getWidth(); c++) {

+

+                int texIdx = ((getHeight() - 1 - r) * getWidth() + c) * 2; // pull from the end

+                int texIdxAbove = ((getHeight() - 1 - (r - 1)) * getWidth() + c) * 2; // pull from the end

+                int texIdxNext = ((getHeight() - 1 - (r + 1)) * getWidth() + c) * 2; // pull from the end

+

+                v1.set(c, getValue(c, r), r);

+                t1.set(textureBuffer.get(texIdx), textureBuffer.get(texIdx + 1));

+

+                // below

+                if (r == getHeight()-1) { // last row

+                    v3.set(c, getValue(c, r), r + 1);

+                    float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdxAbove);

+                    u += textureBuffer.get(texIdx);

+                    float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdxAbove + 1);

+                    v += textureBuffer.get(texIdx + 1);

+                    t3.set(u, v);

+                } else {

+                    v3.set(c, getValue(c, r + 1), r + 1);

+                    t3.set(textureBuffer.get(texIdxNext), textureBuffer.get(texIdxNext + 1));

+                }

+                

+                //right

+                if (c == getWidth()-1) { // last column

+                    v2.set(c + 1, getValue(c, r), r);

+                    float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdx - 2);

+                    u += textureBuffer.get(texIdx);

+                    float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdx - 1);

+                    v += textureBuffer.get(texIdx - 1);

+                    t2.set(u, v);

+                } else {

+                    v2.set(c + 1, getValue(c + 1, r), r); // one to the right

+                    t2.set(textureBuffer.get(texIdx + 2), textureBuffer.get(texIdx + 3));

+                }

+

+                calculateTangent(new Vector3f[]{v1.mult(scale), v2.mult(scale), v3.mult(scale)}, new Vector2f[]{t1, t2, t3}, tangent, binormal);

+                BufferUtils.setInBuffer(tangent, tangentStore, (r * getWidth() + c)); // save the tangent

+                BufferUtils.setInBuffer(binormal, binormalStore, (r * getWidth() + c)); // save the binormal

+            }

+        }

+

+        return new FloatBuffer[]{tangentStore, binormalStore};

+    }

+

+    /**

+     * 

+     * @param v Takes 3 vertices: root, right, bottom

+     * @param t Takes 3 tex coords: root, right, bottom

+     * @param tangent that will store the result

+     * @return the tangent store

+     */

+    public static Vector3f calculateTangent(Vector3f[] v, Vector2f[] t, Vector3f tangent, Vector3f binormal) {

+        Vector3f edge1 = new Vector3f(); // y=0

+        Vector3f edge2 = new Vector3f(); // x=0

+        Vector2f edge1uv = new Vector2f(); // y=0

+        Vector2f edge2uv = new Vector2f(); // x=0

+

+        t[2].subtract(t[0], edge2uv);

+        t[1].subtract(t[0], edge1uv);

+

+        float det = edge1uv.x * edge2uv.y;// - edge1uv.y*edge2uv.x;  = 0

+

+        boolean normalize = true;

+        if (Math.abs(det) < 0.0000001f) {

+            det = 1;

+            normalize = true;

+        }

+

+        v[1].subtract(v[0], edge1);

+        v[2].subtract(v[0], edge2);

+

+        tangent.set(edge1);

+        tangent.normalizeLocal();

+        binormal.set(edge2);

+        binormal.normalizeLocal();

+

+        float factor = 1 / det;

+        tangent.x = (edge2uv.y * edge1.x) * factor;

+        tangent.y = 0;

+        tangent.z = (edge2uv.y * edge1.z) * factor;

+        if (normalize) {

+            tangent.normalizeLocal();

+        }

+

+        binormal.x = 0;

+        binormal.y = (edge1uv.x * edge2.y) * factor;

+        binormal.z = (edge1uv.x * edge2.z) * factor;

+        if (normalize) {

+            binormal.normalizeLocal();

+        }

+

+        return tangent;

+    }

+

+    @Override

+    public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {

+        if (!isLoaded()) {

+            throw new NullPointerException();

+        }

+

+        if (store != null) {

+            if (store.remaining() < getWidth() * getHeight() * 3) {

+                throw new BufferUnderflowException();

+            }

+        } else {

+            store = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);

+        }

+        store.rewind();

+

+        TempVars vars = TempVars.get();

+        

+        Vector3f rootPoint = vars.vect1;

+        Vector3f rightPoint = vars.vect2;

+        Vector3f leftPoint = vars.vect3;

+        Vector3f topPoint = vars.vect4;

+        Vector3f bottomPoint = vars.vect5;

+        

+        Vector3f tmp1 = vars.vect6;

+

+        // calculate normals for each polygon

+        for (int r = 0; r < getHeight(); r++) {

+            for (int c = 0; c < getWidth(); c++) {

+

+                rootPoint.set(c, getValue(c, r), r);

+                Vector3f normal = vars.vect8;

+

+                if (r == 0) { // first row

+                    if (c == 0) { // first column

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1);

+                        getNormal(bottomPoint, rootPoint, rightPoint, scale, normal);

+                    } else if (c == getWidth() - 1) { // last column

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1);

+                        getNormal(leftPoint, rootPoint, bottomPoint, scale, normal);

+                    } else { // all middle columns

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1);

+                        

+                        normal.set( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );

+                        normal.add( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );

+                        normal.normalizeLocal();

+                    }

+                } else if (r == getHeight() - 1) { // last row

+                    if (c == 0) { // first column

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        getNormal(rightPoint, rootPoint, topPoint, scale, normal);

+                    } else if (c == getWidth() - 1) { // last column

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        getNormal(topPoint, rootPoint, leftPoint, scale, normal);

+                    } else { // all middle columns

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        

+                        normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) );

+                        normal.add( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );

+                        normal.normalizeLocal();

+                    }

+                } else { // all middle rows

+                    if (c == 0) { // first column

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1);

+                        

+                        normal.set( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );

+                        normal.add( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );

+                        normal.normalizeLocal();

+                    } else if (c == getWidth() - 1) { // last column

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1); //XXX wrong

+

+                        normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) );

+                        normal.add( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );

+                        normal.normalizeLocal();

+                    } else { // all middle columns

+                        topPoint.set(c, getValue(c, r - 1), r - 1);

+                        leftPoint.set(c - 1, getValue(c - 1, r), r);

+                        rightPoint.set(c + 1, getValue(c + 1, r), r);

+                        bottomPoint.set(c, getValue(c, r + 1), r + 1);

+                        

+                        normal.set( getNormal(topPoint,  rootPoint, leftPoint, scale, tmp1 ) );

+                        normal.add( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );

+                        normal.add( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );

+                        normal.add( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );

+                        normal.normalizeLocal();

+                    }

+                }

+                

+                BufferUtils.setInBuffer(normal, store, (r * getWidth() + c)); // save the normal

+            }

+        }

+        vars.release();

+        

+        return store;

+    }

+

+    private Vector3f getNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint, Vector3f scale, Vector3f store) {

+        float x1 = firstPoint.x - rootPoint.x;

+        float y1 = firstPoint.y - rootPoint.y;

+        float z1 = firstPoint.z - rootPoint.z;

+        x1 *= scale.x;

+        y1 *= scale.y;

+        z1 *= scale.z;

+        float x2 = secondPoint.x - rootPoint.x;

+        float y2 = secondPoint.y - rootPoint.y;

+        float z2 = secondPoint.z - rootPoint.z;

+        x2 *= scale.x;

+        y2 *= scale.y;

+        z2 *= scale.z;

+        float x3 = (y1 * z2) - (z1 * y2);

+        float y3 = (z1 * x2) - (x1 * z2);

+        float z3 = (x1 * y2) - (y1 * x2);

+        

+        float inv = 1.0f / FastMath.sqrt(x3 * x3 + y3 * y3 + z3 * z3);

+        store.x = x3 * inv;

+        store.y = y3 * inv;

+        store.z = z3 * inv;

+        return store;

+        

+        /*store.set( firstPoint.subtractLocal(rootPoint).multLocal(scale).crossLocal(secondPoint.subtractLocal(rootPoint).multLocal(scale)).normalizeLocal() );

+        return store;*/

+        

+    }

+

+    /**

+     * Keeps a count of the number of indexes, good for debugging

+     */

+    public class VerboseIntBuffer {

+

+        private IntBuffer delegate;

+        int count = 0;

+

+        public VerboseIntBuffer(IntBuffer d) {

+            delegate = d;

+        }

+

+        public void put(int value) {

+            try {

+                delegate.put(value);

+                count++;

+            } catch (BufferOverflowException e) {

+                //System.out.println("err buffer size: "+delegate.capacity());

+            }

+        }

+

+        public int getCount() {

+            return count;

+        }

+    }

+

+    /**

+     * Get a representation of the underlying triangle at the given point,

+     * translated to world coordinates.

+     * 

+     * @param x local x coordinate

+     * @param z local z coordinate

+     * @return a triangle in world space not local space

+     */

+    protected Triangle getTriangleAtPoint(float x, float z, Vector3f scale, Vector3f translation) {

+        Triangle tri = getTriangleAtPoint(x, z);

+        if (tri != null) {

+            tri.get1().multLocal(scale).addLocal(translation);

+            tri.get2().multLocal(scale).addLocal(translation);

+            tri.get3().multLocal(scale).addLocal(translation);

+        }

+        return tri;

+    }

+

+    /**

+     * Get the two triangles that make up the grid section at the specified point,

+     * translated to world coordinates.

+     *

+     * @param x local x coordinate

+     * @param z local z coordinate

+     * @param scale

+     * @param translation

+     * @return two triangles in world space not local space

+     */

+    protected Triangle[] getGridTrianglesAtPoint(float x, float z, Vector3f scale, Vector3f translation) {

+        Triangle[] tris = getGridTrianglesAtPoint(x, z);

+        if (tris != null) {

+            tris[0].get1().multLocal(scale).addLocal(translation);

+            tris[0].get2().multLocal(scale).addLocal(translation);

+            tris[0].get3().multLocal(scale).addLocal(translation);

+            tris[1].get1().multLocal(scale).addLocal(translation);

+            tris[1].get2().multLocal(scale).addLocal(translation);

+            tris[1].get3().multLocal(scale).addLocal(translation);

+        }

+        return tris;

+    }

+

+    /**

+     * Get the two triangles that make up the grid section at the specified point.

+     *

+     * For every grid space there are two triangles oriented like this:

+     *  *----*

+     *  |a / |

+     *  | / b|

+     *  *----*

+     * The corners of the mesh have differently oriented triangles. The two

+     * corners that we have to special-case are the top left and bottom right

+     * corners. They are oriented inversely:

+     *  *----*

+     *  | \ b|

+     *  |a \ |

+     *  *----*

+     *

+     * @param x local x coordinate

+     * @param z local z coordinate

+     * @param scale

+     * @param translation

+     * @return

+     */

+    protected Triangle[] getGridTrianglesAtPoint(float x, float z) {

+        int gridX = (int) x;

+        int gridY = (int) z;

+

+        int index = findClosestHeightIndex(gridX, gridY);

+        if (index < 0) {

+            return null;

+        }

+

+        Triangle t = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());

+        Triangle t2 = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());

+

+        float h1 = hdata[index];                // top left

+        float h2 = hdata[index + 1];            // top right

+        float h3 = hdata[index + width];        // bottom left

+        float h4 = hdata[index + width + 1];    // bottom right

+

+

+        if ((gridX == 0 && gridY == 0) || (gridX == width - 1 && gridY == width - 1)) {

+            // top left or bottom right grid point

+            t.get(0).x = (gridX);

+            t.get(0).y = (h1);

+            t.get(0).z = (gridY);

+

+            t.get(1).x = (gridX);

+            t.get(1).y = (h3);

+            t.get(1).z = (gridY + 1);

+

+            t.get(2).x = (gridX + 1);

+            t.get(2).y = (h4);

+            t.get(2).z = (gridY + 1);

+

+            t2.get(0).x = (gridX);

+            t2.get(0).y = (h1);

+            t2.get(0).z = (gridY);

+

+            t2.get(1).x = (gridX + 1);

+            t2.get(1).y = (h4);

+            t2.get(1).z = (gridY + 1);

+

+            t2.get(2).x = (gridX + 1);

+            t2.get(2).y = (h2);

+            t2.get(2).z = (gridY);

+        } else {

+            // all other grid points

+            t.get(0).x = (gridX);

+            t.get(0).y = (h1);

+            t.get(0).z = (gridY);

+

+            t.get(1).x = (gridX);

+            t.get(1).y = (h3);

+            t.get(1).z = (gridY + 1);

+

+            t.get(2).x = (gridX + 1);

+            t.get(2).y = (h2);

+            t.get(2).z = (gridY);

+

+            t2.get(0).x = (gridX + 1);

+            t2.get(0).y = (h2);

+            t2.get(0).z = (gridY);

+

+            t2.get(1).x = (gridX);

+            t2.get(1).y = (h3);

+            t2.get(1).z = (gridY + 1);

+

+            t2.get(2).x = (gridX + 1);

+            t2.get(2).y = (h4);

+            t2.get(2).z = (gridY + 1);

+        }

+

+        return new Triangle[]{t, t2};

+    }

+

+    /**

+     * Get the triangle that the point is on.

+     * 

+     * @param x coordinate in local space to the geomap

+     * @param z coordinate in local space to the geomap

+     * @return triangle in local space to the geomap

+     */

+    protected Triangle getTriangleAtPoint(float x, float z) {

+        Triangle[] triangles = getGridTrianglesAtPoint(x, z);

+        if (triangles == null) {

+            //System.out.println("x,z: " + x + "," + z);

+            return null;

+        }

+        Vector2f point = new Vector2f(x, z);

+        Vector2f t1 = new Vector2f(triangles[0].get1().x, triangles[0].get1().z);

+        Vector2f t2 = new Vector2f(triangles[0].get2().x, triangles[0].get2().z);

+        Vector2f t3 = new Vector2f(triangles[0].get3().x, triangles[0].get3().z);

+

+        if (0 != FastMath.pointInsideTriangle(t1, t2, t3, point)) {

+            return triangles[0];

+        }

+

+        t1.set(triangles[1].get1().x, triangles[1].get1().z);

+        t1.set(triangles[1].get2().x, triangles[1].get2().z);

+        t1.set(triangles[1].get3().x, triangles[1].get3().z);

+

+        if (0 != FastMath.pointInsideTriangle(t1, t2, t3, point)) {

+            return triangles[1];

+        }

+

+        return null;

+    }

+

+    protected int findClosestHeightIndex(int x, int z) {

+

+        if (x < 0 || x >= width - 1) {

+            return -1;

+        }

+        if (z < 0 || z >= width - 1) {

+            return -1;

+        }

+

+        return z * width + x;

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java b/engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java
new file mode 100644
index 0000000..5d2fde3
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/LRUCache.java
@@ -0,0 +1,122 @@
+package com.jme3.terrain.geomipmap;

+

+// Copyright 2007 Christian d'Heureuse, Inventec Informatik AG, Zurich,

+// Switzerland

+// www.source-code.biz, www.inventec.ch/chdh

+//

+// This module is multi-licensed and may be used under the terms

+// of any of the following licenses:

+//

+// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal

+// LGPL, GNU Lesser General Public License, V2 or later,

+// http://www.gnu.org/licenses/lgpl.html

+// GPL, GNU General Public License, V2 or later,

+// http://www.gnu.org/licenses/gpl.html

+// AL, Apache License, V2.0 or later, http://www.apache.org/licenses

+// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php

+//

+// Please contact the author if you need another license.

+// This module is provided "as is", without warranties of any kind.

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.LinkedHashMap;

+import java.util.Map;

+

+/**

+ * An LRU cache, based on <code>LinkedHashMap</code>.

+ * 

+ * <p>

+ * This cache has a fixed maximum number of elements (<code>cacheSize</code>).

+ * If the cache is full and another entry is added, the LRU (least recently

+ * used) entry is dropped.

+ * 

+ * <p>

+ * This class is thread-safe. All methods of this class are synchronized.

+ * 

+ * <p>

+ * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>

+ * Multi-licensed: EPL / LGPL / GPL / AL / BSD.

+ */

+public class LRUCache<K, V> {

+

+    private static final float hashTableLoadFactor = 0.75f;

+    private LinkedHashMap<K, V> map;

+    private int cacheSize;

+

+    /**

+     * Creates a new LRU cache.

+     * 

+     * @param cacheSize

+     *            the maximum number of entries that will be kept in this cache.

+     */

+    public LRUCache(int cacheSize) {

+        this.cacheSize = cacheSize;

+        int hashTableCapacity = (int) Math.ceil(cacheSize / LRUCache.hashTableLoadFactor) + 1;

+        this.map = new LinkedHashMap<K, V>(hashTableCapacity, LRUCache.hashTableLoadFactor, true) {

+            // (an anonymous inner class)

+

+            private static final long serialVersionUID = 1;

+

+            @Override

+            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {

+                return this.size() > LRUCache.this.cacheSize;

+            }

+        };

+    }

+

+    /**

+     * Retrieves an entry from the cache.<br>

+     * The retrieved entry becomes the MRU (most recently used) entry.

+     * 

+     * @param key

+     *            the key whose associated value is to be returned.

+     * @return the value associated to this key, or null if no value with this

+     *         key exists in the cache.

+     */

+    public synchronized V get(K key) {

+        return this.map.get(key);

+    }

+

+    /**

+     * Adds an entry to this cache.

+     * The new entry becomes the MRU (most recently used) entry.

+     * If an entry with the specified key already exists in the cache, it is

+     * replaced by the new entry.

+     * If the cache is full, the LRU (least recently used) entry is removed from

+     * the cache.

+     * 

+     * @param key

+     *            the key with which the specified value is to be associated.

+     * @param value

+     *            a value to be associated with the specified key.

+     */

+    public synchronized void put(K key, V value) {

+        this.map.put(key, value);

+    }

+

+    /**

+     * Clears the cache.

+     */

+    public synchronized void clear() {

+        this.map.clear();

+    }

+

+    /**

+     * Returns the number of used entries in the cache.

+     * 

+     * @return the number of entries currently in the cache.

+     */

+    public synchronized int usedEntries() {

+        return this.map.size();

+    }

+

+    /**

+     * Returns a <code>Collection</code> that contains a copy of all cache

+     * entries.

+     * 

+     * @return a <code>Collection</code> with a copy of the cache content.

+     */

+    public synchronized Collection<Map.Entry<K, V>> getAll() {

+        return new ArrayList<Map.Entry<K, V>>(this.map.entrySet());

+    }

+} // end class LRUCache

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/engine/src/terrain/com/jme3/terrain/geomipmap/NormalRecalcControl.java
new file mode 100644
index 0000000..ba13e52
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/NormalRecalcControl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import java.io.IOException;
+
+
+/**
+ * Handles the normal vector updates when the terrain changes heights.
+ * @author bowens
+ */
+public class NormalRecalcControl extends AbstractControl {
+
+    private TerrainQuad terrain;
+
+    public NormalRecalcControl(){}
+
+    public NormalRecalcControl(TerrainQuad terrain) {
+        this.terrain = terrain;
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        terrain.updateNormals();
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        NormalRecalcControl control = new NormalRecalcControl(terrain);
+        control.setSpatial(spatial);
+        control.setEnabled(true);
+        return control;
+    }
+    
+    @Override
+    public void setSpatial(Spatial spatial) {
+        super.setSpatial(spatial);
+        if (spatial instanceof TerrainQuad)
+            this.terrain = (TerrainQuad)spatial;
+    }
+
+    public TerrainQuad getTerrain() {
+        return terrain;
+    }
+
+    public void setTerrain(TerrainQuad terrain) {
+        this.terrain = terrain;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(terrain, "terrain", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        terrain = (TerrainQuad) ic.readSavable("terrain", null);
+    }
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java
new file mode 100644
index 0000000..502702e
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGrid.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.UpdateControl;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
+import com.jme3.terrain.heightmap.HeightMap;
+import com.jme3.terrain.heightmap.HeightMapGrid;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.
+ * 
+ * The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0.
+ * TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its
+ * updates as well. It does this in the overwritten update() method.
+ * 
+ * It uses an LRU (Least Recently Used) cache of 16 terrain tiles (full TerrainQuadTrees). The
+ * center 4 are the ones that are visible. As the camera moves, it checks what camera cell it is in
+ * and will attach the now visible tiles.
+ * 
+ * The 'quadIndex' variable is a 4x4 array that represents the tiles. The center 
+ * four (index numbers: 5, 6, 9, 10) are what is visible. Each quadIndex value is an
+ * offset vector. The vector contains whole numbers and represents how many tiles in offset
+ * this location is from the center of the map. So for example the index 11 [Vector3f(2, 0, 1)]
+ * is located 2*terrainSize in X axis and 1*terrainSize in Z axis.
+ * 
+ * As the camera moves, it tests what cameraCell it is in. Each camera cell covers four quad tiles
+ * and is half way inside each one.
+ * 
+ * +-------+-------+
+ * | 1     |     4 |    Four terrainQuads that make up the grid
+ * |    *..|..*    |    with the cameraCell in the middle, covering
+ * |----|--|--|----|    all four quads.
+ * |    *..|..*    |
+ * | 2     |     3 |
+ * +-------+-------+
+ * 
+ * This results in the effect of when the camera gets half way across one of the sides of a quad to
+ * an empty (non-loaded) area, it will trigger the system to load in the next tiles.
+ * 
+ * The tile loading is done on a background thread, and once the tile is loaded, then it is
+ * attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from
+ * the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile.
+ * 
+ * The loading of new tiles triggers events for any TerrainGridListeners. The events are:
+ *  -tile Attached
+ *  -tile Detached
+ *  -grid moved.
+ * 
+ * These allow physics to update, and other operation (often needed for loading the terrain) to occur
+ * at the right time.
+ * 
+ * @author Anthyon
+ */
+public class TerrainGrid extends TerrainQuad {
+
+    protected static final Logger log = Logger.getLogger(TerrainGrid.class.getCanonicalName());
+    protected Vector3f currentCamCell = Vector3f.ZERO;
+    protected int quarterSize; // half of quadSize
+    protected int quadSize;
+    protected HeightMapGrid heightMapGrid;
+    private TerrainGridTileLoader gridTileLoader;
+    protected Vector3f[] quadIndex;
+    protected Set<TerrainGridListener> listeners = new HashSet<TerrainGridListener>();
+    protected Material material;
+    protected LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<Vector3f, TerrainQuad>(16);
+    private int cellsLoaded = 0;
+    private int[] gridOffset;
+    private boolean runOnce = false;
+
+    protected class UpdateQuadCache implements Runnable {
+
+        protected final Vector3f location;
+
+        public UpdateQuadCache(Vector3f location) {
+            this.location = location;
+        }
+
+        /**
+         * This is executed if the camera has moved into a new CameraCell and will load in
+         * the new TerrainQuad tiles to be children of this TerrainGrid parent.
+         * It will first check the LRU cache to see if the terrain tile is already there,
+         * if it is not there, it will load it in and then cache that tile.
+         * The terrain tiles get added to the quad tree back on the OGL thread using the
+         * attachQuadAt() method. It also resets any cached values in TerrainQuad (such as
+         * neighbours).
+         */
+        public void run() {
+            for (int i = 0; i < 4; i++) {
+                for (int j = 0; j < 4; j++) {
+                    int quadIdx = i * 4 + j;
+                    final Vector3f quadCell = location.add(quadIndex[quadIdx]);
+                    TerrainQuad q = cache.get(quadCell);
+                    if (q == null) {
+                        if (heightMapGrid != null) {
+                            // create the new Quad since it doesn't exist
+                            HeightMap heightMapAt = heightMapGrid.getHeightMapAt(quadCell);
+                            q = new TerrainQuad(getName() + "Quad" + quadCell, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+                            q.setMaterial(material.clone());
+                            log.log(Level.FINE, "Loaded TerrainQuad {0} from HeightMapGrid", q.getName());
+                        } else if (gridTileLoader != null) {
+                            q = gridTileLoader.getTerrainQuadAt(quadCell);
+                            // only clone the material to the quad if it doesn't have a material of its own
+                            if(q.getMaterial()==null) q.setMaterial(material.clone());
+                            log.log(Level.FINE, "Loaded TerrainQuad {0} from TerrainQuadGrid", q.getName());
+                        }
+                    }
+                    cache.put(quadCell, q);
+
+                    if (isCenter(quadIdx)) {
+                        // if it should be attached as a child right now, attach it
+                        final int quadrant = getQuadrant(quadIdx);
+                        final TerrainQuad newQuad = q;
+                        // back on the OpenGL thread:
+                        getControl(UpdateControl.class).enqueue(new Callable() {
+
+                            public Object call() throws Exception {
+                                attachQuadAt(newQuad, quadrant, quadCell);
+                                //newQuad.resetCachedNeighbours();
+                                return null;
+                            }
+                        });
+                    }
+                }
+            }
+
+        }
+    }
+
+    protected boolean isCenter(int quadIndex) {
+        return quadIndex == 9 || quadIndex == 5 || quadIndex == 10 || quadIndex == 6;
+    }
+
+    protected int getQuadrant(int quadIndex) {
+        if (quadIndex == 5) {
+            return 1;
+        } else if (quadIndex == 9) {
+            return 2;
+        } else if (quadIndex == 6) {
+            return 3;
+        } else if (quadIndex == 10) {
+            return 4;
+        }
+        return 0; // error
+    }
+
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid,
+            Vector2f offset, float offsetAmount) {
+        this.name = name;
+        this.patchSize = patchSize;
+        this.size = maxVisibleSize;
+        this.stepScale = scale;
+        this.offset = offset;
+        this.offsetAmount = offsetAmount;
+        initData();
+        this.gridTileLoader = terrainQuadGrid;
+        terrainQuadGrid.setPatchSize(this.patchSize);
+        terrainQuadGrid.setQuadSize(this.quadSize);
+        addControl(new UpdateControl());
+    }
+
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid) {
+        this(name, patchSize, maxVisibleSize, scale, terrainQuadGrid, new Vector2f(), 0);
+    }
+
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, TerrainGridTileLoader terrainQuadGrid) {
+        this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, terrainQuadGrid);
+    }
+
+    @Deprecated
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, HeightMapGrid heightMapGrid,
+            Vector2f offset, float offsetAmount) {
+        this.name = name;
+        this.patchSize = patchSize;
+        this.size = maxVisibleSize;
+        this.stepScale = scale;
+        this.offset = offset;
+        this.offsetAmount = offsetAmount;
+        initData();
+        this.heightMapGrid = heightMapGrid;
+        heightMapGrid.setSize(this.quadSize);
+        addControl(new UpdateControl());
+    }
+
+    @Deprecated
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, HeightMapGrid heightMapGrid) {
+        this(name, patchSize, maxVisibleSize, scale, heightMapGrid, new Vector2f(), 0);
+    }
+
+    @Deprecated
+    public TerrainGrid(String name, int patchSize, int maxVisibleSize, HeightMapGrid heightMapGrid) {
+        this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, heightMapGrid);
+    }
+
+    public TerrainGrid() {
+    }
+
+    private void initData() {
+        int maxVisibleSize = size;
+        this.quarterSize = maxVisibleSize >> 2;
+        this.quadSize = (maxVisibleSize + 1) >> 1;
+        this.totalSize = maxVisibleSize;
+        this.gridOffset = new int[]{0, 0};
+
+        /*
+         *        -z
+         *         | 
+         *        1|3 
+         *  -x ----+---- x
+         *        2|4
+         *         |
+         *         z
+         */
+        this.quadIndex = new Vector3f[]{
+            new Vector3f(-1, 0, -1), new Vector3f(0, 0, -1), new Vector3f(1, 0, -1), new Vector3f(2, 0, -1),
+            new Vector3f(-1, 0, 0), new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(2, 0, 0),
+            new Vector3f(-1, 0, 1), new Vector3f(0, 0, 1), new Vector3f(1, 0, 1), new Vector3f(2, 0, 1),
+            new Vector3f(-1, 0, 2), new Vector3f(0, 0, 2), new Vector3f(1, 0, 2), new Vector3f(2, 0, 2)};
+
+    }
+
+    /**
+     * @deprecated not needed to be called any more, handled automatically
+     */
+    public void initialize(Vector3f location) {
+        if (this.material == null) {
+            throw new RuntimeException("Material must be set prior to call of initialize");
+        }
+        Vector3f camCell = this.getCamCell(location);
+        this.updateChildren(camCell);
+        for (TerrainGridListener l : this.listeners) {
+            l.gridMoved(camCell);
+        }
+    }
+
+    @Override
+    public void update(List<Vector3f> locations, LodCalculator lodCalculator) {
+        // for now, only the first camera is handled.
+        // to accept more, there are two ways:
+        // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed
+        // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore
+        Vector3f cam = locations.isEmpty() ? Vector3f.ZERO.clone() : locations.get(0);
+        Vector3f camCell = this.getCamCell(cam); // get the grid index value of where the camera is (ie. 2,1)
+        if (cellsLoaded > 1) {                  // Check if cells are updated before updating gridoffset.
+            gridOffset[0] = Math.round(camCell.x * (size / 2));
+            gridOffset[1] = Math.round(camCell.z * (size / 2));
+            cellsLoaded = 0;
+        }
+        if (camCell.x != this.currentCamCell.x || camCell.z != currentCamCell.z || !runOnce) {
+            // if the camera has moved into a new cell, load new terrain into the visible 4 center quads
+            this.updateChildren(camCell);
+            for (TerrainGridListener l : this.listeners) {
+                l.gridMoved(camCell);
+            }
+        }
+        runOnce = true;
+        super.update(locations, lodCalculator);
+    }
+
+    public Vector3f getCamCell(Vector3f location) {
+        Vector3f tile = getTileCell(location);
+        Vector3f offsetHalf = new Vector3f(-0.5f, 0, -0.5f);
+        Vector3f shifted = tile.subtract(offsetHalf);
+        return new Vector3f(FastMath.floor(shifted.x), 0, FastMath.floor(shifted.z));
+    }
+
+    /**
+     * Centered at 0,0.
+     * Get the tile index location in integer form:
+     * @param location world coordinate
+     */
+    public Vector3f getTileCell(Vector3f location) {
+        Vector3f tileLoc = location.divide(this.getWorldScale().mult(this.quadSize));
+        return tileLoc;
+    }
+
+    public TerrainGridTileLoader getGridTileLoader() {
+        return gridTileLoader;
+    }
+    
+    protected void removeQuad(int idx) {
+        if (this.getQuad(idx) != null) {
+            for (TerrainGridListener l : listeners) {
+                l.tileDetached(getTileCell(this.getQuad(idx).getWorldTranslation()), this.getQuad(idx));
+            }
+            this.detachChild(this.getQuad(idx));
+            cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this.
+        }
+    }
+
+    /**
+     * Runs on the rendering thread
+     */
+    protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f quadCell) {
+        this.removeQuad(quadrant);
+
+        q.setQuadrant((short) quadrant);
+        this.attachChild(q);
+
+        Vector3f loc = quadCell.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now
+        q.setLocalTranslation(loc);
+
+        for (TerrainGridListener l : listeners) {
+            l.tileAttached(quadCell, q);
+        }
+        updateModelBound();
+        
+        for (Spatial s : getChildren()) {
+            if (s instanceof TerrainQuad) {
+                TerrainQuad tq = (TerrainQuad)s;
+                tq.resetCachedNeighbours();
+                tq.fixNormalEdges(new BoundingBox(tq.getWorldTranslation(), totalSize*2, Float.MAX_VALUE, totalSize*2));
+            }
+        }
+    }
+
+    @Deprecated
+    /**
+     * @Deprecated, use updateChildren
+     */
+    protected void updateChildrens(Vector3f camCell) {
+        updateChildren(camCell);
+    }
+    
+    /**
+     * Called when the camera has moved into a new cell. We need to
+     * update what quads are in the scene now.
+     * 
+     * Step 1: touch cache
+     * LRU cache is used, so elements that need to remain
+     * should be touched.
+     *
+     * Step 2: load new quads in background thread
+     * if the camera has moved into a new cell, we load in new quads
+     * @param camCell the cell the camera is in
+     */
+    protected void updateChildren(Vector3f camCell) {
+
+        int dx = 0;
+        int dy = 0;
+        if (currentCamCell != null) {
+            dx = (int) (camCell.x - currentCamCell.x);
+            dy = (int) (camCell.z - currentCamCell.z);
+        }
+
+        int xMin = 0;
+        int xMax = 4;
+        int yMin = 0;
+        int yMax = 4;
+        if (dx == -1) { // camera moved to -X direction
+            xMax = 3;
+        } else if (dx == 1) { // camera moved to +X direction
+            xMin = 1;
+        }
+
+        if (dy == -1) { // camera moved to -Y direction
+            yMax = 3;
+        } else if (dy == 1) { // camera moved to +Y direction
+            yMin = 1;
+        }
+
+        // Touch the items in the cache that we are and will be interested in.
+        // We activate cells in the direction we are moving. If we didn't move 
+        // either way in one of the axes (say X or Y axis) then they are all touched.
+        for (int i = yMin; i < yMax; i++) {
+            for (int j = xMin; j < xMax; j++) {
+                cache.get(camCell.add(quadIndex[i * 4 + j]));
+            }
+        }
+        // ---------------------------------------------------
+        // ---------------------------------------------------
+
+        if (executor == null) {
+            // use the same executor as the LODControl
+            executor = createExecutorService();
+        }
+
+        executor.submit(new UpdateQuadCache(camCell));
+
+        this.currentCamCell = camCell;
+    }
+
+    public void addListener(TerrainGridListener listener) {
+        this.listeners.add(listener);
+    }
+
+    public Vector3f getCurrentCell() {
+        return this.currentCamCell;
+    }
+
+    public void removeListener(TerrainGridListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    @Override
+    public void setMaterial(Material mat) {
+        this.material = mat;
+        super.setMaterial(mat);
+    }
+
+    public void setQuadSize(int quadSize) {
+        this.quadSize = quadSize;
+    }
+
+    @Override
+    public void adjustHeight(List<Vector2f> xz, List<Float> height) {
+        Vector3f currentGridLocation = getCurrentCell().mult(getLocalScale()).multLocal(quadSize - 1);
+        for (Vector2f vect : xz) {
+            vect.x -= currentGridLocation.x;
+            vect.y -= currentGridLocation.z;
+        }
+        super.adjustHeight(xz, height);
+    }
+
+    @Override
+    protected float getHeightmapHeight(int x, int z) {
+        return super.getHeightmapHeight(x - gridOffset[0], z - gridOffset[1]);
+    }
+    
+    @Override
+    public int getNumMajorSubdivisions() {
+        return 2;
+    }
+    
+    @Override
+    public Material getMaterial(Vector3f worldLocation) {
+        if (worldLocation == null)
+            return null;
+        Vector3f tileCell = getTileCell(worldLocation);
+        Terrain terrain = cache.get(tileCell);
+        if (terrain == null)
+            return null; // terrain not loaded for that cell yet!
+        return terrain.getMaterial(worldLocation);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule c = im.getCapsule(this);
+        name = c.readString("name", null);
+        size = c.readInt("size", 0);
+        patchSize = c.readInt("patchSize", 0);
+        stepScale = (Vector3f) c.readSavable("stepScale", null);
+        offset = (Vector2f) c.readSavable("offset", null);
+        offsetAmount = c.readFloat("offsetAmount", 0);
+        gridTileLoader = (TerrainGridTileLoader) c.readSavable("terrainQuadGrid", null);
+        material = (Material) c.readSavable("material", null);
+        initData();
+        if (gridTileLoader != null) {
+            gridTileLoader.setPatchSize(this.patchSize);
+            gridTileLoader.setQuadSize(this.quadSize);
+        }
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule c = ex.getCapsule(this);
+        c.write(gridTileLoader, "terrainQuadGrid", null);
+        c.write(size, "size", 0);
+        c.write(patchSize, "patchSize", 0);
+        c.write(stepScale, "stepScale", null);
+        c.write(offset, "offset", null);
+        c.write(offsetAmount, "offsetAmount", 0);
+        c.write(material, "material", null);
+    }
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridListener.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridListener.java
new file mode 100644
index 0000000..e972ee0
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap;
+
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author Anthyon
+ */
+public interface TerrainGridListener {
+
+    public void gridMoved(Vector3f newCenter);
+
+    public void tileAttached( Vector3f cell, TerrainQuad quad );
+
+    public void tileDetached( Vector3f cell, TerrainQuad quad );
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java
new file mode 100644
index 0000000..485f51f
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java
@@ -0,0 +1,21 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.geomipmap;
+
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author normenhansen
+ */
+public interface TerrainGridTileLoader extends Savable {
+
+    public TerrainQuad getTerrainQuadAt(Vector3f location);
+
+    public void setPatchSize(int patchSize);
+
+    public void setQuadSize(int quadSize);
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
new file mode 100644
index 0000000..aedd744
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainLodControl.java
@@ -0,0 +1,210 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.geomipmap;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.Camera;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.ViewPort;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.control.AbstractControl;

+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 java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+

+/**

+ * Tells the terrain to update its Level of Detail.

+ * It needs the cameras to do this, and there could possibly

+ * be several cameras in the scene, so it accepts a list

+ * of cameras.

+ * NOTE: right now it just uses the first camera passed in,

+ * in the future it will use all of them to determine what

+ * LOD to set.

+ *

+ * This control serializes, but it does not save the Camera reference.

+ * This camera reference has to be manually added in when you load the

+ * terrain to the scene!

+ * 

+ * @author Brent Owens

+ */

+public class TerrainLodControl extends AbstractControl {

+

+    private Terrain terrain;

+    private List<Camera> cameras;

+    private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();

+    private LodCalculator lodCalculator;

+    private boolean hasResetLod = false; // used when enabled is set to false

+

+    public TerrainLodControl() {

+    }

+

+    public TerrainLodControl(Terrain terrain, Camera camera) {

+        List<Camera> cams = new ArrayList<Camera>();

+        cams.add(camera);

+        this.terrain = terrain;

+        this.cameras = cams;

+        lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator

+    }

+    

+    /**

+     * Only uses the first camera right now.

+     * @param terrain to act upon (must be a Spatial)

+     * @param cameras one or more cameras to reference for LOD calc

+     */

+    public TerrainLodControl(Terrain terrain, List<Camera> cameras) {

+        this.terrain = terrain;

+        this.cameras = cameras;

+        lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator

+    }

+

+    @Override

+    protected void controlRender(RenderManager rm, ViewPort vp) {

+    }

+

+    @Override

+    public void update(float tpf) {

+        controlUpdate(tpf);

+    }

+    

+    @Override

+    protected void controlUpdate(float tpf) {

+        //list of cameras for when terrain supports multiple cameras (ie split screen)

+

+        if (lodCalculator == null)

+            return;

+        

+        if (!enabled) {

+            if (!hasResetLod) {

+                // this will get run once

+                hasResetLod = true;

+                lodCalculator.turnOffLod();

+            }

+        }

+        

+        if (cameras != null) {

+            if (cameraLocations.isEmpty() && !cameras.isEmpty()) {

+                for (Camera c : cameras) // populate them

+                {

+                    cameraLocations.add(c.getLocation());

+                }

+            }

+            terrain.update(cameraLocations, lodCalculator);

+        }

+    }

+

+    public Control cloneForSpatial(Spatial spatial) {

+        if (spatial instanceof Terrain) {

+            List<Camera> cameraClone = new ArrayList<Camera>();

+            if (cameras != null) {

+                for (Camera c : cameras) {

+                    cameraClone.add(c);

+                }

+            }

+            TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameraClone);

+            cloned.setLodCalculator(lodCalculator.clone());

+            return cloned;

+        }

+        return null;

+    }

+

+    public void setCamera(Camera camera) {

+        List<Camera> cams = new ArrayList<Camera>();

+        cams.add(camera);

+        setCameras(cams);

+    }

+    

+    public void setCameras(List<Camera> cameras) {

+        this.cameras = cameras;

+        cameraLocations.clear();

+        for (Camera c : cameras) {

+            cameraLocations.add(c.getLocation());

+        }

+    }

+

+    @Override

+    public void setSpatial(Spatial spatial) {

+        super.setSpatial(spatial);

+        if (spatial instanceof Terrain) {

+            this.terrain = (Terrain) spatial;

+        }

+    }

+

+    public void setTerrain(Terrain terrain) {

+        this.terrain = terrain;

+    }

+

+    public LodCalculator getLodCalculator() {

+        return lodCalculator;

+    }

+

+    public void setLodCalculator(LodCalculator lodCalculator) {

+        this.lodCalculator = lodCalculator;

+    }

+    

+    @Override

+    public void setEnabled(boolean enabled) {

+        this.enabled = enabled;

+        if (!enabled) {

+            // reset the lod levels to max detail for the terrain

+            hasResetLod = false;

+        } else {

+            hasResetLod = true;

+            lodCalculator.turnOnLod();

+        }

+    }

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        super.write(ex);

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write((Node)terrain, "terrain", null);

+        oc.write(lodCalculator, "lodCalculator", null);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule ic = im.getCapsule(this);

+        terrain = (Terrain) ic.readSavable("terrain", null);

+        lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());

+    }

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
new file mode 100644
index 0000000..40cf190
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
@@ -0,0 +1,983 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.terrain.geomipmap;

+

+import com.jme3.bounding.BoundingBox;

+import com.jme3.bounding.BoundingSphere;

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.collision.Collidable;

+import com.jme3.collision.CollisionResults;

+import com.jme3.collision.UnsupportedCollisionException;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.math.*;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Mesh;

+import com.jme3.scene.VertexBuffer;

+import com.jme3.scene.VertexBuffer.Type;

+import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight;

+import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil;

+import com.jme3.util.BufferUtils;

+import java.io.IOException;

+import java.nio.FloatBuffer;

+import java.nio.IntBuffer;

+import java.util.HashMap;

+import java.util.List;

+

+

+/**

+ * A terrain patch is a leaf in the terrain quad tree. It has a mesh that can change levels of detail (LOD)

+ * whenever the view point, or camera, changes. The actual terrain mesh is created by the LODGeomap class.

+ * That uses a geo-mipmapping algorithm to change the index buffer of the mesh.

+ * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate

+ * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost.

+ * 

+ * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different

+ * LOD. If this doesn't happen, you will see gaps.

+ * 

+ * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which

+ * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that

+ * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far.

+ * 

+ * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change 

+ * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, 

+ * then the LOD changes every 130 units away.

+ * 

+ * @author Brent Owens

+ */

+public class TerrainPatch extends Geometry {

+

+    protected LODGeomap geomap;

+    protected int lod = -1; // this terrain patch's LOD

+    private int maxLod = -1;

+    protected int previousLod = -1;

+    protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs

+

+    protected int size;

+

+    protected int totalSize;

+

+    protected short quadrant = 1;

+

+    // x/z step

+    protected Vector3f stepScale;

+

+    // center of the patch in relation to (0,0,0)

+    protected Vector2f offset;

+

+    // amount the patch has been shifted.

+    protected float offsetAmount;

+

+    //protected LodCalculator lodCalculator;

+    //protected LodCalculatorFactory lodCalculatorFactory;

+

+    protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour;

+    protected boolean searchedForNeighboursAlready = false;

+

+

+    protected float[] lodEntropy;

+

+    public TerrainPatch() {

+        super("TerrainPatch");

+    }

+    

+    public TerrainPatch(String name) {

+        super(name);

+    }

+

+    public TerrainPatch(String name, int size) {

+        this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0));

+    }

+

+    /**

+     * Constructor instantiates a new <code>TerrainPatch</code> object. The

+     * parameters and heightmap data are then processed to generate a

+     * <code>TriMesh</code> object for rendering.

+     *

+     * @param name

+     *			the name of the terrain patch.

+     * @param size

+     *			the size of the heightmap.

+     * @param stepScale

+     *			the scale for the axes.

+     * @param heightMap

+     *			the height data.

+     * @param origin

+     *			the origin offset of the patch.

+     */

+    public TerrainPatch(String name, int size, Vector3f stepScale,

+                    float[] heightMap, Vector3f origin) {

+        this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0);

+    }

+

+    /**

+     * Constructor instantiates a new <code>TerrainPatch</code> object. The

+     * parameters and heightmap data are then processed to generate a

+     * <code>TriMesh</code> object for renderering.

+     *

+     * @param name

+     *			the name of the terrain patch.

+     * @param size

+     *			the size of the patch.

+     * @param stepScale

+     *			the scale for the axes.

+     * @param heightMap

+     *			the height data.

+     * @param origin

+     *			the origin offset of the patch.

+     * @param totalSize

+     *			the total size of the terrain. (Higher if the patch is part of

+     *			a <code>TerrainQuad</code> tree.

+     * @param offset

+     *			the offset for texture coordinates.

+     * @param offsetAmount

+     *			the total offset amount. Used for texture coordinates.

+     */

+    public TerrainPatch(String name, int size, Vector3f stepScale,

+                    float[] heightMap, Vector3f origin, int totalSize,

+                    Vector2f offset, float offsetAmount) {

+        super(name);

+        this.size = size;

+        this.stepScale = stepScale;

+        this.totalSize = totalSize;

+        this.offsetAmount = offsetAmount;

+        this.offset = offset;

+

+        setLocalTranslation(origin);

+

+        geomap = new LODGeomap(size, heightMap);

+        Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);

+        setMesh(m);

+

+    }

+

+    /**

+     * This calculation is slow, so don't use it often.

+     */

+    public void generateLodEntropies() {

+        float[] entropies = new float[getMaxLod()+1];

+        for (int i = 0; i <= getMaxLod(); i++){

+            int curLod = (int) Math.pow(2, i);

+            IntBuffer buf = geomap.writeIndexArrayLodDiff(null, curLod, false, false, false, false);

+            entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, buf);

+        }

+

+        lodEntropy = entropies;

+    }

+

+    public float[] getLodEntropies(){

+        if (lodEntropy == null){

+            generateLodEntropies();

+        }

+        return lodEntropy;

+    }

+

+    @Deprecated

+    public FloatBuffer getHeightmap() {

+        return BufferUtils.createFloatBuffer(geomap.getHeightArray());

+    }

+    

+    public float[] getHeightMap() {

+        return geomap.getHeightArray();

+    }

+

+    /**

+     * The maximum lod supported by this terrain patch.

+     * If the patch size is 32 then the returned value would be log2(32)-2 = 3

+     * You can then use that value, 3, to see how many times you can divide 32 by 2

+     * before the terrain gets too un-detailed (can't stitch it any further).

+     * @return

+     */

+    public int getMaxLod() {

+        if (maxLod < 0)

+            maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide

+

+        return maxLod;

+    }

+

+    protected void reIndexGeometry(HashMap<String,UpdatedTerrainPatch> updated, boolean useVariableLod) {

+

+        UpdatedTerrainPatch utp = updated.get(getName());

+

+        if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) {

+            int pow = (int) Math.pow(2, utp.getNewLod());

+            boolean left = utp.getLeftLod() > utp.getNewLod();

+            boolean top = utp.getTopLod() > utp.getNewLod();

+            boolean right = utp.getRightLod() > utp.getNewLod();

+            boolean bottom = utp.getBottomLod() > utp.getNewLod();

+

+            IntBuffer ib = null;

+            if (useVariableLod)

+                ib = geomap.writeIndexArrayLodVariable(null, pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()));

+            else

+                ib = geomap.writeIndexArrayLodDiff(null, pow, right, top, left, bottom);

+            utp.setNewIndexBuffer(ib);

+        }

+

+    }

+

+

+    public Vector2f getTex(float x, float z, Vector2f store) {

+        if (x < 0 || z < 0 || x >= size || z >= size) {

+            store.set(Vector2f.ZERO);

+            return store;

+        }

+        int idx = (int) (z * size + x);

+        return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2),

+                         getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) );

+    }

+    

+    public float getHeightmapHeight(float x, float z) {

+        if (x < 0 || z < 0 || x >= size || z >= size)

+            return 0;

+        int idx = (int) (z * size + x);

+        return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y

+    }

+    

+    /**

+     * Get the triangle of this geometry at the specified local coordinate.

+     * @param x local to the terrain patch

+     * @param z local to the terrain patch

+     * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis

+     */

+    public Triangle getTriangle(float x, float z) {

+        return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation());

+    }

+

+    /**

+     * Get the triangles at the specified grid point. Probably only 2 triangles

+     * @param x local to the terrain patch

+     * @param z local to the terrain patch

+     * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis

+     */

+    public Triangle[] getGridTriangles(float x, float z) {

+        return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation());

+    }

+

+    protected void setHeight(List<LocationHeight> locationHeights, boolean overrideHeight) {

+        

+        for (LocationHeight lh : locationHeights) {

+            if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size)

+                continue;

+            int idx = lh.z * size + lh.x;

+            if (overrideHeight) {

+                geomap.getHeightArray()[idx] = lh.h;

+            } else {

+                float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1);

+                geomap.getHeightArray()[idx] = h+lh.h;

+            }

+            

+        }

+

+        FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false);

+        getMesh().clearBuffer(Type.Position);

+        getMesh().setBuffer(Type.Position, 3, newVertexBuffer);

+    }

+

+    /**

+     * recalculate all of the normal vectors in this terrain patch

+     */

+    protected void updateNormals() {

+        FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale());

+        getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer);

+        FloatBuffer newTangentBuffer = null;

+        FloatBuffer newBinormalBuffer = null;

+        FloatBuffer[] tb = geomap.writeTangentArray(newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale());

+        newTangentBuffer = tb[0];

+        newBinormalBuffer = tb[1];

+        getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer);

+        getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer);

+    }

+

+    /**

+     * Matches the normals along the edge of the patch with the neighbours.

+     * Computes the normals for the right, bottom, left, and top edges of the

+     * patch, and saves those normals in the neighbour's edges too.

+     *

+     * Takes 4 points (if has neighbour on that side) for each

+     * point on the edge of the patch:

+     *              *

+     *              |

+     *          *---x---*

+     *              |

+     *              *

+     * It works across the right side of the patch, from the top down to 

+     * the bottom. Then it works on the bottom side of the patch, from the

+     * left to the right.

+     */

+    protected void fixNormalEdges(TerrainPatch right,

+                                TerrainPatch bottom,

+                                TerrainPatch top,

+                                TerrainPatch left,

+                                TerrainPatch bottomRight,

+                                TerrainPatch bottomLeft,

+                                TerrainPatch topRight,

+                                TerrainPatch topLeft)

+    {

+        Vector3f rootPoint = new Vector3f();

+        Vector3f rightPoint = new Vector3f();

+        Vector3f leftPoint = new Vector3f();

+        Vector3f topPoint = new Vector3f();

+

+        Vector3f bottomPoint = new Vector3f();

+

+        Vector3f tangent = new Vector3f();

+        Vector3f binormal = new Vector3f();

+        Vector3f normal = new Vector3f();

+

+        int s = this.getSize()-1;

+        

+        if (right != null) { // right side,    works its way down

+            for (int i=0; i<s+1; i++) {

+                rootPoint.set(s, this.getHeightmapHeight(s,i), i);

+                leftPoint.set(s-1, this.getHeightmapHeight(s-1,i), i);

+                rightPoint.set(s+1, right.getHeightmapHeight(1,i), i);

+

+                if (i == 0) { // top point

+                    if (top == null) {

+                        bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);

+                        

+                        averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);

+                    } else {

+                        topPoint.set(s, top.getHeightmapHeight(s,s-1), i-1);

+                        bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);

+                        

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);

+                        

+                        if (topRight != null) {

+                            VertexBuffer topRightNB = topRight.getMesh().getBuffer(Type.Normal);

+                            BufferUtils.setInBuffer(normal, (FloatBuffer)topRightNB.getData(), (s+1)*s);

+                            topRightNB.setUpdateNeeded();

+                        }

+                    }

+                } else if (i == s) { // bottom point

+                    if (bottom == null) {

+                        topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);

+                        

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(s));

+                    } else {

+                        topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);

+                        bottomPoint.set(s, bottom.getHeightmapHeight(s,1), i+1);

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);

+

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s+1)-1);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*s);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), s);

+                        

+                        if (bottomRight != null) {

+                            VertexBuffer bottomRightNB = bottomRight.getMesh().getBuffer(Type.Normal);

+                            BufferUtils.setInBuffer(normal, (FloatBuffer)bottomRightNB.getData(), 0);

+                            bottomRightNB.setUpdateNeeded();

+                        }

+                        downNB.setUpdateNeeded();

+                    }

+                } else { // all in the middle

+                    topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);

+                    bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);

+                    averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                    VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                    VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(i));

+                }

+            }

+            right.getMesh().getBuffer(Type.Normal).setUpdateNeeded();

+        }

+

+        if (left != null) { // left side,    works its way down

+            for (int i=0; i<s+1; i++) {

+                rootPoint.set(0, this.getHeightmapHeight(0,i), i);

+                leftPoint.set(-1, left.getHeightmapHeight(s-1,i), i);

+                rightPoint.set(1, this.getHeightmapHeight(1,i), i);

+                

+                if (i == 0) { // top point

+                    if (top == null) {

+                        bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);

+                        averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s);

+                    } else {

+                        topPoint.set(0, top.getHeightmapHeight(0,s-1), i-1);

+                        bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);

+                        

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s);

+                        

+                        if (topLeft != null) {

+                            VertexBuffer topLeftNB = topLeft.getMesh().getBuffer(Type.Normal);

+                            BufferUtils.setInBuffer(normal, (FloatBuffer)topLeftNB.getData(), (s+1)*(s+1)-1);

+                            topLeftNB.setUpdateNeeded();

+                        }

+                    }

+                } else if (i == s) { // bottom point

+                    if (bottom == null) {

+                        topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);

+                        

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s));

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);

+                    } else {

+                        topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);

+                        bottomPoint.set(0, bottom.getHeightmapHeight(0,1), i+1);

+                        

+                        averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                        VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);

+                        VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);

+

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s));

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);

+                        BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), 0);

+                        

+                        if (bottomLeft != null) {

+                            VertexBuffer bottomLeftNB = bottomLeft.getMesh().getBuffer(Type.Normal);

+                            BufferUtils.setInBuffer(normal, (FloatBuffer)bottomLeftNB.getData(), s);

+                            bottomLeftNB.setUpdateNeeded();

+                        }

+                        downNB.setUpdateNeeded();

+                    }

+                } else { // all in the middle

+                    topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);

+                    bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);

+                    

+                    averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                    VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                    VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i));

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);

+                }

+            }

+            left.getMesh().getBuffer(Type.Normal).setUpdateNeeded();

+        }

+        

+        if (top != null) { // top side,    works its way right

+            for (int i=0; i<s+1; i++) {

+                rootPoint.set(i, this.getHeightmapHeight(i,0), 0);

+                topPoint.set(i, top.getHeightmapHeight(i,s-1), -1);

+                bottomPoint.set(i, this.getHeightmapHeight(i,1), 1);

+                

+                if (i == 0) { // left corner

+                    // handled by left side pass

+                    

+                } else if (i == s) { // right corner

+                    

+                    // handled by this patch when it does its right side

+                    

+                } else { // all in the middle

+                    leftPoint.set(i-1, this.getHeightmapHeight(i-1,0), 0);

+                    rightPoint.set(i+1, this.getHeightmapHeight(i+1,0), 0);

+                    averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                    VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), i);

+                    VertexBuffer topNB = top.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)topNB.getData(), (s+1)*(s)+i);

+                }

+            }

+            top.getMesh().getBuffer(Type.Normal).setUpdateNeeded();

+            

+        }

+        

+        if (bottom != null) { // bottom side,    works its way right

+            for (int i=0; i<s+1; i++) {

+                rootPoint.set(i, this.getHeightmapHeight(i,s), s);

+                topPoint.set(i, this.getHeightmapHeight(i,s-1), s-1);

+                bottomPoint.set(i, bottom.getHeightmapHeight(i,1), s+1);

+

+                if (i == 0) { // left

+                    // handled by the left side pass

+                    

+                } else if (i == s) { // right

+                    

+                    // handled by this patch when it does its right side

+                    

+                } else { // all in the middle

+                    leftPoint.set(i-1, this.getHeightmapHeight(i-1,s), s);

+                    rightPoint.set(i+1, this.getHeightmapHeight(i+1,s), s);

+                    averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);

+                    VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s)+i);

+                    VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);

+                    BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), i);

+                }

+            }

+            bottom.getMesh().getBuffer(Type.Normal).setUpdateNeeded();

+            

+        }

+

+        this.getMesh().getBuffer(Type.Normal).setUpdateNeeded();

+        this.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();

+        this.getMesh().getBuffer(Type.Binormal).setUpdateNeeded();

+    }

+

+    protected void averageNormalsTangents(

+            Vector3f topPoint,

+            Vector3f rootPoint,

+            Vector3f leftPoint, 

+            Vector3f bottomPoint, 

+            Vector3f rightPoint,

+            Vector2f topTex,

+            Vector2f rootTex,

+            Vector2f leftTex,

+            Vector2f bottomTex,

+            Vector2f rightTex,

+            Vector3f normal,

+            Vector3f tangent,

+            Vector3f binormal)

+    {

+        Vector3f scale = getWorldScale();

+        

+        Vector3f n1 = Vector3f.ZERO;

+        if (topPoint != null && leftPoint != null) {

+            n1 = calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale));

+        }

+        Vector3f n2 = Vector3f.ZERO;

+        if (leftPoint != null && bottomPoint != null) {

+            n2 = calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale));

+        }

+        Vector3f n3 = Vector3f.ZERO;

+        if (rightPoint != null && bottomPoint != null) {

+            n3 = calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale));

+        }

+        Vector3f n4 = Vector3f.ZERO;

+        if (rightPoint != null && topPoint != null) {

+            n4 = calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale));

+        }

+        

+        if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null)

+            LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal);

+

+        normal.set(n1.add(n2).add(n3).add(n4).normalizeLocal());

+    }

+

+    private Vector3f calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint) {

+        Vector3f normal = new Vector3f();

+        normal.set(firstPoint).subtractLocal(rootPoint)

+                  .crossLocal(secondPoint.subtract(rootPoint)).normalizeLocal();

+        return normal;

+    }

+    

+    protected Vector3f getMeshNormal(int x, int z) {

+        if (x >= size || z >= size)

+            return null; // out of range

+        

+        int index = (z*size+x)*3;

+        FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData();

+        Vector3f normal = new Vector3f();

+        normal.x = nb.get(index);

+        normal.y = nb.get(index+1);

+        normal.z = nb.get(index+2);

+        return normal;

+    }

+

+    /**

+     * Locks the mesh (sets it static) to improve performance.

+     * But it it not editable then. Set unlock to make it editable.

+     */

+    public void lockMesh() {

+        getMesh().setStatic();

+    }

+

+    /**

+     * Unlocks the mesh (sets it dynamic) to make it editable.

+     * It will be editable but performance will be reduced.

+     * Call lockMesh to improve performance.

+     */

+    public void unlockMesh() {

+        getMesh().setDynamic();

+    }

+	

+    /**

+     * Returns the offset amount this terrain patch uses for textures.

+     *

+     * @return The current offset amount.

+     */

+    public float getOffsetAmount() {

+        return offsetAmount;

+    }

+

+    /**

+     * Returns the step scale that stretches the height map.

+     *

+     * @return The current step scale.

+     */

+    public Vector3f getStepScale() {

+        return stepScale;

+    }

+

+    /**

+     * Returns the total size of the terrain.

+     *

+     * @return The terrain's total size.

+     */

+    public int getTotalSize() {

+        return totalSize;

+    }

+

+    /**

+     * Returns the size of this terrain patch.

+     *

+     * @return The current patch size.

+     */

+    public int getSize() {

+        return size;

+    }

+

+    /**

+     * Returns the current offset amount. This is used when building texture

+     * coordinates.

+     *

+     * @return The current offset amount.

+     */

+    public Vector2f getOffset() {

+        return offset;

+    }

+

+    /**

+     * Sets the value for the current offset amount to use when building texture

+     * coordinates. Note that this does <b>NOT </b> rebuild the terrain at all.

+     * This is mostly used for outside constructors of terrain patches.

+     *

+     * @param offset

+     *			The new texture offset.

+     */

+    public void setOffset(Vector2f offset) {

+        this.offset = offset;

+    }

+

+    /**

+     * Sets the size of this terrain patch. Note that this does <b>NOT </b>

+     * rebuild the terrain at all. This is mostly used for outside constructors

+     * of terrain patches.

+     *

+     * @param size

+     *			The new size.

+     */

+    public void setSize(int size) {

+        this.size = size;

+

+        maxLod = -1; // reset it

+    }

+

+    /**

+     * Sets the total size of the terrain . Note that this does <b>NOT </b>

+     * rebuild the terrain at all. This is mostly used for outside constructors

+     * of terrain patches.

+     *

+     * @param totalSize

+     *			The new total size.

+     */

+    public void setTotalSize(int totalSize) {

+        this.totalSize = totalSize;

+    }

+

+    /**

+     * Sets the step scale of this terrain patch's height map. Note that this

+     * does <b>NOT </b> rebuild the terrain at all. This is mostly used for

+     * outside constructors of terrain patches.

+     *

+     * @param stepScale

+     *			The new step scale.

+     */

+    public void setStepScale(Vector3f stepScale) {

+        this.stepScale = stepScale;

+    }

+

+    /**

+     * Sets the offset of this terrain texture map. Note that this does <b>NOT

+     * </b> rebuild the terrain at all. This is mostly used for outside

+     * constructors of terrain patches.

+     *

+     * @param offsetAmount

+     *			The new texture offset.

+     */

+    public void setOffsetAmount(float offsetAmount) {

+        this.offsetAmount = offsetAmount;

+    }

+

+    /**

+     * @return Returns the quadrant.

+     */

+    public short getQuadrant() {

+        return quadrant;

+    }

+

+    /**

+     * @param quadrant

+     *			The quadrant to set.

+     */

+    public void setQuadrant(short quadrant) {

+        this.quadrant = quadrant;

+    }

+

+    public int getLod() {

+        return lod;

+    }

+

+    public void setLod(int lod) {

+        this.lod = lod;

+    }

+

+    public int getPreviousLod() {

+        return previousLod;

+    }

+

+    public void setPreviousLod(int previousLod) {

+        this.previousLod = previousLod;

+    }

+

+    protected int getLodLeft() {

+        return lodLeft;

+    }

+

+    protected void setLodLeft(int lodLeft) {

+        this.lodLeft = lodLeft;

+    }

+

+    protected int getLodTop() {

+        return lodTop;

+    }

+

+    protected void setLodTop(int lodTop) {

+        this.lodTop = lodTop;

+    }

+

+    protected int getLodRight() {

+        return lodRight;

+    }

+

+    protected void setLodRight(int lodRight) {

+        this.lodRight = lodRight;

+    }

+

+    protected int getLodBottom() {

+        return lodBottom;

+    }

+

+    protected void setLodBottom(int lodBottom) {

+        this.lodBottom = lodBottom;

+    }

+    

+    /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) {

+        this.lodCalculatorFactory = lodCalculatorFactory;

+        setLodCalculator(lodCalculatorFactory.createCalculator(this));

+    }*/

+

+    @Override

+    public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {

+        if (refreshFlags != 0)

+            throw new IllegalStateException("Scene graph must be updated" +

+                                            " before checking collision");

+

+        if (other instanceof BoundingVolume)

+            if (!getWorldBound().intersects((BoundingVolume)other))

+                return 0;

+        

+        if(other instanceof Ray)

+            return collideWithRay((Ray)other, results);

+        else if (other instanceof BoundingVolume)

+            return collideWithBoundingVolume((BoundingVolume)other, results);

+        else {

+            throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName());

+        }

+    }

+

+

+    private int collideWithRay(Ray ray, CollisionResults results) {

+        // This should be handled in the root terrain quad

+        return 0;

+    }

+

+    private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) {

+        if (boundingVolume instanceof BoundingBox)

+            return collideWithBoundingBox((BoundingBox)boundingVolume, results);

+        else if(boundingVolume instanceof BoundingSphere) {

+            BoundingSphere sphere = (BoundingSphere) boundingVolume;

+            BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(),

+                                                           sphere.getRadius(),

+                                                           sphere.getRadius());

+            return collideWithBoundingBox(bbox, results);

+        }

+        return 0;

+    }

+

+    protected Vector3f worldCoordinateToLocal(Vector3f loc) {

+        Vector3f translated = new Vector3f();

+        translated.x = loc.x/getWorldScale().x - getWorldTranslation().x;

+        translated.y = loc.y/getWorldScale().y - getWorldTranslation().y;

+        translated.z = loc.z/getWorldScale().z - getWorldTranslation().z;

+        return translated;

+    }

+

+    /**

+     * This most definitely is not optimized.

+     */

+    private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) {

+        

+        // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time

+        Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent()));

+        Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent()));

+        Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));

+        Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));

+

+        Triangle t = getTriangle(topLeft.x, topLeft.z);

+        if (t != null && bbox.collideWith(t, results) > 0)

+            return 1;

+        t = getTriangle(topRight.x, topRight.z);

+        if (t != null && bbox.collideWith(t, results) > 0)

+            return 1;

+        t = getTriangle(bottomLeft.x, bottomLeft.z);

+        if (t != null && bbox.collideWith(t, results) > 0)

+            return 1;

+        t = getTriangle(bottomRight.x, bottomRight.z);

+        if (t != null && bbox.collideWith(t, results) > 0)

+            return 1;

+        

+        // box is larger than the points on the terrain, so test against the points

+        for (float z=topLeft.z; z<bottomLeft.z; z+=1) {

+            for (float x=topLeft.x; x<topRight.x; x+=1) {

+                

+                if (x < 0 || z < 0 || x >= size || z >= size)

+                    continue;

+                t = getTriangle(x,z);

+                if (t != null && bbox.collideWith(t, results) > 0)

+                    return 1;

+            }

+        }

+

+        return 0;

+    }

+

+

+    @Override

+    public void write(JmeExporter ex) throws IOException {

+        // the mesh is removed, and reloaded when read() is called

+        // this reduces the save size to 10% by not saving the mesh

+        Mesh temp = getMesh();

+        mesh = null;

+        

+        super.write(ex);

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(size, "size", 16);

+        oc.write(totalSize, "totalSize", 16);

+        oc.write(quadrant, "quadrant", (short)0);

+        oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ);

+        oc.write(offset, "offset", Vector3f.UNIT_XYZ);

+        oc.write(offsetAmount, "offsetAmount", 0);

+        //oc.write(lodCalculator, "lodCalculator", null);

+        //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null);

+        oc.write(lodEntropy, "lodEntropy", null);

+        oc.write(geomap, "geomap", null);

+        

+        setMesh(temp);

+    }

+

+    @Override

+    public void read(JmeImporter im) throws IOException {

+        super.read(im);

+        InputCapsule ic = im.getCapsule(this);

+        size = ic.readInt("size", 16);

+        totalSize = ic.readInt("totalSize", 16);

+        quadrant = ic.readShort("quadrant", (short)0);

+        stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ);

+        offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ);

+        offsetAmount = ic.readFloat("offsetAmount", 0);

+        //lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());

+        //lodCalculator.setTerrainPatch(this);

+        //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null);

+        lodEntropy = ic.readFloatArray("lodEntropy", null);

+        geomap = (LODGeomap) ic.readSavable("geomap", null);

+        

+        Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);

+        setMesh(regen);

+        //TangentBinormalGenerator.generate(this); // note that this will be removed

+        ensurePositiveVolumeBBox();

+    }

+

+    @Override

+    public TerrainPatch clone() {

+        TerrainPatch clone = new TerrainPatch();

+        clone.name = name.toString();

+        clone.size = size;

+        clone.totalSize = totalSize;

+        clone.quadrant = quadrant;

+        clone.stepScale = stepScale.clone();

+        clone.offset = offset.clone();

+        clone.offsetAmount = offsetAmount;

+        //clone.lodCalculator = lodCalculator.clone();

+        //clone.lodCalculator.setTerrainPatch(clone);

+        //clone.setLodCalculator(lodCalculatorFactory.clone());

+        clone.geomap = new LODGeomap(size, geomap.getHeightArray());

+        clone.setLocalTranslation(getLocalTranslation().clone());

+        Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false);

+        clone.setMesh(m);

+        clone.setMaterial(material.clone());

+        return clone;

+    }

+

+    protected void ensurePositiveVolumeBBox() {

+        if (getModelBound() instanceof BoundingBox) {

+            if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) {

+                // a correction so the box always has a volume

+                ((BoundingBox)getModelBound()).setYExtent(0.001f);

+                updateWorldBound();

+            }

+        }

+    }

+

+

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java
new file mode 100644
index 0000000..0b9b218
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java
@@ -0,0 +1,1862 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+

+package com.jme3.terrain.geomipmap;

+

+import com.jme3.bounding.BoundingBox;

+import com.jme3.bounding.BoundingVolume;

+import com.jme3.collision.Collidable;

+import com.jme3.collision.CollisionResults;

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.material.Material;

+import com.jme3.math.FastMath;

+import com.jme3.math.Ray;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.debug.WireBox;

+import com.jme3.terrain.ProgressMonitor;

+import com.jme3.terrain.Terrain;

+import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;

+import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker;

+import com.jme3.terrain.geomipmap.picking.TerrainPickData;

+import com.jme3.terrain.geomipmap.picking.TerrainPicker;

+import com.jme3.util.TangentBinormalGenerator;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.concurrent.ExecutorService;

+import java.util.concurrent.Executors;

+import java.util.concurrent.ThreadFactory;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * A terrain quad is a node in the quad tree of the terrain system.

+ * The root terrain quad will be the only one that receives the update() call every frame

+ * and it will determine if there has been any LOD change.

+ *

+ * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh.

+ *

+ * 

+ * Heightmap coordinates start from the bottom left of the world and work towards the

+ * top right.

+ * 

+ *  +x

+ *  ^

+ *  | ......N = length of heightmap

+ *  | :     :

+ *  | :     :

+ *  | 0.....:

+ *  +---------> +z

+ * (world coordinates)

+ * 

+ * @author Brent Owens

+ */

+public class TerrainQuad extends Node implements Terrain {

+

+    protected Vector2f offset;

+

+    protected int totalSize; // the size of this entire terrain tree (on one side)

+

+    protected int size; // size of this quad, can be between totalSize and patchSize

+

+    protected int patchSize; // size of the individual patches

+

+    protected Vector3f stepScale;

+

+    protected float offsetAmount;

+

+    protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right

+

+    //protected LodCalculatorFactory lodCalculatorFactory;

+    //protected LodCalculator lodCalculator;

+    

+    protected List<Vector3f> lastCameraLocations; // used for LOD calc

+    private boolean lodCalcRunning = false;

+    private int lodOffCount = 0;

+    private int maxLod = -1;

+    private HashMap<String,UpdatedTerrainPatch> updatedPatches;

+    private final Object updatePatchesLock = new Object();

+    private BoundingBox affectedAreaBBox; // only set in the root quad

+

+    private TerrainPicker picker;

+    private Vector3f lastScale = Vector3f.UNIT_XYZ;

+

+    protected ExecutorService executor;

+

+    protected ExecutorService createExecutorService() {

+        return Executors.newSingleThreadExecutor(new ThreadFactory() {

+            public Thread newThread(Runnable r) {

+                Thread th = new Thread(r);

+                th.setName("jME Terrain Thread");

+                th.setDaemon(true);

+                return th;

+            }

+        });

+    }

+

+    public TerrainQuad() {

+        super("Terrain");

+    }

+

+    /**

+     * 

+     * @param name the name of the scene element. This is required for

+     * identification and comparison purposes.

+     * @param patchSize size of the individual patches

+     * @param totalSize the size of this entire terrain tree (on one side)

+     * @param heightMap The height map to generate the terrain from (a flat

+     * height map will be generated if this is null)

+     */

+    public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {

+        this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);

+    }

+    

+    /**

+     * 

+     * @param name the name of the scene element. This is required for

+     * identification and comparison purposes.

+     * @param patchSize size of the individual patches

+     * @param quadSize

+     * @param totalSize the size of this entire terrain tree (on one side)

+     * @param heightMap The height map to generate the terrain from (a flat

+     * height map will be generated if this is null)

+     */

+    public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) {

+        this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap);

+    }

+

+    /**

+     * 

+     * @param name the name of the scene element. This is required for

+     * identification and comparison purposes.

+     * @param patchSize size of the individual patches

+     * @param size size of this quad, can be between totalSize and patchSize

+     * @param scale

+     * @param heightMap The height map to generate the terrain from (a flat

+     * height map will be generated if this is null)

+     */

+    public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) {

+        this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0);

+        affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);

+        fixNormalEdges(affectedAreaBBox);

+        addControl(new NormalRecalcControl(this));

+    }

+    

+    /**

+     * 

+     * @param name the name of the scene element. This is required for

+     * identification and comparison purposes.

+     * @param patchSize size of the individual patches

+     * @param totalSize the size of this entire terrain tree (on one side)

+     * @param quadSize

+     * @param scale

+     * @param heightMap The height map to generate the terrain from (a flat

+     * height map will be generated if this is null)

+     */

+    public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) {

+        this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0);

+        affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);

+        fixNormalEdges(affectedAreaBBox);

+        addControl(new NormalRecalcControl(this));

+    }

+

+    protected TerrainQuad(String name, int patchSize, int quadSize,

+                            Vector3f scale, float[] heightMap, int totalSize,

+                            Vector2f offset, float offsetAmount)

+    {

+        super(name);

+        

+        if (heightMap == null)

+            heightMap = generateDefaultHeightMap(quadSize);

+        

+        if (!FastMath.isPowerOfTwo(quadSize - 1)) {

+            throw new RuntimeException("size given: " + quadSize + "  Terrain quad sizes may only be (2^N + 1)");

+        }

+        if (FastMath.sqrt(heightMap.length) > quadSize) {

+            Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!");

+        }

+        

+        this.offset = offset;

+        this.offsetAmount = offsetAmount;

+        this.totalSize = totalSize;

+        this.size = quadSize;

+        this.patchSize = patchSize;

+        this.stepScale = scale;

+        //this.lodCalculatorFactory = lodCalculatorFactory;

+        //this.lodCalculator = lodCalculator;

+        split(patchSize, heightMap);

+    }

+

+    /*public void setLodCalculatorFactory(LodCalculatorFactory lodCalculatorFactory) {

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).setLodCalculatorFactory(lodCalculatorFactory);

+                } else if (child instanceof TerrainPatch) {

+                    ((TerrainPatch) child).setLodCalculator(lodCalculatorFactory.createCalculator((TerrainPatch) child));

+                }

+            }

+        }

+    }*/

+

+

+    /**

+     * Create just a flat heightmap

+     */

+    private float[] generateDefaultHeightMap(int size) {

+        float[] heightMap = new float[size*size];

+        return heightMap;

+    }

+

+     /**

+      * Call from the update() method of a terrain controller to update

+      * the LOD values of each patch.

+      * This will perform the geometry calculation in a background thread and

+      * do the actual update on the opengl thread.

+      */

+    public void update(List<Vector3f> locations, LodCalculator lodCalculator) {

+        updateLOD(locations, lodCalculator);

+    }

+

+    /**

+     * update the normals if there were any height changes recently.

+     * Should only be called on the root quad

+     */

+    protected void updateNormals() {

+

+        if (needToRecalculateNormals()) {

+            //TODO background-thread this if it ends up being expensive

+            fixNormals(affectedAreaBBox); // the affected patches

+            fixNormalEdges(affectedAreaBBox); // the edges between the patches

+            

+            setNormalRecalcNeeded(null); // set to false

+        }

+    }

+

+    // do all of the LOD calculations

+    protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {

+        // update any existing ones that need updating

+        updateQuadLODs();

+

+        if (lodCalculator.isLodOff()) {

+            // we want to calculate the base lod at least once

+            if (lodOffCount == 1)

+                return;

+            else

+                lodOffCount++;

+        } else 

+            lodOffCount = 0;

+        

+        if (lastCameraLocations != null) {

+            if (lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())

+                return; // don't update if in same spot

+            else

+                lastCameraLocations = cloneVectorList(locations);

+        }

+        else {

+            lastCameraLocations = cloneVectorList(locations);

+            return;

+        }

+

+        if (isLodCalcRunning()) {

+            return;

+        }

+

+        if (getParent() instanceof TerrainQuad) {

+            return; // we just want the root quad to perform this.

+        }

+

+        if (executor == null)

+            executor = createExecutorService();

+        

+        UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator);

+        executor.execute(updateLodThread);

+    }

+

+    private synchronized boolean isLodCalcRunning() {

+        return lodCalcRunning;

+    }

+

+    private synchronized void setLodCalcRunning(boolean running) {

+        lodCalcRunning = running;

+    }

+

+    private List<Vector3f> cloneVectorList(List<Vector3f> locations) {

+        List<Vector3f> cloned = new ArrayList<Vector3f>();

+        for(Vector3f l : locations)

+            cloned.add(l.clone());

+        return cloned;

+    }

+

+    private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {

+        boolean theSame = true;

+        for (Vector3f l : locations) {

+            for (Vector3f v : lastCameraLocations) {

+                if (!v.equals(l) ) {

+                    theSame = false;

+                    return false;

+                }

+            }

+        }

+        return theSame;

+    }

+

+    private int collideWithRay(Ray ray, CollisionResults results) {

+        if (picker == null)

+            picker = new BresenhamTerrainPicker(this);

+

+        Vector3f intersection = picker.getTerrainIntersection(ray, results);

+        if (intersection != null)

+            return 1;

+        else

+            return 0;

+    }

+

+    /**

+     * Generate the entropy values for the terrain for the "perspective" LOD

+     * calculator. This routine can take a long time to run!

+     * @param progressMonitor optional

+     */

+    public void generateEntropy(ProgressMonitor progressMonitor) {

+        // only check this on the root quad

+        if (isRootQuad())

+            if (progressMonitor != null) {

+                int numCalc = (totalSize-1)/(patchSize-1); // make it an even number

+                progressMonitor.setMonitorMax(numCalc*numCalc);

+            }

+

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                        ((TerrainQuad) child).generateEntropy(progressMonitor);

+                } else if (child instanceof TerrainPatch) {

+                    ((TerrainPatch) child).generateLodEntropies();

+                    if (progressMonitor != null)

+                        progressMonitor.incrementProgress(1);

+                }

+            }

+        }

+

+        // only do this on the root quad

+        if (isRootQuad())

+            if (progressMonitor != null)

+                progressMonitor.progressComplete();

+    }

+

+    protected boolean isRootQuad() {

+        return (getParent() != null && !(getParent() instanceof TerrainQuad) );

+    }

+

+    public Material getMaterial() {

+        return getMaterial(null);

+    }

+    

+    public Material getMaterial(Vector3f worldLocation) {

+        // get the material from one of the children. They all share the same material

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    return ((TerrainQuad)child).getMaterial(worldLocation);

+                } else if (child instanceof TerrainPatch) {

+                    return ((TerrainPatch)child).getMaterial();

+                }

+            }

+        }

+        return null;

+    }

+

+    //public float getTextureCoordinateScale() {

+    //    return 1f/(float)totalSize;

+    //}

+    public int getNumMajorSubdivisions() {

+        return 1;

+    }

+

+    /**

+     * Calculates the LOD of all child terrain patches.

+     */

+    private class UpdateLOD implements Runnable {

+        private List<Vector3f> camLocations;

+        private LodCalculator lodCalculator;

+

+        UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {

+            this.camLocations = camLocations;

+            this.lodCalculator = lodCalculator;

+        }

+

+        public void run() {

+            long start = System.currentTimeMillis();

+            if (isLodCalcRunning()) {

+                //System.out.println("thread already running");

+                return;

+            }

+            //System.out.println("spawned thread "+toString());

+            setLodCalcRunning(true);

+

+            // go through each patch and calculate its LOD based on camera distance

+            HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();

+            boolean lodChanged = calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here

+

+            if (!lodChanged) {

+                // not worth updating anything else since no one's LOD changed

+                setLodCalcRunning(false);

+                return;

+            }

+            // then calculate its neighbour LOD values for seaming in the shader

+            findNeighboursLod(updated);

+

+            fixEdges(updated); // 'updated' can get added to here

+

+            reIndexPages(updated, lodCalculator.usesVariableLod());

+

+            setUpdateQuadLODs(updated); // set back to main ogl thread

+

+            setLodCalcRunning(false);

+            //double duration = (System.currentTimeMillis()-start);

+            //System.out.println("terminated in "+duration);

+        }

+    }

+

+    private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) {

+        synchronized (updatePatchesLock) {

+            updatedPatches = updated;

+        }

+    }

+

+    /**

+     * Back on the ogl thread: update the terrain patch geometries

+     * @param updatedPatches to be updated

+     */

+    private void updateQuadLODs() {

+        synchronized (updatePatchesLock) {

+            

+            if (updatedPatches == null || updatedPatches.isEmpty())

+                return;

+

+            // do the actual geometry update here

+            for (UpdatedTerrainPatch utp : updatedPatches.values()) {

+                utp.updateAll();

+            }

+

+            updatedPatches.clear();

+        }

+    }

+    

+    public boolean hasPatchesToUpdate() {

+        return updatedPatches != null && !updatedPatches.isEmpty();

+    }

+

+    protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates, LodCalculator lodCalculator) {

+

+        boolean lodChanged = false;

+

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator);

+                    if (b)

+                        lodChanged = true;

+                } else if (child instanceof TerrainPatch) {

+                    boolean b = lodCalculator.calculateLod((TerrainPatch) child, location, updates);

+                    if (b)

+                        lodChanged = true;

+                }

+            }

+        }

+

+        return lodChanged;

+    }

+

+    protected synchronized void findNeighboursLod(HashMap<String,UpdatedTerrainPatch> updated) {

+        if (children != null) {

+            for (int x = children.size(); --x >= 0;) {

+                Spatial child = children.get(x);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).findNeighboursLod(updated);

+                } else if (child instanceof TerrainPatch) {

+

+                    TerrainPatch patch = (TerrainPatch) child;

+                    if (!patch.searchedForNeighboursAlready) {

+                        // set the references to the neighbours

+                        patch.rightNeighbour = findRightPatch(patch);

+                        patch.bottomNeighbour = findDownPatch(patch);

+                        patch.leftNeighbour = findLeftPatch(patch);

+                        patch.topNeighbour = findTopPatch(patch);

+                        patch.searchedForNeighboursAlready = true;

+                    }

+                    TerrainPatch right = patch.rightNeighbour;

+                    TerrainPatch down = patch.bottomNeighbour;

+

+                    UpdatedTerrainPatch utp = updated.get(patch.getName());

+                    if (utp == null) {

+                        utp = new UpdatedTerrainPatch(patch, patch.lod);

+                        updated.put(utp.getName(), utp);

+                    }

+

+                    if (right != null) {

+                        UpdatedTerrainPatch utpR = updated.get(right.getName());

+                        if (utpR == null) {

+                            utpR = new UpdatedTerrainPatch(right, right.lod);

+                            updated.put(utpR.getName(), utpR);

+                        }

+

+                        utp.setRightLod(utpR.getNewLod());

+                        utpR.setLeftLod(utp.getNewLod());

+                    }

+                    if (down != null) {

+                        UpdatedTerrainPatch utpD = updated.get(down.getName());

+                        if (utpD == null) {

+                            utpD = new UpdatedTerrainPatch(down, down.lod);

+                            updated.put(utpD.getName(), utpD);

+                        }

+

+                        utp.setBottomLod(utpD.getNewLod());

+                        utpD.setTopLod(utp.getNewLod());

+                    }

+

+                }

+            }

+        }

+    }

+

+    /**

+     * TerrainQuad caches neighbours for faster LOD checks.

+     * Sometimes you might want to reset this cache (for instance in TerrainGrid)

+     */

+    protected void resetCachedNeighbours() {

+        if (children != null) {

+            for (int x = children.size(); --x >= 0;) {

+                Spatial child = children.get(x);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).resetCachedNeighbours();

+                } else if (child instanceof TerrainPatch) {

+                    TerrainPatch patch = (TerrainPatch) child;

+                    patch.searchedForNeighboursAlready = false;

+                }

+            }

+        }

+    }

+    

+    /**

+     * Find any neighbours that should have their edges seamed because another neighbour

+     * changed its LOD to a greater value (less detailed)

+     */

+    protected synchronized void fixEdges(HashMap<String,UpdatedTerrainPatch> updated) {

+        if (children != null) {

+            for (int x = children.size(); --x >= 0;) {

+                Spatial child = children.get(x);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).fixEdges(updated);

+                } else if (child instanceof TerrainPatch) {

+                    TerrainPatch patch = (TerrainPatch) child;

+                    UpdatedTerrainPatch utp = updated.get(patch.getName());

+

+                    if(utp != null && utp.lodChanged()) {

+                        if (!patch.searchedForNeighboursAlready) {

+                            // set the references to the neighbours

+                            patch.rightNeighbour = findRightPatch(patch);

+                            patch.bottomNeighbour = findDownPatch(patch);

+                            patch.leftNeighbour = findLeftPatch(patch);

+                            patch.topNeighbour = findTopPatch(patch);

+                            patch.searchedForNeighboursAlready = true;

+                        }

+                        TerrainPatch right = patch.rightNeighbour;

+                        TerrainPatch down = patch.bottomNeighbour;

+                        TerrainPatch top = patch.topNeighbour;

+                        TerrainPatch left = patch.leftNeighbour;

+                        if (right != null) {

+                            UpdatedTerrainPatch utpR = updated.get(right.getName());

+                            if (utpR == null) {

+                                utpR = new UpdatedTerrainPatch(right, right.lod);

+                                updated.put(utpR.getName(), utpR);

+                            }

+                            utpR.setFixEdges(true);

+                        }

+                        if (down != null) {

+                            UpdatedTerrainPatch utpD = updated.get(down.getName());

+                            if (utpD == null) {

+                                utpD = new UpdatedTerrainPatch(down, down.lod);

+                                updated.put(utpD.getName(), utpD);

+                            }

+                            utpD.setFixEdges(true);

+                        }

+                        if (top != null){

+                            UpdatedTerrainPatch utpT = updated.get(top.getName());

+                            if (utpT == null) {

+                                utpT = new UpdatedTerrainPatch(top, top.lod);

+                                updated.put(utpT.getName(), utpT);

+                            }

+                            utpT.setFixEdges(true);

+                        }

+                        if (left != null){

+                            UpdatedTerrainPatch utpL = updated.get(left.getName());

+                            if (utpL == null) {

+                                utpL = new UpdatedTerrainPatch(left, left.lod);

+                                updated.put(utpL.getName(), utpL);

+                            }

+                            utpL.setFixEdges(true);

+                        }

+                    }

+                }

+            }

+        }

+    }

+

+    protected synchronized void reIndexPages(HashMap<String,UpdatedTerrainPatch> updated, boolean usesVariableLod) {

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).reIndexPages(updated, usesVariableLod);

+                } else if (child instanceof TerrainPatch) {

+                    ((TerrainPatch) child).reIndexGeometry(updated, usesVariableLod);

+                }

+            }

+        }

+    }

+

+    /**

+     * <code>split</code> divides the heightmap data for four children. The

+     * children are either quads or patches. This is dependent on the size of the

+     * children. If the child's size is less than or equal to the set block

+     * size, then patches are created, otherwise, quads are created.

+     *

+     * @param blockSize

+     *			the blocks size to test against.

+     * @param heightMap

+     *			the height data.

+     */

+    protected void split(int blockSize, float[] heightMap) {

+        if ((size >> 1) + 1 <= blockSize) {

+            createQuadPatch(heightMap);

+        } else {

+            createQuad(blockSize, heightMap);

+        }

+

+    }

+

+    /**

+     * Quadrants, world coordinates, and heightmap coordinates (Y-up):

+     * 

+     *         -z

+     *      -u | 

+     *    -v  1|3 

+     *  -x ----+---- x

+     *        2|4 u

+     *         | v

+     *         z

+     * <code>createQuad</code> generates four new quads from this quad.

+     * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z

+     * coordinate of the terrain, so it grows in the positive x.z direction.

+     */

+    protected void createQuad(int blockSize, float[] heightMap) {

+        // create 4 terrain quads

+        int quarterSize = size >> 2;

+

+        int split = (size + 1) >> 1;

+

+        Vector2f tempOffset = new Vector2f();

+        offsetAmount += quarterSize;

+

+        //if (lodCalculator == null)

+        //    lodCalculator = createDefaultLodCalculator(); // set a default one

+

+        // 1 upper left of heightmap, upper left quad

+        float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);

+

+        Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0,

+                        -quarterSize * stepScale.z);

+

+        tempOffset.x = offset.x;

+        tempOffset.y = offset.y;

+        tempOffset.x += origin1.x;

+        tempOffset.y += origin1.z;

+

+        TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize,

+                        split, stepScale, heightBlock1, totalSize, tempOffset,

+                        offsetAmount);

+        quad1.setLocalTranslation(origin1);

+        quad1.quadrant = 1;

+        this.attachChild(quad1);

+

+        // 2 lower left of heightmap, lower left quad

+        float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,

+                        split);

+

+        Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0,

+                        quarterSize * stepScale.z);

+

+        tempOffset = new Vector2f();

+        tempOffset.x = offset.x;

+        tempOffset.y = offset.y;

+        tempOffset.x += origin2.x;

+        tempOffset.y += origin2.z;

+

+        TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize,

+                        split, stepScale, heightBlock2, totalSize, tempOffset,

+                        offsetAmount);

+        quad2.setLocalTranslation(origin2);

+        quad2.quadrant = 2;

+        this.attachChild(quad2);

+

+        // 3 upper right of heightmap, upper right quad

+        float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,

+                        split);

+

+        Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0,

+                        -quarterSize * stepScale.z);

+

+        tempOffset = new Vector2f();

+        tempOffset.x = offset.x;

+        tempOffset.y = offset.y;

+        tempOffset.x += origin3.x;

+        tempOffset.y += origin3.z;

+

+        TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize,

+                        split, stepScale, heightBlock3, totalSize, tempOffset,

+                        offsetAmount);

+        quad3.setLocalTranslation(origin3);

+        quad3.quadrant = 3;

+        this.attachChild(quad3);

+        

+        // 4 lower right of heightmap, lower right quad

+        float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,

+                        split - 1, split);

+

+        Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0,

+                        quarterSize * stepScale.z);

+

+        tempOffset = new Vector2f();

+        tempOffset.x = offset.x;

+        tempOffset.y = offset.y;

+        tempOffset.x += origin4.x;

+        tempOffset.y += origin4.z;

+

+        TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize,

+                        split, stepScale, heightBlock4, totalSize, tempOffset,

+                        offsetAmount);

+        quad4.setLocalTranslation(origin4);

+        quad4.quadrant = 4;

+        this.attachChild(quad4);

+

+    }

+

+    public void generateDebugTangents(Material mat) {

+        for (int x = children.size(); --x >= 0;) {

+            Spatial child = children.get(x);

+            if (child instanceof TerrainQuad) {

+                ((TerrainQuad)child).generateDebugTangents(mat);

+            } else if (child instanceof TerrainPatch) {

+                Geometry debug = new Geometry( "Debug " + name,

+                    TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f));

+                attachChild(debug);

+                debug.setLocalTranslation(child.getLocalTranslation());

+                debug.setCullHint(CullHint.Never);

+                debug.setMaterial(mat);

+            }

+        }

+    }

+

+    /**

+     * <code>createQuadPatch</code> creates four child patches from this quad.

+     */

+    protected void createQuadPatch(float[] heightMap) {

+        // create 4 terrain patches

+        int quarterSize = size >> 2;

+        int halfSize = size >> 1;

+        int split = (size + 1) >> 1;

+

+        //if (lodCalculator == null)

+        //    lodCalculator = createDefaultLodCalculator(); // set a default one

+

+        offsetAmount += quarterSize;

+

+        // 1 lower left

+        float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);

+

+        Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize

+                        * stepScale.z);

+

+        Vector2f tempOffset1 = new Vector2f();

+        tempOffset1.x = offset.x;

+        tempOffset1.y = offset.y;

+        tempOffset1.x += origin1.x / 2;

+        tempOffset1.y += origin1.z / 2;

+

+        TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split,

+                        stepScale, heightBlock1, origin1, totalSize, tempOffset1,

+                        offsetAmount);

+        patch1.setQuadrant((short) 1);

+        this.attachChild(patch1);

+        patch1.setModelBound(new BoundingBox());

+        patch1.updateModelBound();

+        //patch1.setLodCalculator(lodCalculator);

+        //TangentBinormalGenerator.generate(patch1);

+

+        // 2 upper left

+        float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,

+                        split);

+

+        Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0);

+

+        Vector2f tempOffset2 = new Vector2f();

+        tempOffset2.x = offset.x;

+        tempOffset2.y = offset.y;

+        tempOffset2.x += origin1.x / 2;

+        tempOffset2.y += quarterSize * stepScale.z;

+

+        TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split,

+                        stepScale, heightBlock2, origin2, totalSize, tempOffset2,

+                        offsetAmount);

+        patch2.setQuadrant((short) 2);

+        this.attachChild(patch2);

+        patch2.setModelBound(new BoundingBox());

+        patch2.updateModelBound();

+        //patch2.setLodCalculator(lodCalculator);

+        //TangentBinormalGenerator.generate(patch2);

+

+        // 3 lower right

+        float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,

+                        split);

+

+        Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z);

+

+        Vector2f tempOffset3 = new Vector2f();

+        tempOffset3.x = offset.x;

+        tempOffset3.y = offset.y;

+        tempOffset3.x += quarterSize * stepScale.x;

+        tempOffset3.y += origin3.z / 2;

+

+        TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split,

+                        stepScale, heightBlock3, origin3, totalSize, tempOffset3,

+                        offsetAmount);

+        patch3.setQuadrant((short) 3);

+        this.attachChild(patch3);

+        patch3.setModelBound(new BoundingBox());

+        patch3.updateModelBound();

+        //patch3.setLodCalculator(lodCalculator);

+        //TangentBinormalGenerator.generate(patch3);

+

+        // 4 upper right

+        float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,

+                        split - 1, split);

+

+        Vector3f origin4 = new Vector3f(0, 0, 0);

+

+        Vector2f tempOffset4 = new Vector2f();

+        tempOffset4.x = offset.x;

+        tempOffset4.y = offset.y;

+        tempOffset4.x += quarterSize * stepScale.x;

+        tempOffset4.y += quarterSize * stepScale.z;

+

+        TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split,

+                        stepScale, heightBlock4, origin4, totalSize, tempOffset4,

+                        offsetAmount);

+        patch4.setQuadrant((short) 4);

+        this.attachChild(patch4);

+        patch4.setModelBound(new BoundingBox());

+        patch4.updateModelBound();

+        //patch4.setLodCalculator(lodCalculator);

+        //TangentBinormalGenerator.generate(patch4);

+    }

+

+    public float[] createHeightSubBlock(float[] heightMap, int x,

+                    int y, int side) {

+        float[] rVal = new float[side * side];

+        int bsize = (int) FastMath.sqrt(heightMap.length);

+        int count = 0;

+        for (int i = y; i < side + y; i++) {

+            for (int j = x; j < side + x; j++) {

+                if (j < bsize && i < bsize)

+                    rVal[count] = heightMap[j + (i * bsize)];

+                count++;

+            }

+        }

+        return rVal;

+    }

+

+    /**

+     * A handy method that will attach all bounding boxes of this terrain

+     * to the node you supply.

+     * Useful to visualize the bounding boxes when debugging.

+     *

+     * @param parent that will get the bounding box shapes of the terrain attached to

+     */

+    public void attachBoundChildren(Node parent) {

+        for (int i = 0; i < this.getQuantity(); i++) {

+            if (this.getChild(i) instanceof TerrainQuad) {

+                ((TerrainQuad) getChild(i)).attachBoundChildren(parent);

+            } else if (this.getChild(i) instanceof TerrainPatch) {

+                BoundingVolume bv = getChild(i).getWorldBound();

+                if (bv instanceof BoundingBox) {

+                    attachBoundingBox((BoundingBox)bv, parent);

+                }

+            }

+        }

+        BoundingVolume bv = getWorldBound();

+        if (bv instanceof BoundingBox) {

+            attachBoundingBox((BoundingBox)bv, parent);

+        }

+    }

+

+    /**

+     * used by attachBoundChildren()

+     */

+    private void attachBoundingBox(BoundingBox bb, Node parent) {

+        WireBox wb = new WireBox(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());

+        Geometry g = new Geometry();

+        g.setMesh(wb);

+        g.setLocalTranslation(bb.getCenter());

+        parent.attachChild(g);

+    }

+

+    /**

+     * Signal if the normal vectors for the terrain need to be recalculated.

+     * Does this by looking at the affectedAreaBBox bounding box. If the bbox

+     * exists already, then it will grow the box to fit the new changedPoint.

+     * If the affectedAreaBBox is null, then it will create one of unit size.

+     *

+     * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false

+     */

+    protected void setNormalRecalcNeeded(Vector2f changedPoint) {

+        if (changedPoint == null) { // set needToRecalculateNormals() to false

+            affectedAreaBBox = null;

+            return;

+        }

+

+        if (affectedAreaBBox == null) {

+            affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length

+        } else {

+            // adjust size of box to be larger

+            affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f));

+        }

+    }

+

+    protected boolean needToRecalculateNormals() {

+        if (affectedAreaBBox != null)

+            return true;

+        if (!lastScale.equals(getWorldScale())) {

+            affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size);

+            lastScale = getWorldScale();

+            return true;

+        }

+        return false;

+    }

+    

+    /**

+     * This will cause all normals for this terrain quad to be recalculated

+     */

+    protected void setNeedToRecalculateNormals() {

+        affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);

+    }

+

+    public float getHeightmapHeight(Vector2f xz) {

+        // offset

+        int halfSize = totalSize / 2;

+        int x = Math.round((xz.x / getWorldScale().x) + halfSize);

+        int z = Math.round((xz.y / getWorldScale().z) + halfSize);

+

+        return getHeightmapHeight(x, z);

+    }

+

+    /**

+     * This will just get the heightmap value at the supplied point,

+     * not an interpolated (actual) height value.

+     */

+    protected float getHeightmapHeight(int x, int z) {

+        int quad = findQuadrant(x, z);

+        int split = (size + 1) >> 1;

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial spat = children.get(i);

+                int col = x;

+                int row = z;

+                boolean match = false;

+

+                // get the childs quadrant

+                int childQuadrant = 0;

+                if (spat instanceof TerrainQuad) {

+                    childQuadrant = ((TerrainQuad) spat).getQuadrant();

+                } else if (spat instanceof TerrainPatch) {

+                    childQuadrant = ((TerrainPatch) spat).getQuadrant();

+                }

+

+                if (childQuadrant == 1 && (quad & 1) != 0) {

+                    match = true;

+                } else if (childQuadrant == 2 && (quad & 2) != 0) {

+                    row = z - split + 1;

+                    match = true;

+                } else if (childQuadrant == 3 && (quad & 4) != 0) {

+                    col = x - split + 1;

+                    match = true;

+                } else if (childQuadrant == 4 && (quad & 8) != 0) {

+                    col = x - split + 1;

+                    row = z - split + 1;

+                    match = true;

+                }

+

+                if (match) {

+                    if (spat instanceof TerrainQuad) {

+                        return ((TerrainQuad) spat).getHeightmapHeight(col, row);

+                    } else if (spat instanceof TerrainPatch) {

+                        return ((TerrainPatch) spat).getHeightmapHeight(col, row);

+                    }

+                }

+

+            }

+        }

+        return Float.NaN;

+    }

+

+    protected Vector3f getMeshNormal(int x, int z) {

+        int quad = findQuadrant(x, z);

+        int split = (size + 1) >> 1;

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial spat = children.get(i);

+                int col = x;

+                int row = z;

+                boolean match = false;

+

+                // get the childs quadrant

+                int childQuadrant = 0;

+                if (spat instanceof TerrainQuad) {

+                    childQuadrant = ((TerrainQuad) spat).getQuadrant();

+                } else if (spat instanceof TerrainPatch) {

+                    childQuadrant = ((TerrainPatch) spat).getQuadrant();

+                }

+

+                if (childQuadrant == 1 && (quad & 1) != 0) {

+                    match = true;

+                } else if (childQuadrant == 2 && (quad & 2) != 0) {

+                    row = z - split + 1;

+                    match = true;

+                } else if (childQuadrant == 3 && (quad & 4) != 0) {

+                    col = x - split + 1;

+                    match = true;

+                } else if (childQuadrant == 4 && (quad & 8) != 0) {

+                    col = x - split + 1;

+                    row = z - split + 1;

+                    match = true;

+                }

+

+                if (match) {

+                    if (spat instanceof TerrainQuad) {

+                        return ((TerrainQuad) spat).getMeshNormal(col, row);

+                    } else if (spat instanceof TerrainPatch) {

+                        return ((TerrainPatch) spat).getMeshNormal(col, row);

+                    }

+                }

+

+            }

+        }

+        return null;

+    }

+

+    public float getHeight(Vector2f xz) {

+        // offset

+        float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);

+        float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);

+        float height = getHeight(x, z);

+        height *= getWorldScale().y;

+        return height;

+    }

+

+    /*

+     * gets an interpolated value at the specified point

+     * @param x coordinate translated into actual (positive) terrain grid coordinates

+     * @param y coordinate translated into actual (positive) terrain grid coordinates

+     */

+    protected float getHeight(float x, float z) {

+        x-=0.5f;

+        z-=0.5f;

+        float col = FastMath.floor(x);

+        float row = FastMath.floor(z);

+        boolean onX = false;

+        if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on

+            onX = true;

+        // v1--v2  ^

+        // |  / |  |

+        // | /  |  |

+        // v3--v4  | Z

+        //         |

+        // <-------Y

+        //     X 

+        float v1 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.ceil(z));

+        float v2 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.ceil(z));

+        float v3 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.floor(z));

+        float v4 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.floor(z));

+        if (onX) {

+            return ((x - col) + (z - row) - 1f)*v1 + (1f - (x - col))*v2 + (1f - (z - row))*v3;

+        } else {

+            return (1f - (x - col) - (z - row))*v4 + (z - row)*v2 + (x - col)*v3;

+        }

+    }

+

+    public Vector3f getNormal(Vector2f xz) {

+        // offset

+        float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);

+        float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);

+        Vector3f normal = getNormal(x, z, xz);

+        

+        return normal;

+    }

+    

+    protected Vector3f getNormal(float x, float z, Vector2f xz) {

+        x-=0.5f;

+        z-=0.5f;

+        float col = FastMath.floor(x);

+        float row = FastMath.floor(z);

+        boolean onX = false;

+        if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on

+            onX = true;

+        // v1--v2  ^

+        // |  / |  |

+        // | /  |  |

+        // v3--v4  | Z

+        //         |

+        // <-------Y

+        //     X 

+        Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z));

+        Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z));

+        Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z));

+        Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z));

+        

+        return n1.add(n2).add(n3).add(n4).normalize();

+    }

+    

+    public void setHeight(Vector2f xz, float height) {

+        List<Vector2f> coord = new ArrayList<Vector2f>();

+        coord.add(xz);

+        List<Float> h = new ArrayList<Float>();

+        h.add(height);

+

+        setHeight(coord, h);

+    }

+

+    public void adjustHeight(Vector2f xz, float delta) {

+        List<Vector2f> coord = new ArrayList<Vector2f>();

+        coord.add(xz);

+        List<Float> h = new ArrayList<Float>();

+        h.add(delta);

+

+        adjustHeight(coord, h);

+    }

+

+    public void setHeight(List<Vector2f> xz, List<Float> height) {

+        setHeight(xz, height, true);

+    }

+

+    public void adjustHeight(List<Vector2f> xz, List<Float> height) {

+        setHeight(xz, height, false);

+    }

+

+    protected void setHeight(List<Vector2f> xz, List<Float> height, boolean overrideHeight) {

+        if (xz.size() != height.size())

+            throw new IllegalArgumentException("Both lists must be the same length!");

+

+        int halfSize = totalSize / 2;

+

+        List<LocationHeight> locations = new ArrayList<LocationHeight>();

+

+        // offset

+        for (int i=0; i<xz.size(); i++) {

+            int x = Math.round((xz.get(i).x / getWorldScale().x) + halfSize);

+            int z = Math.round((xz.get(i).y / getWorldScale().z) + halfSize);

+            locations.add(new LocationHeight(x,z,height.get(i)));

+        }

+

+        setHeight(locations, overrideHeight); // adjust height of the actual mesh

+

+        // signal that the normals need updating

+        for (int i=0; i<xz.size(); i++)

+            setNormalRecalcNeeded(xz.get(i) );

+    }

+

+    protected class LocationHeight {

+        int x;

+        int z;

+        float h;

+

+        LocationHeight(){}

+

+        LocationHeight(int x, int z, float h){

+            this.x = x;

+            this.z = z;

+            this.h = h;

+        }

+    }

+

+    protected void setHeight(List<LocationHeight> locations, boolean overrideHeight) {

+        if (children == null)

+            return;

+

+        List<LocationHeight> quadLH1 = new ArrayList<LocationHeight>();

+        List<LocationHeight> quadLH2 = new ArrayList<LocationHeight>();

+        List<LocationHeight> quadLH3 = new ArrayList<LocationHeight>();

+        List<LocationHeight> quadLH4 = new ArrayList<LocationHeight>();

+        Spatial quad1 = null;

+        Spatial quad2 = null;

+        Spatial quad3 = null;

+        Spatial quad4 = null;

+

+        // get the child quadrants

+        for (int i = children.size(); --i >= 0;) {

+            Spatial spat = children.get(i);

+            int childQuadrant = 0;

+            if (spat instanceof TerrainQuad) {

+                childQuadrant = ((TerrainQuad) spat).getQuadrant();

+            } else if (spat instanceof TerrainPatch) {

+                childQuadrant = ((TerrainPatch) spat).getQuadrant();

+            }

+

+            if (childQuadrant == 1)

+                quad1 = spat;

+            else if (childQuadrant == 2)

+                quad2 = spat;

+            else if (childQuadrant == 3)

+                quad3 = spat;

+            else if (childQuadrant == 4)

+                quad4 = spat;

+        }

+

+        int split = (size + 1) >> 1;

+

+        // distribute each locationHeight into the quadrant it intersects

+        for (LocationHeight lh : locations) {

+            int quad = findQuadrant(lh.x, lh.z);

+

+            int col = lh.x;

+            int row = lh.z;

+

+            if ((quad & 1) != 0) {

+                quadLH1.add(lh);

+            }

+            if ((quad & 2) != 0) {

+                row = lh.z - split + 1;

+                quadLH2.add(new LocationHeight(lh.x, row, lh.h));

+            }

+            if ((quad & 4) != 0) {

+                col = lh.x - split + 1;

+                quadLH3.add(new LocationHeight(col, lh.z, lh.h));

+            }

+            if ((quad & 8) != 0) {

+                col = lh.x - split + 1;

+                row = lh.z - split + 1;

+                quadLH4.add(new LocationHeight(col, row, lh.h));

+            }

+        }

+

+        // send the locations to the children

+        if (!quadLH1.isEmpty()) {

+            if (quad1 instanceof TerrainQuad)

+                ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight);

+            else if(quad1 instanceof TerrainPatch)

+                ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight);

+        }

+

+        if (!quadLH2.isEmpty()) {

+            if (quad2 instanceof TerrainQuad)

+                ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight);

+            else if(quad2 instanceof TerrainPatch)

+                ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight);

+        }

+

+        if (!quadLH3.isEmpty()) {

+            if (quad3 instanceof TerrainQuad)

+                ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight);

+            else if(quad3 instanceof TerrainPatch)

+                ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight);

+        }

+

+        if (!quadLH4.isEmpty()) {

+            if (quad4 instanceof TerrainQuad)

+                ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight);

+            else if(quad4 instanceof TerrainPatch)

+                ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight);

+        }

+    }

+

+    protected boolean isPointOnTerrain(int x, int z) {

+        return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize);

+    }

+

+    

+    public int getTerrainSize() {

+        return totalSize;

+    }

+

+

+    // a position can be in multiple quadrants, so use a bit anded value.

+    private int findQuadrant(int x, int y) {

+        int split = (size + 1) >> 1;

+        int quads = 0;

+        if (x < split && y < split)

+            quads |= 1;

+        if (x < split && y >= split - 1)

+            quads |= 2;

+        if (x >= split - 1 && y < split)

+            quads |= 4;

+        if (x >= split - 1 && y >= split - 1)

+            quads |= 8;

+        return quads;

+    }

+

+    /**

+     * lock or unlock the meshes of this terrain.

+     * Locked meshes are uneditable but have better performance.

+     * @param locked or unlocked

+     */

+    public void setLocked(boolean locked) {

+        for (int i = 0; i < this.getQuantity(); i++) {

+            if (this.getChild(i) instanceof TerrainQuad) {

+                ((TerrainQuad) getChild(i)).setLocked(locked);

+            } else if (this.getChild(i) instanceof TerrainPatch) {

+                if (locked)

+                    ((TerrainPatch) getChild(i)).lockMesh();

+                else

+                    ((TerrainPatch) getChild(i)).unlockMesh();

+            }

+        }

+    }

+

+

+    public int getQuadrant() {

+        return quadrant;

+    }

+

+    public void setQuadrant(short quadrant) {

+        this.quadrant = quadrant;

+    }

+

+

+    protected TerrainPatch getPatch(int quad) {

+        if (children != null)

+            for (int x = children.size(); --x >= 0;) {

+                Spatial child = children.get(x);

+                if (child instanceof TerrainPatch) {

+                    TerrainPatch tb = (TerrainPatch) child;

+                    if (tb.getQuadrant() == quad)

+                        return tb;

+                }

+            }

+        return null;

+    }

+

+    protected TerrainQuad getQuad(int quad) {

+        if (children != null)

+            for (int x = children.size(); --x >= 0;) {

+                Spatial child = children.get(x);

+                if (child instanceof TerrainQuad) {

+                    TerrainQuad tq = (TerrainQuad) child;

+                    if (tq.getQuadrant() == quad)

+                        return tq;

+                }

+            }

+        return null;

+    }

+

+    protected TerrainPatch findRightPatch(TerrainPatch tp) {

+        if (tp.getQuadrant() == 1)

+            return getPatch(3);

+        else if (tp.getQuadrant() == 2)

+            return getPatch(4);

+        else if (tp.getQuadrant() == 3) {

+            // find the patch to the right and ask it for child 1.

+            TerrainQuad quad = findRightQuad();

+            if (quad != null)

+                return quad.getPatch(1);

+        } else if (tp.getQuadrant() == 4) {

+            // find the patch to the right and ask it for child 2.

+            TerrainQuad quad = findRightQuad();

+            if (quad != null)

+                return quad.getPatch(2);

+        }

+

+        return null;

+    }

+

+    protected TerrainPatch findDownPatch(TerrainPatch tp) {

+        if (tp.getQuadrant() == 1)

+            return getPatch(2);

+        else if (tp.getQuadrant() == 3)

+            return getPatch(4);

+        else if (tp.getQuadrant() == 2) {

+            // find the patch below and ask it for child 1.

+            TerrainQuad quad = findDownQuad();

+            if (quad != null)

+                return quad.getPatch(1);

+        } else if (tp.getQuadrant() == 4) {

+            TerrainQuad quad = findDownQuad();

+            if (quad != null)

+                return quad.getPatch(3);

+        }

+

+        return null;

+    }

+

+

+    protected TerrainPatch findTopPatch(TerrainPatch tp) {

+        if (tp.getQuadrant() == 2)

+            return getPatch(1);

+        else if (tp.getQuadrant() == 4)

+            return getPatch(3);

+        else if (tp.getQuadrant() == 1) {

+            // find the patch above and ask it for child 2.

+            TerrainQuad quad = findTopQuad();

+            if (quad != null)

+                return quad.getPatch(2);

+        } else if (tp.getQuadrant() == 3) {

+            TerrainQuad quad = findTopQuad();

+            if (quad != null)

+                return quad.getPatch(4);

+        }

+

+        return null;

+    }

+

+    protected TerrainPatch findLeftPatch(TerrainPatch tp) {

+        if (tp.getQuadrant() == 3)

+            return getPatch(1);

+        else if (tp.getQuadrant() == 4)

+            return getPatch(2);

+        else if (tp.getQuadrant() == 1) {

+            // find the patch above and ask it for child 2.

+            TerrainQuad quad = findLeftQuad();

+            if (quad != null)

+                return quad.getPatch(3);

+        } else if (tp.getQuadrant() == 2) {

+            TerrainQuad quad = findLeftQuad();

+            if (quad != null)

+                return quad.getPatch(4);

+        }

+

+        return null;

+    }

+

+    protected TerrainQuad findRightQuad() {

+        if (getParent() == null || !(getParent() instanceof TerrainQuad))

+            return null;

+

+        TerrainQuad pQuad = (TerrainQuad) getParent();

+

+        if (quadrant == 1)

+            return pQuad.getQuad(3);

+        else if (quadrant == 2)

+            return pQuad.getQuad(4);

+        else if (quadrant == 3) {

+            TerrainQuad quad = pQuad.findRightQuad();

+            if (quad != null)

+                return quad.getQuad(1);

+        } else if (quadrant == 4) {

+            TerrainQuad quad = pQuad.findRightQuad();

+            if (quad != null)

+                return quad.getQuad(2);

+        }

+

+        return null;

+    }

+

+    protected TerrainQuad findDownQuad() {

+        if (getParent() == null || !(getParent() instanceof TerrainQuad))

+            return null;

+

+        TerrainQuad pQuad = (TerrainQuad) getParent();

+

+        if (quadrant == 1)

+            return pQuad.getQuad(2);

+        else if (quadrant == 3)

+            return pQuad.getQuad(4);

+        else if (quadrant == 2) {

+            TerrainQuad quad = pQuad.findDownQuad();

+            if (quad != null)

+                return quad.getQuad(1);

+        } else if (quadrant == 4) {

+            TerrainQuad quad = pQuad.findDownQuad();

+            if (quad != null)

+                return quad.getQuad(3);

+        }

+

+        return null;

+    }

+

+    protected TerrainQuad findTopQuad() {

+        if (getParent() == null || !(getParent() instanceof TerrainQuad))

+            return null;

+

+        TerrainQuad pQuad = (TerrainQuad) getParent();

+

+        if (quadrant == 2)

+            return pQuad.getQuad(1);

+        else if (quadrant == 4)

+            return pQuad.getQuad(3);

+        else if (quadrant == 1) {

+            TerrainQuad quad = pQuad.findTopQuad();

+            if (quad != null)

+                return quad.getQuad(2);

+        } else if (quadrant == 3) {

+            TerrainQuad quad = pQuad.findTopQuad();

+            if (quad != null)

+                return quad.getQuad(4);

+        }

+

+        return null;

+    }

+

+    protected TerrainQuad findLeftQuad() {

+        if (getParent() == null || !(getParent() instanceof TerrainQuad))

+            return null;

+

+        TerrainQuad pQuad = (TerrainQuad) getParent();

+

+        if (quadrant == 3)

+            return pQuad.getQuad(1);

+        else if (quadrant == 4)

+            return pQuad.getQuad(2);

+        else if (quadrant == 1) {

+            TerrainQuad quad = pQuad.findLeftQuad();

+            if (quad != null)

+                return quad.getQuad(3);

+        } else if (quadrant == 2) {

+            TerrainQuad quad = pQuad.findLeftQuad();

+            if (quad != null)

+                return quad.getQuad(4);

+        }

+

+        return null;

+    }

+

+    /**

+     * Find what terrain patches need normal recalculations and update

+     * their normals;

+     */

+    protected void fixNormals(BoundingBox affectedArea) {

+        if (children == null)

+            return;

+

+        // go through the children and see if they collide with the affectedAreaBBox

+        // if they do, then update their normals

+        for (int x = children.size(); --x >= 0;) {

+            Spatial child = children.get(x);

+            if (child instanceof TerrainQuad) {

+                if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )

+                    ((TerrainQuad) child).fixNormals(affectedArea);

+            } else if (child instanceof TerrainPatch) {

+                if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) )

+                    ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals

+            }

+        }

+    }

+

+    /**

+     * fix the normals on the edge of the terrain patches.

+     */

+    protected void fixNormalEdges(BoundingBox affectedArea) {

+        if (children == null)

+            return;

+

+        for (int x = children.size(); --x >= 0;) {

+            Spatial child = children.get(x);

+            if (child instanceof TerrainQuad) {

+                if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) )

+                    ((TerrainQuad) child).fixNormalEdges(affectedArea);

+            } else if (child instanceof TerrainPatch) {

+                if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue

+                    continue;

+

+                TerrainPatch tp = (TerrainPatch) child;

+                TerrainPatch right = findRightPatch(tp);

+                TerrainPatch bottom = findDownPatch(tp);

+                TerrainPatch top = findTopPatch(tp);

+                TerrainPatch left = findLeftPatch(tp);

+                TerrainPatch topLeft = null;

+                if (top != null)

+                    topLeft = findLeftPatch(top);

+                TerrainPatch bottomRight = null;

+                if (right != null)

+                    bottomRight = findDownPatch(right);

+                TerrainPatch topRight = null;

+                if (top != null)

+                    topRight = findRightPatch(top);

+                TerrainPatch bottomLeft = null;

+                if (left != null)

+                    bottomLeft = findDownPatch(left);

+

+                tp.fixNormalEdges(right, bottom, top, left, bottomRight, bottomLeft, topRight, topLeft);

+

+            }

+        } // for each child

+

+    }

+

+

+

+    @Override

+    public int collideWith(Collidable other, CollisionResults results){

+        int total = 0;

+

+        if (other instanceof Ray)

+            return collideWithRay((Ray)other, results);

+

+        // if it didn't collide with this bbox, return

+        if (other instanceof BoundingVolume)

+            if (!this.getWorldBound().intersects((BoundingVolume)other))

+                return total;

+

+        for (Spatial child : children){

+            total += child.collideWith(other, results);

+        }

+        return total;

+    }

+

+    /**

+     * Gather the terrain patches that intersect the given ray (toTest).

+     * This only tests the bounding boxes

+     * @param toTest

+     * @param results

+     */

+    public void findPick(Ray toTest, List<TerrainPickData> results) {

+

+        if (getWorldBound() != null) {

+            if (getWorldBound().intersects(toTest)) {

+                // further checking needed.

+                for (int i = 0; i < getQuantity(); i++) {

+                    if (children.get(i) instanceof TerrainPatch) {

+                        TerrainPatch tp = (TerrainPatch) children.get(i);

+                        tp.ensurePositiveVolumeBBox();

+                        if (tp.getWorldBound().intersects(toTest)) {

+                            CollisionResults cr = new CollisionResults();

+                            toTest.collideWith(tp.getWorldBound(), cr);

+                            if (cr != null && cr.getClosestCollision() != null) {

+                                cr.getClosestCollision().getDistance();

+                                results.add(new TerrainPickData(tp, cr.getClosestCollision()));

+                            }

+                        }

+                    }

+                    else

+                        ((TerrainQuad) children.get(i)).findPick(toTest, results);

+                }

+            }

+        }

+    }

+

+

+    /**

+     * Retrieve all Terrain Patches from all children and store them

+     * in the 'holder' list

+     * @param holder must not be null, will be populated when returns

+     */

+    public void getAllTerrainPatches(List<TerrainPatch> holder) {

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).getAllTerrainPatches(holder);

+                } else if (child instanceof TerrainPatch) {

+                    holder.add((TerrainPatch)child);

+                }

+            }

+        }

+    }

+

+    public void getAllTerrainPatchesWithTranslation(Map<TerrainPatch,Vector3f> holder, Vector3f translation) {

+        if (children != null) {

+            for (int i = children.size(); --i >= 0;) {

+                Spatial child = children.get(i);

+                if (child instanceof TerrainQuad) {

+                    ((TerrainQuad) child).getAllTerrainPatchesWithTranslation(holder, translation.clone().add(child.getLocalTranslation()));

+                } else if (child instanceof TerrainPatch) {

+                    //if (holder.size() < 4)

+                    holder.put((TerrainPatch)child, translation.clone().add(child.getLocalTranslation()));

+                }

+            }

+        }

+    }

+

+    @Override

+    public void read(JmeImporter e) throws IOException {

+        super.read(e);

+        InputCapsule c = e.getCapsule(this);

+        size = c.readInt("size", 0);

+        stepScale = (Vector3f) c.readSavable("stepScale", null);

+        offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0));

+        offsetAmount = c.readFloat("offsetAmount", 0);

+        quadrant = c.readInt("quadrant", 0);

+        totalSize = c.readInt("totalSize", 0);

+        //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator());

+        //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);

+        

+        if ( !(getParent() instanceof TerrainQuad) ) {

+            BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize);

+            affectedAreaBBox = all;

+            updateNormals();

+        }

+    }

+

+    @Override

+    public void write(JmeExporter e) throws IOException {

+        super.write(e);

+        OutputCapsule c = e.getCapsule(this);

+        c.write(size, "size", 0);

+        c.write(totalSize, "totalSize", 0);

+        c.write(stepScale, "stepScale", null);

+        c.write(offset, "offset", new Vector2f(0,0));

+        c.write(offsetAmount, "offsetAmount", 0);

+        c.write(quadrant, "quadrant", 0);

+        //c.write(lodCalculatorFactory, "lodCalculatorFactory", null);

+        //c.write(lodCalculator, "lodCalculator", null);

+    }

+

+    @Override

+    public TerrainQuad clone() {

+        return this.clone(true);

+    }

+

+	@Override

+    public TerrainQuad clone(boolean cloneMaterials) {

+        TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials);

+        quadClone.name = name.toString();

+        quadClone.size = size;

+        quadClone.totalSize = totalSize;

+        if (stepScale != null) {

+            quadClone.stepScale = stepScale.clone();

+        }

+        if (offset != null) {

+            quadClone.offset = offset.clone();

+        }

+        quadClone.offsetAmount = offsetAmount;

+        quadClone.quadrant = quadrant;

+        //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();

+        //quadClone.lodCalculator = lodCalculator.clone();

+        

+        TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class);

+        TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class);

+        

+        if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) {

+            //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone());

+        }

+        NormalRecalcControl normalControl = getControl(NormalRecalcControl.class);

+        if (normalControl != null)

+            normalControl.setTerrain(this);

+

+        return quadClone;

+    }

+    

+    public int getMaxLod() {

+        if (maxLod < 0)

+            maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide

+

+        return maxLod;

+    }

+

+    public int getPatchSize() {

+        return patchSize;

+    }

+

+    public int getTotalSize() {

+        return totalSize;

+    }

+

+    public float[] getHeightMap() {

+

+        float[] hm = null;

+        int length = ((size-1)/2)+1;

+        int area = size*size;

+        hm = new float[area];

+

+        if (getChildren() != null && !getChildren().isEmpty()) {

+            float[] ul=null, ur=null, bl=null, br=null;

+            // get the child heightmaps

+            if (getChild(0) instanceof TerrainPatch) {

+                for (Spatial s : getChildren()) {

+                    if ( ((TerrainPatch)s).getQuadrant() == 1)

+                        ul = ((TerrainPatch)s).getHeightMap();

+                    else if(((TerrainPatch) s).getQuadrant() == 2)

+                        bl = ((TerrainPatch)s).getHeightMap();

+                    else if(((TerrainPatch) s).getQuadrant() == 3)

+                        ur = ((TerrainPatch)s).getHeightMap();

+                    else if(((TerrainPatch) s).getQuadrant() == 4)

+                        br = ((TerrainPatch)s).getHeightMap();

+                }

+            }

+            else {

+                ul = getQuad(1).getHeightMap();

+                bl = getQuad(2).getHeightMap();

+                ur = getQuad(3).getHeightMap();

+                br = getQuad(4).getHeightMap();

+            }

+

+            // combine them into a single heightmap

+

+

+            // first upper blocks

+            for (int y=0; y<length; y++) { // rows

+                for (int x1=0; x1<length; x1++) {

+                    int row = y*size;

+                    hm[row+x1] = ul[y*length+x1];

+                }

+                for (int x2=1; x2<length; x2++) {

+                    int row = y*size + length;

+                    hm[row+x2-1] = ur[y*length + x2];

+                }

+            }

+            // second lower blocks

+            int rowOffset = size*length;

+            for (int y=1; y<length; y++) { // rows

+                for (int x1=0; x1<length; x1++) {

+                    int row = (y-1)*size;

+                    hm[rowOffset+row+x1] = bl[y*length+x1];

+                }

+                for (int x2=1; x2<length; x2++) {

+                    int row = (y-1)*size + length;

+                    hm[rowOffset+row+x2-1] = br[y*length + x2];

+                }

+            }

+        }

+

+        return hm;

+    }

+}

+

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java b/engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java
new file mode 100644
index 0000000..7a335d0
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java
@@ -0,0 +1,183 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.geomipmap;

+

+import com.jme3.scene.VertexBuffer.Type;

+import java.nio.IntBuffer;

+

+/**

+ * Stores a terrain patch's details so the LOD background thread can update

+ * the actual terrain patch back on the ogl thread.

+ *

+ * @author Brent Owens

+ *

+ */

+public class UpdatedTerrainPatch {

+

+	private TerrainPatch updatedPatch;

+	private int newLod;

+	private int previousLod;

+	private int rightLod,topLod,leftLod,bottomLod;

+	private IntBuffer newIndexBuffer;

+	private boolean reIndexNeeded = false;

+	private boolean fixEdges = false;

+

+	public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {

+		this.updatedPatch = updatedPatch;

+		this.newLod = newLod;

+	}

+

+	public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod, int prevLOD, boolean reIndexNeeded) {

+		this.updatedPatch = updatedPatch;

+		this.newLod = newLod;

+		this.previousLod = prevLOD;

+		this.reIndexNeeded = reIndexNeeded;

+		if (this.newLod <= 0)

+                    throw new IllegalArgumentException();

+	}

+

+	public String getName() {

+		return updatedPatch.getName();

+	}

+

+	protected boolean lodChanged() {

+		if (reIndexNeeded && previousLod != newLod)

+			return true;

+		else

+			return false;

+	}

+

+	protected TerrainPatch getUpdatedPatch() {

+		return updatedPatch;

+	}

+

+	protected void setUpdatedPatch(TerrainPatch updatedPatch) {

+		this.updatedPatch = updatedPatch;

+	}

+

+	protected int getNewLod() {

+		return newLod;

+	}

+

+	public void setNewLod(int newLod) {

+		this.newLod = newLod;

+                if (this.newLod < 0)

+                    throw new IllegalArgumentException();

+	}

+

+	protected IntBuffer getNewIndexBuffer() {

+		return newIndexBuffer;

+	}

+

+	protected void setNewIndexBuffer(IntBuffer newIndexBuffer) {

+		this.newIndexBuffer = newIndexBuffer;

+	}

+

+

+	protected int getRightLod() {

+		return rightLod;

+	}

+

+

+	protected void setRightLod(int rightLod) {

+		this.rightLod = rightLod;

+	}

+

+

+	protected int getTopLod() {

+		return topLod;

+	}

+

+

+	protected void setTopLod(int topLod) {

+		this.topLod = topLod;

+	}

+

+

+	protected int getLeftLod() {

+		return leftLod;

+	}

+

+

+	protected void setLeftLod(int leftLod) {

+		this.leftLod = leftLod;

+	}

+

+

+	protected int getBottomLod() {

+		return bottomLod;

+	}

+

+

+	protected void setBottomLod(int bottomLod) {

+		this.bottomLod = bottomLod;

+	}

+

+	public boolean isReIndexNeeded() {

+		return reIndexNeeded;

+	}

+

+	public void setReIndexNeeded(boolean reIndexNeeded) {

+		this.reIndexNeeded = reIndexNeeded;

+	}

+

+	public boolean isFixEdges() {

+		return fixEdges;

+	}

+

+	public void setFixEdges(boolean fixEdges) {

+		this.fixEdges = fixEdges;

+	}

+

+	public int getPreviousLod() {

+		return previousLod;

+	}

+

+	public void setPreviousLod(int previousLod) {

+		this.previousLod = previousLod;

+	}

+

+	public void updateAll() {

+		updatedPatch.setLod(newLod);

+		updatedPatch.setLodRight(rightLod);

+		updatedPatch.setLodTop(topLod);

+		updatedPatch.setLodLeft(leftLod);

+		updatedPatch.setLodBottom(bottomLod);

+		if (newIndexBuffer != null && (reIndexNeeded || fixEdges)) {

+			updatedPatch.setPreviousLod(previousLod);

+			updatedPatch.getMesh().clearBuffer(Type.Index);

+			updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer);

+		}

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java
new file mode 100644
index 0000000..0c61e75
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java
@@ -0,0 +1,92 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.geomipmap.grid;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class AssetTileLoader implements TerrainGridTileLoader {
+
+    private AssetManager manager;
+    private String assetPath;
+    private String name;
+    private int size;
+    private int patchSize;
+    private int quadSize;
+
+    public AssetTileLoader() {
+    }
+
+    public AssetTileLoader(AssetManager manager, String name, String assetPath) {
+        this.manager = manager;
+        this.name = name;
+        this.assetPath = assetPath;
+    }
+
+    public TerrainQuad getTerrainQuadAt(Vector3f location) {
+        String modelName = assetPath + "/" + name + "_" + Math.round(location.x) + "_" + Math.round(location.y) + "_" + Math.round(location.z) + ".j3o";
+        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Load terrain grid tile: {0}", modelName);
+        TerrainQuad quad = null;
+        try {
+            quad = (TerrainQuad) manager.loadModel(modelName);
+        } catch (Exception e) {
+//            e.printStackTrace();
+        }
+        if (quad == null) {
+            Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Could not load terrain grid tile: {0}", modelName);
+            quad = createNewQuad(location);
+        } else {
+            Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Loaded terrain grid tile: {0}", modelName);
+        }
+        return quad;
+    }
+
+    public String getAssetPath() {
+        return assetPath;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setPatchSize(int patchSize) {
+        this.patchSize = patchSize;
+    }
+
+    public void setQuadSize(int quadSize) {
+        this.quadSize = quadSize;
+    }
+
+    private TerrainQuad createNewQuad(Vector3f location) {
+        TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, null);
+        return q;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule c = ex.getCapsule(this);
+        c.write(assetPath, "assetPath", null);
+        c.write(name, "name", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule c = im.getCapsule(this);
+        manager = im.getAssetManager();
+        assetPath = c.readString("assetPath", null);
+        name = c.readString("name", null);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java
new file mode 100644
index 0000000..9eaa9a2
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java
@@ -0,0 +1,87 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.geomipmap.grid;
+
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.HeightMap;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import com.jme3.terrain.noise.Basis;
+
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+public class FractalTileLoader implements TerrainGridTileLoader{
+            
+    public class FloatBufferHeightMap extends AbstractHeightMap {
+
+        private final FloatBuffer buffer;
+
+        public FloatBufferHeightMap(FloatBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public boolean load() {
+            this.heightData = this.buffer.array();
+            return true;
+        }
+
+    }
+
+    private int patchSize;
+    private int quadSize;
+    private final Basis base;
+    private final float heightScale;
+
+    public FractalTileLoader(Basis base, float heightScale) {
+        this.base = base;
+        this.heightScale = heightScale;
+    }
+
+    private HeightMap getHeightMapAt(Vector3f location) {
+        AbstractHeightMap heightmap = null;
+        
+        FloatBuffer buffer = this.base.getBuffer(location.x * (this.quadSize - 1), location.z * (this.quadSize - 1), 0, this.quadSize);
+
+        float[] arr = buffer.array();
+        for (int i = 0; i < arr.length; i++) {
+            arr[i] = arr[i] * this.heightScale;
+        }
+        heightmap = new FloatBufferHeightMap(buffer);
+        heightmap.load();
+        return heightmap;
+    }
+
+    public TerrainQuad getTerrainQuadAt(Vector3f location) {
+        HeightMap heightMapAt = getHeightMapAt(location);
+        TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+        return q;
+    }
+
+    public void setPatchSize(int patchSize) {
+        this.patchSize = patchSize;
+    }
+
+    public void setQuadSize(int quadSize) {
+        this.quadSize = quadSize;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        //TODO: serialization
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        //TODO: serialization
+    }    
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java
new file mode 100644
index 0000000..cef6a72
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java
@@ -0,0 +1,153 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.geomipmap.grid;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.*;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+public class ImageTileLoader implements TerrainGridTileLoader{
+    private static final Logger logger = Logger.getLogger(ImageTileLoader.class.getName());
+    private final AssetManager assetManager;
+    private final Namer namer;
+    private int patchSize;
+    private int quadSize;
+    private float heightScale = 1;
+    //private int imageType = BufferedImage.TYPE_USHORT_GRAY; // 16 bit grayscale
+    //private ImageHeightmap customImageHeightmap;
+
+    public ImageTileLoader(final String textureBase, final String textureExt, AssetManager assetManager) {
+        this(assetManager, new Namer() {
+
+            public String getName(int x, int y) {
+                return textureBase + "_" + x + "_" + y + "." + textureExt;
+            }
+        });
+    }
+
+    public ImageTileLoader(AssetManager assetManager, Namer namer) {
+        this.assetManager = assetManager;
+        this.namer = namer;
+    }
+
+    /**
+     * Effects vertical scale of the height of the terrain when loaded.
+     */
+    public void setHeightScale(float heightScale) {
+        this.heightScale = heightScale;
+    }
+    
+    
+    /**
+     * Lets you specify the type of images that are being loaded. All images
+     * must be the same type.
+     * @param imageType eg. BufferedImage.TYPE_USHORT_GRAY
+     */
+    /*public void setImageType(int imageType) {
+        this.imageType = imageType;
+    }*/
+
+    /**
+     * The ImageHeightmap that will parse the image type that you 
+     * specify with setImageType().
+     * @param customImageHeightmap must extend AbstractHeightmap
+     */
+    /*public void setCustomImageHeightmap(ImageHeightmap customImageHeightmap) {
+        if (!(customImageHeightmap instanceof AbstractHeightMap)) {
+            throw new IllegalArgumentException("customImageHeightmap must be an AbstractHeightMap!");
+        }
+        this.customImageHeightmap = customImageHeightmap;
+    }*/
+
+    private HeightMap getHeightMapAt(Vector3f location) {
+        // HEIGHTMAP image (for the terrain heightmap)
+        int x = (int) location.x;
+        int z = (int) location.z;
+        
+        AbstractHeightMap heightmap = null;
+        //BufferedImage im = null;
+        
+        String name = null;
+        try {
+            name = namer.getName(x, z);
+            logger.log(Level.INFO, "Loading heightmap from file: {0}", name);
+            final Texture texture = assetManager.loadTexture(new TextureKey(name));
+            heightmap = new ImageBasedHeightMap(texture.getImage());
+            /*if (assetInfo != null){
+                InputStream in = assetInfo.openStream();
+                im = ImageIO.read(in);
+            } else {
+                im = new BufferedImage(patchSize, patchSize, imageType);
+                logger.log(Level.WARNING, "File: {0} not found, loading zero heightmap instead", name);
+            }*/
+            // CREATE HEIGHTMAP
+            /*if (imageType == BufferedImage.TYPE_USHORT_GRAY) {
+                heightmap = new Grayscale16BitHeightMap(im);
+            } else if (imageType == BufferedImage.TYPE_3BYTE_BGR) {
+                heightmap = new ImageBasedHeightMap(im);
+            } else if (customImageHeightmap != null && customImageHeightmap instanceof AbstractHeightMap) {
+                // If it gets here, it means you have specified a different image type, and you must
+                // then also supply a custom image heightmap class that can parse that image into
+                // a heightmap.
+                customImageHeightmap.setImage(im);
+                heightmap = (AbstractHeightMap) customImageHeightmap;
+            } else {
+                // error, no supported image format and no custom image heightmap specified
+                if (customImageHeightmap == null)
+                    logger.log(Level.SEVERE, "Custom image type specified [{0}] but no customImageHeightmap declared! Use setCustomImageHeightmap()",imageType);
+                if (!(customImageHeightmap instanceof AbstractHeightMap))
+                    logger.severe("customImageHeightmap must be an AbstractHeightMap!");
+                return null;
+            }*/
+            heightmap.setHeightScale(1);
+            heightmap.load();
+        //} catch (IOException e) {
+        //    e.printStackTrace();
+        } catch (AssetNotFoundException e) {
+            logger.log(Level.WARNING, "Asset {0} not found, loading zero heightmap instead", name);
+        }
+        return heightmap;
+    }
+
+    public void setSize(int size) {
+        this.patchSize = size - 1;
+    }
+
+    public TerrainQuad getTerrainQuadAt(Vector3f location) {
+        HeightMap heightMapAt = getHeightMapAt(location);
+        TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+        return q;
+    }
+
+    public void setPatchSize(int patchSize) {
+        this.patchSize = patchSize;
+    }
+
+    public void setQuadSize(int quadSize) {
+        this.quadSize = quadSize;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        //TODO: serialization
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        //TODO: serialization
+    }
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java
new file mode 100644
index 0000000..3ab34c9
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import com.jme3.terrain.geomipmap.UpdatedTerrainPatch;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Calculates the LOD of the terrain based on its distance from the
+ * cameras. Taking the minimum distance from all cameras.
+ *
+ * @author bowens
+ */
+public class DistanceLodCalculator implements LodCalculator {
+
+    private int size; // size of a terrain patch
+    private float lodMultiplier = 2;
+    private boolean turnOffLod = false;
+    
+    public DistanceLodCalculator() {
+    }
+    
+    public DistanceLodCalculator(int patchSize, float multiplier) {
+        this.size = patchSize;
+        this.lodMultiplier = multiplier;
+    }
+    
+    public boolean calculateLod(TerrainPatch terrainPatch, List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
+        float distance = getCenterLocation(terrainPatch).distance(locations.get(0));
+
+        
+        if (turnOffLod) {
+            // set to full detail
+            int prevLOD = terrainPatch.getLod();
+            UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
+            if (utp == null) {
+                utp = new UpdatedTerrainPatch(terrainPatch, 0);
+                updates.put(utp.getName(), utp);
+            }
+            utp.setNewLod(0);
+            utp.setPreviousLod(prevLOD);
+            utp.setReIndexNeeded(true);
+            return true;
+        }
+        
+        // go through each lod level to find the one we are in
+        for (int i = 0; i <= terrainPatch.getMaxLod(); i++) {
+            if (distance < getLodDistanceThreshold() * (i + 1)*terrainPatch.getWorldScale().x || i == terrainPatch.getMaxLod()) {
+                boolean reIndexNeeded = false;
+                if (i != terrainPatch.getLod()) {
+                    reIndexNeeded = true;
+                    //System.out.println("lod change: "+lod+" > "+i+"    dist: "+distance);
+                }
+                int prevLOD = terrainPatch.getLod();
+                //previousLod = lod;
+                //lod = i;
+                UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
+                if (utp == null) {
+                    utp = new UpdatedTerrainPatch(terrainPatch, i);//save in here, do not update actual variables
+                    updates.put(utp.getName(), utp);
+                }
+                utp.setPreviousLod(prevLOD);
+                utp.setReIndexNeeded(reIndexNeeded);
+
+                return reIndexNeeded;
+            }
+        }
+
+        return false;
+    }
+
+    protected Vector3f getCenterLocation(TerrainPatch terrainPatch) {
+        Vector3f loc = terrainPatch.getWorldTranslation().clone();
+        loc.x += terrainPatch.getSize()*terrainPatch.getWorldScale().x / 2;
+        loc.z += terrainPatch.getSize()*terrainPatch.getWorldScale().z / 2;
+        return loc;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(size, "patchSize", 32);
+        oc.write(lodMultiplier, "lodMultiplier", 32);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        size = ic.readInt("patchSize", 32);
+        lodMultiplier = ic.readFloat("lodMultiplier", 2.7f);
+    }
+
+    @Override
+    public LodCalculator clone() {
+        DistanceLodCalculator clone = new DistanceLodCalculator(size, lodMultiplier);
+        return clone;
+    }
+
+    @Override
+    public String toString() {
+        return "DistanceLodCalculator "+size+"*"+lodMultiplier;
+    }
+
+    /**
+     * Gets the camera distance where the LOD level will change
+     */
+    protected float getLodDistanceThreshold() {
+        return size*lodMultiplier;
+    }
+    
+    /**
+     * Does this calculator require the terrain to have the difference of 
+     * LOD levels of neighbours to be more than 1.
+     */
+    public boolean usesVariableLod() {
+        return false;
+    }
+
+    public float getLodMultiplier() {
+        return lodMultiplier;
+    }
+
+    public void setLodMultiplier(float lodMultiplier) {
+        this.lodMultiplier = lodMultiplier;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public void turnOffLod() {
+        turnOffLod = true;
+    }
+    
+    public boolean isLodOff() {
+        return turnOffLod;
+    }
+    
+    public void turnOnLod() {
+        turnOffLod = false;
+    }
+    
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java
new file mode 100644
index 0000000..2d6c364
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import com.jme3.terrain.geomipmap.UpdatedTerrainPatch;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Calculate the Level of Detail of a terrain patch based on the
+ * cameras, or other locations.
+ *
+ * @author Brent Owens
+ */
+public interface LodCalculator extends Savable, Cloneable {
+
+    public boolean calculateLod(TerrainPatch terrainPatch, List<Vector3f> locations, HashMap<String,UpdatedTerrainPatch> updates);
+    
+    public LodCalculator clone();
+    
+    public void turnOffLod();
+    public void turnOnLod();
+    public boolean isLodOff();
+
+    /**
+     * If true, then this calculator can cause neighbouring terrain chunks to 
+     * have LOD levels that are greater than 1 apart.
+     * Entropy algorithms will want to return true for this. Straight distance
+     * calculations will just want to return false.
+     */
+    public boolean usesVariableLod();
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java
new file mode 100644
index 0000000..6d5f5b8
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.Savable;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+
+/**
+ * Creates LOD Calculator objects for the terrain patches.
+ *
+ * @author Brent Owens
+ * @deprecated phasing this out
+ */
+public interface LodCalculatorFactory extends Savable, Cloneable {
+
+    public LodCalculator createCalculator();
+    public LodCalculator createCalculator(TerrainPatch terrainPatch);
+
+    public LodCalculatorFactory clone();
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java
new file mode 100644
index 0000000..29d5c25
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import java.io.IOException;
+
+/**
+ *
+ * @author bowens
+ * @deprecated phasing out
+ */
+public class LodDistanceCalculatorFactory implements LodCalculatorFactory {
+
+    private float lodThresholdSize = 2.7f;
+    private LodThreshold lodThreshold = null;
+
+
+    public LodDistanceCalculatorFactory() {
+    }
+    
+    public LodDistanceCalculatorFactory(LodThreshold lodThreshold) {
+        this.lodThreshold = lodThreshold;
+    }
+
+    public LodCalculator createCalculator() {
+        return new DistanceLodCalculator();
+    }
+
+    public LodCalculator createCalculator(TerrainPatch terrainPatch) {
+        return new DistanceLodCalculator();
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+		OutputCapsule c = ex.getCapsule(this);
+		c.write(lodThreshold, "lodThreshold", null);
+        c.write(lodThresholdSize, "lodThresholdSize", 2);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule c = im.getCapsule(this);
+		lodThresholdSize = c.readFloat("lodThresholdSize", 2);
+        lodThreshold = (LodThreshold) c.readSavable("lodThreshold", null);
+    }
+
+    @Override
+    public LodDistanceCalculatorFactory clone() {
+        LodDistanceCalculatorFactory clone = new LodDistanceCalculatorFactory();
+        clone.lodThreshold = lodThreshold.clone();
+        clone.lodThresholdSize = lodThresholdSize;
+        return clone;
+    }
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java
new file mode 100644
index 0000000..b453e26
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.renderer.Camera;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import java.io.IOException;
+
+/**
+ * TODO: Make it work with multiple cameras
+ * TODO: Fix the cracks when the lod differences are greater than 1
+ * for two adjacent blocks.
+ * @deprecated phasing out
+ */
+public class LodPerspectiveCalculatorFactory implements LodCalculatorFactory {
+
+    private Camera cam;
+    private float pixelError;
+
+    public LodPerspectiveCalculatorFactory(Camera cam, float pixelError){
+        this.cam = cam;
+        this.pixelError = pixelError;
+    }
+
+    public LodCalculator createCalculator() {
+        return new PerspectiveLodCalculator(cam, pixelError);
+    }
+
+    public LodCalculator createCalculator(TerrainPatch terrainPatch) {
+        PerspectiveLodCalculator p = new PerspectiveLodCalculator(cam, pixelError);
+        return p;
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+    }
+
+    public void read(JmeImporter im) throws IOException {
+    }
+
+    @Override
+    public LodCalculatorFactory clone() {
+        try {
+            return (LodCalculatorFactory) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java
new file mode 100644
index 0000000..73d5c35
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java
@@ -0,0 +1,54 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;

+

+import com.jme3.export.Savable;

+

+

+/**

+ * Calculates the LOD value based on where the camera is.

+ * This is plugged into the Terrain system and any terrain

+ * using LOD will use this to determine when a patch of the 

+ * terrain should switch Levels of Detail.

+ * 

+ * @author bowens

+ */

+public interface LodThreshold extends Savable, Cloneable {

+

+    /**

+     * A distance of how far between each LOD threshold.

+     */

+    public float getLodDistanceThreshold();

+

+    public LodThreshold clone();

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java
new file mode 100644
index 0000000..312b17d
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import com.jme3.terrain.geomipmap.UpdatedTerrainPatch;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+
+public class PerspectiveLodCalculator implements LodCalculator {
+
+    private TerrainPatch patch;
+    private Camera cam;
+    private float[] entropyDistances;
+    private float pixelError;
+
+    public PerspectiveLodCalculator() {}
+    
+    public PerspectiveLodCalculator(Camera cam, float pixelError){
+        this.cam = cam;
+        this.pixelError = pixelError;
+    }
+
+    /**
+     * This computes the "C" value in the geomipmapping paper.
+     * See section "2.3.1.2 Pre-calculating d"
+     * 
+     * @param cam
+     * @param pixelLimit
+     * @return
+     */
+    private float getCameraConstant(Camera cam, float pixelLimit){
+        float n = cam.getFrustumNear();
+        float t = FastMath.abs(cam.getFrustumTop());
+        float A = n / t;
+        float v_res = cam.getHeight();
+        float T = (2f * pixelLimit) / v_res;
+        return A / T;
+    }
+    
+    public boolean calculateLod(List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
+        return calculateLod(patch, locations, updates);
+    }
+
+    public boolean calculateLod(TerrainPatch terrainPatch, List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
+        if (entropyDistances == null){
+            // compute entropy distances
+            float[] lodEntropies = patch.getLodEntropies();
+            entropyDistances = new float[lodEntropies.length];
+            float cameraConstant = getCameraConstant(cam, pixelError);
+            for (int i = 0; i < lodEntropies.length; i++){
+                entropyDistances[i] = lodEntropies[i] * cameraConstant;
+            }
+        }
+
+        Vector3f patchPos = getCenterLocation(patch);
+
+        // vector from camera to patch
+        //Vector3f toPatchDir = locations.get(0).subtract(patchPos).normalizeLocal();
+        //float facing = cam.getDirection().dot(toPatchDir);
+        float distance = patchPos.distance(locations.get(0));
+
+        // go through each lod level to find the one we are in
+        for (int i = 0; i <= patch.getMaxLod(); i++) {
+            if (distance < entropyDistances[i] || i == patch.getMaxLod()){
+                boolean reIndexNeeded = false;
+                if (i != patch.getLod()) {
+                    reIndexNeeded = true;
+//                    System.out.println("lod change: "+lod+" > "+i+"    dist: "+distance);
+                }
+                int prevLOD = patch.getLod();
+
+                //previousLod = lod;
+                //lod = i;
+                UpdatedTerrainPatch utp = updates.get(patch.getName());
+                if (utp == null) {
+                    utp = new UpdatedTerrainPatch(patch, i);//save in here, do not update actual variables
+                    updates.put(utp.getName(), utp);
+                }
+                utp.setPreviousLod(prevLOD);
+                utp.setReIndexNeeded(reIndexNeeded);
+                return reIndexNeeded;
+            }
+        }
+
+        return false;
+    }
+
+    public Vector3f getCenterLocation(TerrainPatch patch) {
+        Vector3f loc = patch.getWorldTranslation().clone();
+        loc.x += patch.getSize() / 2;
+        loc.z += patch.getSize() / 2;
+        return loc;
+    }
+
+    @Override
+    public LodCalculator clone() {
+        try {
+            return (LodCalculator) super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+    }
+
+    public void read(JmeImporter im) throws IOException {
+    }
+
+    public boolean usesVariableLod() {
+        return true;
+    }
+
+    public float getPixelError() {
+        return pixelError;
+    }
+
+    public void setPixelError(float pixelError) {
+        this.pixelError = pixelError;
+    }
+
+    public void setCam(Camera cam) {
+        this.cam = cam;
+    }
+
+    public void turnOffLod() {
+        //TODO
+    }
+
+    public boolean isLodOff() {
+        return false; //TODO
+    }
+    
+    public void turnOnLod() {
+        //TODO
+    }
+    
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java
new file mode 100644
index 0000000..bd512e5
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java
@@ -0,0 +1,116 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.geomipmap.lodcalc;

+

+import com.jme3.export.InputCapsule;

+import com.jme3.export.JmeExporter;

+import com.jme3.export.JmeImporter;

+import com.jme3.export.OutputCapsule;

+import com.jme3.terrain.Terrain;

+import com.jme3.terrain.geomipmap.TerrainQuad;

+import java.io.IOException;

+

+

+/**

+ * Just multiplies the terrain patch size by 2. So every two

+ * patches away the camera is, the LOD changes.

+ * 

+ * Set it higher to have the LOD change less frequently.

+ * 

+ * @author bowens

+ */

+public class SimpleLodThreshold implements LodThreshold {

+	

+    private int size; // size of a terrain patch

+    private float lodMultiplier = 2;

+

+    

+    public SimpleLodThreshold() {

+    }

+    

+    public SimpleLodThreshold(Terrain terrain) {

+        if (terrain instanceof TerrainQuad)

+            this.size = ((TerrainQuad)terrain).getPatchSize();

+    }

+

+    public SimpleLodThreshold(int patchSize, float lodMultiplier) {

+        this.size = patchSize;

+    }

+

+    public float getLodMultiplier() {

+        return lodMultiplier;

+    }

+

+    public void setLodMultiplier(float lodMultiplier) {

+        this.lodMultiplier = lodMultiplier;

+    }

+

+    public int getSize() {

+        return size;

+    }

+

+    public void setSize(int size) {

+        this.size = size;

+    }

+	

+

+    public float getLodDistanceThreshold() {

+        return size*lodMultiplier;

+    }

+

+    public void write(JmeExporter ex) throws IOException {

+        OutputCapsule oc = ex.getCapsule(this);

+        oc.write(size, "size", 16);

+        oc.write(lodMultiplier, "lodMultiplier", 2);

+    }

+

+    public void read(JmeImporter im) throws IOException {

+        InputCapsule ic = im.getCapsule(this);

+        size = ic.readInt("size", 16);

+        lodMultiplier = ic.readInt("lodMultiplier", 2);

+    }

+

+    @Override

+    public LodThreshold clone() {

+        SimpleLodThreshold clone = new SimpleLodThreshold();

+        clone.size = size;

+        clone.lodMultiplier = lodMultiplier;

+        

+        return clone;

+    }

+

+    @Override

+    public String toString() {

+        return "SimpleLodThreshold "+size+", "+lodMultiplier;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java
new file mode 100644
index 0000000..c358aae
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java
@@ -0,0 +1,75 @@
+package com.jme3.terrain.geomipmap.lodcalc.util;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.collision.CollisionResults;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Computes the entropy value δ (delta) for a given terrain block and
+ * LOD level.
+ * See the geomipmapping paper section
+ * "2.3.1 Choosing the appropriate GeoMipMap level"
+ *
+ * @author Kirill Vainer
+ */
+public class EntropyComputeUtil {
+
+    public static float computeLodEntropy(Mesh terrainBlock, IntBuffer lodIndices){
+        // Bounding box for the terrain block
+        BoundingBox bbox = (BoundingBox) terrainBlock.getBound();
+
+        // Vertex positions for the block
+        FloatBuffer positions = terrainBlock.getFloatBuffer(Type.Position);
+
+        // Prepare to cast rays
+        Vector3f pos = new Vector3f();
+        Vector3f dir = new Vector3f(0, -1, 0);
+        Ray ray = new Ray(pos, dir);
+
+        // Prepare collision results
+        CollisionResults results = new CollisionResults();
+
+        // Set the LOD indices on the block
+        VertexBuffer originalIndices = terrainBlock.getBuffer(Type.Index);
+
+        terrainBlock.clearBuffer(Type.Index);
+        terrainBlock.setBuffer(Type.Index, 3, lodIndices);
+
+        // Recalculate collision mesh
+        terrainBlock.createCollisionData();
+
+        float entropy = 0;
+        for (int i = 0; i < positions.capacity() / 3; i++){
+            BufferUtils.populateFromBuffer(pos, positions, i);
+
+            float realHeight = pos.y;
+
+            pos.addLocal(0, bbox.getYExtent(), 0);
+            ray.setOrigin(pos);
+
+            results.clear();
+            terrainBlock.collideWith(ray, Matrix4f.IDENTITY, bbox, results);
+
+            if (results.size() > 0){
+                Vector3f contactPoint = results.getClosestCollision().getContactPoint();
+                float delta = Math.abs(realHeight - contactPoint.y);
+                entropy = Math.max(delta, entropy);
+            }
+        }
+
+        // Restore original indices
+        terrainBlock.clearBuffer(Type.Index);
+        terrainBlock.setBuffer(originalIndices);
+
+        return entropy;
+    }
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java
new file mode 100644
index 0000000..9f044c6
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.picking;
+
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.math.Ray;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.picking.BresenhamYUpGridTracer.Direction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * It basically works by casting a pick ray
+ * against the bounding volumes of the TerrainQuad and its children, gathering
+ * all of the TerrainPatches hit (in distance order.) The triangles of each patch
+ * are then tested using the BresenhamYUpGridTracer to determine which triangles
+ * to test and in what order. When a hit is found, it is guaranteed to be the
+ * first such hit and can immediately be returned.
+ * 
+ * @author Joshua Slack
+ * @author Brent Owens
+ */
+public class BresenhamTerrainPicker implements TerrainPicker {
+
+    private final Triangle gridTriA = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
+    private final Triangle gridTriB = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
+
+    private final Vector3f calcVec1 = new Vector3f();
+    private final Ray workRay = new Ray();
+    private final Ray worldPickRay = new Ray();
+
+    private final TerrainQuad root;
+    private final BresenhamYUpGridTracer tracer = new BresenhamYUpGridTracer();
+
+
+    public BresenhamTerrainPicker(TerrainQuad root) {
+        this.root = root;
+    }
+
+    public Vector3f getTerrainIntersection(Ray worldPick, CollisionResults results) {
+
+        worldPickRay.set(worldPick);
+        List<TerrainPickData> pickData = new ArrayList<TerrainPickData>();
+        root.findPick(worldPick.clone(), pickData);
+        Collections.sort(pickData);
+
+        if (pickData.isEmpty())
+            return null;
+
+        workRay.set(worldPick);
+
+        for (TerrainPickData pd : pickData) {
+            TerrainPatch patch = pd.targetPatch;
+
+
+            tracer.getGridSpacing().set(patch.getWorldScale());
+            tracer.setGridOrigin(patch.getWorldTranslation());
+
+            workRay.getOrigin().set(worldPick.getDirection()).multLocal(pd.cr.getDistance()-.1f).addLocal(worldPick.getOrigin());
+
+            tracer.startWalk(workRay);
+
+            final Vector3f intersection = new Vector3f();
+            final Vector2f loc = tracer.getGridLocation();
+
+            if (tracer.isRayPerpendicularToGrid()) {
+                Triangle hit = new Triangle();
+                checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit);
+                float distance = worldPickRay.origin.distance(intersection);
+                CollisionResult cr = new CollisionResult(intersection, distance);
+                cr.setGeometry(patch);
+                cr.setContactNormal(hit.getNormal());
+                results.addCollision(cr);
+                return intersection;
+            }
+            
+            
+
+            while (loc.x >= -1 && loc.x <= patch.getSize() && 
+                   loc.y >= -1 && loc.y <= patch.getSize()) {
+
+                //System.out.print(loc.x+","+loc.y+" : ");
+                // check the triangles of main square for intersection.
+                Triangle hit = new Triangle();
+                if (checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit)) {
+                    // we found an intersection, so return that!
+                    float distance = worldPickRay.origin.distance(intersection);
+                    CollisionResult cr = new CollisionResult(intersection, distance);
+                    cr.setGeometry(patch);
+                    results.addCollision(cr);
+                    cr.setContactNormal(hit.getNormal());
+                    return intersection;
+                }
+
+                // because of how we get our height coords, we will
+                // sometimes be off by a grid spot, so we check the next
+                // grid space up.
+                int dx = 0, dz = 0;
+                Direction d = tracer.getLastStepDirection();
+                switch (d) {
+                case PositiveX:
+                case NegativeX:
+                    dx = 0;
+                    dz = 1;
+                    break;
+                case PositiveZ:
+                case NegativeZ:
+                    dx = 1;
+                    dz = 0;
+                    break;
+                }
+
+                if (checkTriangles(loc.x + dx, loc.y + dz, workRay, intersection, patch, hit)) {
+                    // we found an intersection, so return that!
+                    float distance = worldPickRay.origin.distance(intersection);
+                    CollisionResult cr = new CollisionResult(intersection, distance);
+                    results.addCollision(cr);
+                    cr.setGeometry(patch);
+                    cr.setContactNormal(hit.getNormal());
+                    return intersection;
+                }
+
+                tracer.next();
+            }
+        }
+
+        return null;
+    }
+
+    protected boolean checkTriangles(float gridX, float gridY, Ray pick, Vector3f intersection, TerrainPatch patch, Triangle store) {
+        if (!getTriangles(gridX, gridY, patch))
+            return false;
+
+        if (pick.intersectWhere(gridTriA, intersection)) {
+            store.set(gridTriA.get1(), gridTriA.get2(), gridTriA.get3());
+            return true;
+        } else {
+            if (pick.intersectWhere(gridTriB, intersection)) {
+                store.set(gridTriB.get1(), gridTriB.get2(), gridTriB.get3());
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Request the triangles (in world coord space) of a TerrainBlock that
+     * correspond to the given grid location. The triangles are stored in the
+     * class fields _gridTriA and _gridTriB.
+     *
+     * @param gridX
+     *            grid row
+     * @param gridY
+     *            grid column
+     * @param block
+     *            the TerrainBlock we are working with
+     * @return true if the grid point is valid for the given block, false if it
+     *         is off the block.
+     */
+    protected boolean getTriangles(float gridX, float gridY, TerrainPatch patch) {
+        calcVec1.set(gridX, 0, gridY);
+        int index = findClosestHeightIndex(calcVec1, patch);
+
+        if (index == -1)
+            return false;
+        
+        Triangle[] t = patch.getGridTriangles(gridX, gridY);
+        if (t == null || t.length == 0)
+            return false;
+        
+        gridTriA.set1(t[0].get1());
+        gridTriA.set2(t[0].get2());
+        gridTriA.set3(t[0].get3());
+
+        gridTriB.set1(t[1].get1());
+        gridTriB.set2(t[1].get2());
+        gridTriB.set3(t[1].get3());
+
+        return true;
+    }
+
+    /**
+     * Finds the closest height point to a position. Will always be left/above
+     * that position.
+     *
+     * @param position
+     *            the position to check at
+     * @param block
+     *            the block to get height values from
+     * @return an index to the height position of the given block.
+     */
+    protected int findClosestHeightIndex(Vector3f position, TerrainPatch patch) {
+
+        int x = (int) position.x;
+        int z = (int) position.z;
+
+        if (x < 0 || x >= patch.getSize() - 1) {
+            return -1;
+        }
+        if (z < 0 || z >= patch.getSize() - 1) {
+            return -1;
+        }
+
+        return z * patch.getSize() + x;
+    }
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java
new file mode 100644
index 0000000..8d8a53d
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.picking;
+
+import com.jme3.math.Ray;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+
+/**
+ * Works on the XZ plane, with positive Y as up.
+ *
+ * @author Joshua Slack
+ * @author Brent Owens
+ */
+public class BresenhamYUpGridTracer {
+
+    protected Vector3f gridOrigin = new Vector3f();
+    protected Vector3f gridSpacing = new Vector3f();
+    protected Vector2f gridLocation = new Vector2f();
+    protected Vector3f rayLocation = new Vector3f();
+    protected Ray walkRay = new Ray();
+
+    protected Direction stepDirection = Direction.None;
+    protected float rayLength;
+
+    public static enum Direction {
+        None, PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ;
+    };
+
+    // a "near zero" value we will use to determine if the walkRay is
+    // perpendicular to the grid.
+    protected static float TOLERANCE = 0.0000001f;
+
+    private int stepXDirection;
+    private int stepZDirection;
+
+    // from current position along ray
+    private float distToNextXIntersection, distToNextZIntersection;
+    private float distBetweenXIntersections, distBetweenZIntersections;
+
+    public void startWalk(final Ray walkRay) {
+        // store ray
+        this.walkRay.set(walkRay);
+
+        // simplify access to direction
+        Vector3f direction = this.walkRay.getDirection();
+
+        // Move start point to grid space
+        Vector3f start = this.walkRay.getOrigin().subtract(gridOrigin);
+
+        gridLocation.x = (int) (start.x / gridSpacing.x);
+        gridLocation.y = (int) (start.z / gridSpacing.z);
+
+        Vector3f ooDirection = new Vector3f(1.0f / direction.x, 1,1.0f / direction.z);
+
+        // Check which direction on the X world axis we are moving.
+        if (direction.x > TOLERANCE) {
+            distToNextXIntersection = ((gridLocation.x + 1) * gridSpacing.x - start.x) * ooDirection.x;
+            distBetweenXIntersections = gridSpacing.x * ooDirection.x;
+            stepXDirection = 1;
+        } else if (direction.x < -TOLERANCE) {
+            distToNextXIntersection = (start.x - (gridLocation.x * gridSpacing.x)) * -direction.x;
+            distBetweenXIntersections = -gridSpacing.x * ooDirection.x;
+            stepXDirection = -1;
+        } else {
+            distToNextXIntersection = Float.MAX_VALUE;
+            distBetweenXIntersections = Float.MAX_VALUE;
+            stepXDirection = 0;
+        }
+
+        // Check which direction on the Z world axis we are moving.
+        if (direction.z > TOLERANCE) {
+            distToNextZIntersection = ((gridLocation.y + 1) * gridSpacing.z - start.z) * ooDirection.z;
+            distBetweenZIntersections = gridSpacing.z * ooDirection.z;
+            stepZDirection = 1;
+        } else if (direction.z < -TOLERANCE) {
+            distToNextZIntersection = (start.z - (gridLocation.y * gridSpacing.z)) * -direction.z;
+            distBetweenZIntersections = -gridSpacing.z * ooDirection.z;
+            stepZDirection = -1;
+        } else {
+            distToNextZIntersection = Float.MAX_VALUE;
+            distBetweenZIntersections = Float.MAX_VALUE;
+            stepZDirection = 0;
+        }
+
+        // Reset some variables
+        rayLocation.set(start);
+        rayLength = 0.0f;
+        stepDirection = Direction.None;
+    }
+
+    public void next() {
+        // Walk us to our next location based on distances to next X or Z grid
+        // line.
+        if (distToNextXIntersection < distToNextZIntersection) {
+            rayLength = distToNextXIntersection;
+            gridLocation.x += stepXDirection;
+            distToNextXIntersection += distBetweenXIntersections;
+            switch (stepXDirection) {
+            case -1:
+                stepDirection = Direction.NegativeX;
+                break;
+            case 0:
+                stepDirection = Direction.None;
+                break;
+            case 1:
+                stepDirection = Direction.PositiveX;
+                break;
+            }
+        } else {
+            rayLength = distToNextZIntersection;
+            gridLocation.y += stepZDirection;
+            distToNextZIntersection += distBetweenZIntersections;
+            switch (stepZDirection) {
+            case -1:
+                stepDirection = Direction.NegativeZ;
+                break;
+            case 0:
+                stepDirection = Direction.None;
+                break;
+            case 1:
+                stepDirection = Direction.PositiveZ;
+                break;
+            }
+        }
+
+        rayLocation.set(walkRay.direction).multLocal(rayLength).addLocal(walkRay.origin);
+    }
+
+    public Direction getLastStepDirection() {
+        return stepDirection;
+    }
+
+    public boolean isRayPerpendicularToGrid() {
+        return stepXDirection == 0 && stepZDirection == 0;
+    }
+
+
+    public Vector2f getGridLocation() {
+        return gridLocation;
+    }
+
+    public Vector3f getGridOrigin() {
+        return gridOrigin;
+    }
+
+    public Vector3f getGridSpacing() {
+        return gridSpacing;
+    }
+
+
+    public void setGridLocation(Vector2f gridLocation) {
+        this.gridLocation = gridLocation;
+    }
+
+    public void setGridOrigin(Vector3f gridOrigin) {
+        this.gridOrigin = gridOrigin;
+    }
+
+    public void setGridSpacing(Vector3f gridSpacing) {
+        this.gridSpacing = gridSpacing;
+    }
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPickData.java b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPickData.java
new file mode 100644
index 0000000..bee7623
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPickData.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.picking;
+
+import com.jme3.collision.CollisionResult;
+import com.jme3.terrain.geomipmap.TerrainPatch;
+
+/**
+ * Pick result on a terrain patch with the intersection on the bounding box
+ * of that terrain patch.
+ *
+ * @author Brent Owens
+ */
+public class TerrainPickData implements Comparable {
+
+    protected TerrainPatch targetPatch;
+    protected CollisionResult cr;
+
+    public TerrainPickData() {
+    }
+
+    public TerrainPickData(TerrainPatch patch, CollisionResult cr) {
+        this.targetPatch = patch;
+        this.cr = cr;
+    }
+
+    public int compareTo(Object o) {
+        if (o instanceof TerrainPickData) {
+            TerrainPickData tpd = (TerrainPickData) o;
+            if (this.cr.getDistance() < tpd.cr.getDistance())
+                return -1;
+            else if (this.cr.getDistance() == tpd.cr.getDistance())
+                return 0;
+            else
+                return 1;
+        }
+
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if(obj instanceof TerrainPickData){
+            return ((TerrainPickData)obj).compareTo(this) == 0;
+        }
+        return super.equals(obj);
+    }
+    
+}
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPicker.java b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPicker.java
new file mode 100644
index 0000000..67df8e7
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/picking/TerrainPicker.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain.geomipmap.picking;
+
+import com.jme3.collision.CollisionResults;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+
+/**
+ * Pick the location on the terrain from a given ray.
+ *
+ * @author Brent Owens
+ */
+public interface TerrainPicker {
+
+    /**
+     * Ask for the point of intersection between the given ray and the terrain.
+     *
+     * @param worldPick
+     *            our pick ray, in world space.
+     * @return null if no pick is found. Otherwise it returns a Vector3f  populated with the pick
+     *         coordinates.
+     */
+    public Vector3f getTerrainIntersection(final Ray worldPick, CollisionResults results);
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/AbstractHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/AbstractHeightMap.java
new file mode 100644
index 0000000..3193f52
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/AbstractHeightMap.java
@@ -0,0 +1,485 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import java.io.DataOutputStream;

+import java.io.FileNotFoundException;

+import java.io.FileOutputStream;

+import java.io.IOException;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * <code>AbstractHeightMap</code> provides a base implementation of height

+ * data for terrain rendering. The loading of the data is dependent on the

+ * subclass. The abstract implementation provides a means to retrieve the height

+ * data and to save it.

+ *

+ * It is the general contract that any subclass provide a means of editing

+ * required attributes and calling <code>load</code> again to recreate a

+ * heightfield with these new parameters.

+ *

+ * @author Mark Powell

+ * @version $Id: AbstractHeightMap.java 4133 2009-03-19 20:40:11Z blaine.dev $

+ */

+public abstract class AbstractHeightMap implements HeightMap {

+

+    private static final Logger logger = Logger.getLogger(AbstractHeightMap.class.getName());

+    /** Height data information. */

+    protected float[] heightData = null;

+    /** The size of the height map's width. */

+    protected int size = 0;

+    /** Allows scaling the Y height of the map. */

+    protected float heightScale = 1.0f;

+    /** The filter is used to erode the terrain. */

+    protected float filter = 0.5f;

+    /** The range used to normalize terrain */

+    public static float NORMALIZE_RANGE = 255f;

+

+    /**

+     * <code>unloadHeightMap</code> clears the data of the height map. This

+     * insures it is ready for reloading.

+     */

+    public void unloadHeightMap() {

+        heightData = null;

+    }

+

+    /**

+     * <code>setHeightScale</code> sets the scale of the height values.

+     * Typically, the height is a little too extreme and should be scaled to a

+     * smaller value (i.e. 0.25), to produce cleaner slopes.

+     *

+     * @param scale

+     *            the scale to multiply height values by.

+     */

+    public void setHeightScale(float scale) {

+        heightScale = scale;

+    }

+

+    /**

+     * <code>setHeightAtPoint</code> sets the height value for a given

+     * coordinate. It is recommended that the height value be within the 0 - 255

+     * range.

+     *

+     * @param height

+     *            the new height for the coordinate.

+     * @param x

+     *            the x (east/west) coordinate.

+     * @param z

+     *            the z (north/south) coordinate.

+     */

+    public void setHeightAtPoint(float height, int x, int z) {

+        heightData[x + (z * size)] = height;

+    }

+

+    /**

+     * <code>setSize</code> sets the size of the terrain where the area is

+     * size x size.

+     *

+     * @param size

+     *            the new size of the terrain.

+     * @throws Exception 

+     *

+     * @throws JmeException

+     *             if the size is less than or equal to zero.

+     */

+    public void setSize(int size) throws Exception {

+        if (size <= 0) {

+            throw new Exception("size must be greater than zero.");

+        }

+

+        this.size = size;

+    }

+

+    /**

+     * <code>setFilter</code> sets the erosion value for the filter. This

+     * value must be between 0 and 1, where 0.2 - 0.4 produces arguably the best

+     * results.

+     *

+     * @param filter

+     *            the erosion value.

+     * @throws Exception 

+     * @throws JmeException

+     *             if filter is less than 0 or greater than 1.

+     */

+    public void setMagnificationFilter(float filter) throws Exception {

+        if (filter < 0 || filter >= 1) {

+            throw new Exception("filter must be between 0 and 1");

+        }

+        this.filter = filter;

+    }

+

+    /**

+     * <code>getTrueHeightAtPoint</code> returns the non-scaled value at the

+     * point provided.

+     *

+     * @param x

+     *            the x (east/west) coordinate.

+     * @param z

+     *            the z (north/south) coordinate.

+     * @return the value at (x,z).

+     */

+    public float getTrueHeightAtPoint(int x, int z) {

+        //logger.info( heightData[x + (z*size)]);

+        return heightData[x + (z * size)];

+    }

+

+    /**

+     * <code>getScaledHeightAtPoint</code> returns the scaled value at the

+     * point provided.

+     *

+     * @param x

+     *            the x (east/west) coordinate.

+     * @param z

+     *            the z (north/south) coordinate.

+     * @return the scaled value at (x, z).

+     */

+    public float getScaledHeightAtPoint(int x, int z) {

+        return ((heightData[x + (z * size)]) * heightScale);

+    }

+

+    /**

+     * <code>getInterpolatedHeight</code> returns the height of a point that

+     * does not fall directly on the height posts.

+     *

+     * @param x

+     *            the x coordinate of the point.

+     * @param z

+     *            the y coordinate of the point.

+     * @return the interpolated height at this point.

+     */

+    public float getInterpolatedHeight(float x, float z) {

+        float low, highX, highZ;

+        float intX, intZ;

+        float interpolation;

+

+        low = getScaledHeightAtPoint((int) x, (int) z);

+

+        if (x + 1 >= size) {

+            return low;

+        }

+

+        highX = getScaledHeightAtPoint((int) x + 1, (int) z);

+

+        interpolation = x - (int) x;

+        intX = ((highX - low) * interpolation) + low;

+

+        if (z + 1 >= size) {

+            return low;

+        }

+

+        highZ = getScaledHeightAtPoint((int) x, (int) z + 1);

+

+        interpolation = z - (int) z;

+        intZ = ((highZ - low) * interpolation) + low;

+

+        return ((intX + intZ) / 2);

+    }

+

+    /**

+     * <code>getHeightMap</code> returns the entire grid of height data.

+     *

+     * @return the grid of height data.

+     */

+    public float[] getHeightMap() {

+        return heightData;

+    }

+

+    /**

+     * Build a new array of height data with the scaled values.

+     * @return

+     */

+    public float[] getScaledHeightMap() {

+        float[] hm = new float[heightData.length];

+        for (int i=0; i<heightData.length; i++) {

+            hm[i] = heightScale * heightData[i];

+        }

+        return hm;

+    }

+

+    /**

+     * <code>getSize</code> returns the size of one side the height map. Where

+     * the area of the height map is size x size.

+     *

+     * @return the size of a single side.

+     */

+    public int getSize() {

+        return size;

+    }

+

+    /**

+     * <code>save</code> will save the heightmap data into a new RAW file

+     * denoted by the supplied filename.

+     *

+     * @param filename

+     *            the file name to save the current data as.

+     * @return true if the save was successful, false otherwise.

+     * @throws Exception 

+     *

+     * @throws JmeException

+     *             if filename is null.

+     */

+    public boolean save(String filename) throws Exception {

+        if (null == filename) {

+            throw new Exception("Filename must not be null");

+        }

+        //open the streams and send the height data to the file.

+        try {

+            FileOutputStream fos = new FileOutputStream(filename);

+            DataOutputStream dos = new DataOutputStream(fos);

+            for (int i = 0; i < size; i++) {

+                for (int j = 0; j < size; j++) {

+                    dos.write((int) heightData[j + (i * size)]);

+                }

+            }

+

+            fos.close();

+            dos.close();

+        } catch (FileNotFoundException e) {

+            logger.log(Level.WARNING, "Error opening file {0}", filename);

+            return false;

+        } catch (IOException e) {

+            logger.log(Level.WARNING, "Error writing to file {0}", filename);

+            return false;

+        }

+

+        logger.log(Level.INFO, "Saved terrain to {0}", filename);

+        return true;

+    }

+

+    /**

+     * <code>normalizeTerrain</code> takes the current terrain data and

+     * converts it to values between 0 and <code>value</code>.

+     *

+     * @param value

+     *            the value to normalize to.

+     */

+    public void normalizeTerrain(float value) {

+        float currentMin, currentMax;

+        float height;

+

+        currentMin = heightData[0];

+        currentMax = heightData[0];

+

+        //find the min/max values of the height fTemptemptempBuffer

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                if (heightData[i + j * size] > currentMax) {

+                    currentMax = heightData[i + j * size];

+                } else if (heightData[i + j * size] < currentMin) {

+                    currentMin = heightData[i + j * size];

+                }

+            }

+        }

+

+        //find the range of the altitude

+        if (currentMax <= currentMin) {

+            return;

+        }

+

+        height = currentMax - currentMin;

+

+        //scale the values to a range of 0-255

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                heightData[i + j * size] = ((heightData[i + j * size] - currentMin) / height) * value;

+            }

+        }

+    }

+

+    /**

+     * Find the minimum and maximum height values.

+     * @return a float array with two value: min height, max height

+     */

+    public float[] findMinMaxHeights() {

+        float[] minmax = new float[2];

+

+        float currentMin, currentMax;

+        currentMin = heightData[0];

+        currentMax = heightData[0];

+

+        for (int i = 0; i < heightData.length; i++) {

+            if (heightData[i] > currentMax) {

+                currentMax = heightData[i];

+            } else if (heightData[i] < currentMin) {

+                currentMin = heightData[i];

+            }

+        }

+        minmax[0] = currentMin;

+        minmax[1] = currentMax;

+        return minmax;

+    }

+

+    /**

+     * <code>erodeTerrain</code> is a convenience method that applies the FIR

+     * filter to a given height map. This simulates water errosion.

+     *

+     * @see setFilter

+     */

+    public void erodeTerrain() {

+        //erode left to right

+        float v;

+

+        for (int i = 0; i < size; i++) {

+            v = heightData[i];

+            for (int j = 1; j < size; j++) {

+                heightData[i + j * size] = filter * v + (1 - filter) * heightData[i + j * size];

+                v = heightData[i + j * size];

+            }

+        }

+

+        //erode right to left

+        for (int i = size - 1; i >= 0; i--) {

+            v = heightData[i];

+            for (int j = 0; j < size; j++) {

+                heightData[i + j * size] = filter * v + (1 - filter) * heightData[i + j * size];

+                v = heightData[i + j * size];

+                //erodeBand(tempBuffer[size * i + size - 1], -1);

+            }

+        }

+

+        //erode top to bottom

+        for (int i = 0; i < size; i++) {

+            v = heightData[i];

+            for (int j = 0; j < size; j++) {

+                heightData[i + j * size] = filter * v + (1 - filter) * heightData[i + j * size];

+                v = heightData[i + j * size];

+            }

+        }

+

+        //erode from bottom to top

+        for (int i = size - 1; i >= 0; i--) {

+            v = heightData[i];

+            for (int j = 0; j < size; j++) {

+                heightData[i + j * size] = filter * v + (1 - filter) * heightData[i + j * size];

+                v = heightData[i + j * size];

+            }

+        }

+    }

+

+    /**

+     * Flattens out the valleys. The flatten algorithm makes the valleys more

+     * prominent while keeping the hills mostly intact. This effect is based on

+     * what happens when values below one are squared. The terrain will be

+     * normalized between 0 and 1 for this function to work.

+     *

+     * @param flattening

+     *            the power of flattening applied, 1 means none

+     */

+    public void flatten(byte flattening) {

+        // If flattening is one we can skip the calculations

+        // since it wouldn't change anything. (e.g. 2 power 1 = 2)

+        if (flattening <= 1) {

+            return;

+        }

+

+        float[] minmax = findMinMaxHeights();

+

+        normalizeTerrain(1f);

+

+        for (int x = 0; x < size; x++) {

+            for (int y = 0; y < size; y++) {

+                float flat = 1.0f;

+                float original = heightData[x + y * size];

+

+                // Flatten as many times as desired;

+                for (int i = 0; i < flattening; i++) {

+                    flat *= original;

+                }

+                heightData[x + y * size] = flat;

+            }

+        }

+

+        // re-normalize back to its oraginal height range

+        float height = minmax[1] - minmax[0];

+        normalizeTerrain(height);

+    }

+

+    /**

+     * Smooth the terrain. For each node, its 8 neighbors heights

+     * are averaged and will participate in the  node new height

+     * by a factor <code>np</code> between 0 and 1

+     * 

+     * You must first load() the heightmap data before this will have any effect.

+     * 

+     * @param np

+     *          The factor to what extend the neighbors average has an influence.

+     *          Value of 0 will ignore neighbors (no smoothing)

+     *          Value of 1 will ignore the node old height.

+     */

+    public void smooth(float np) {

+        smooth(np, 1);

+    }

+    

+    /**

+     * Smooth the terrain. For each node, its X(determined by radius) neighbors heights

+     * are averaged and will participate in the  node new height

+     * by a factor <code>np</code> between 0 and 1

+     *

+     * You must first load() the heightmap data before this will have any effect.

+     * 

+     * @param np

+     *          The factor to what extend the neighbors average has an influence.

+     *          Value of 0 will ignore neighbors (no smoothing)

+     *          Value of 1 will ignore the node old height.

+     */

+    public void smooth(float np, int radius) {

+        if (np < 0 || np > 1) {

+            return;

+        }

+        if (radius == 0)

+            radius = 1;

+        

+        for (int x = 0; x < size; x++) {

+            for (int y = 0; y < size; y++) {

+                int neighNumber = 0;

+                float neighAverage = 0;

+                for (int rx = -radius; rx <= radius; rx++) {

+                    for (int ry = -radius; ry <= radius; ry++) {

+                        if (x+rx < 0 || x+rx >= size) {

+                        continue;

+                        }

+                        if (y+ry < 0 || y+ry >= size) {

+                            continue;

+                        }

+                        neighNumber++;

+                        neighAverage += heightData[(x+rx) + (y+ry) * size];

+                    }

+                }

+

+                neighAverage /= neighNumber;

+                float cp = 1 - np;

+                heightData[x + y * size] = neighAverage * np + heightData[x + y * size] * cp;

+            }

+        }

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/CombinerHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/CombinerHeightMap.java
new file mode 100644
index 0000000..7a25834
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/CombinerHeightMap.java
@@ -0,0 +1,269 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import java.util.logging.Logger;

+

+/**

+ * <code>CombinerHeightMap</code> generates a new height map based on

+ * two provided height maps. These had maps can either be added together

+ * or substracted from each other. Each heightmap has a weight to

+ * determine how much one will affect the other. By default it is set to

+ * 0.5, 0.5 and meaning the two heightmaps are averaged evenly. This

+ * value can be adjusted at will, as long as the two factors are equal

+ * to 1.0.

+ *

+ * @author Mark Powell

+ * @version $Id$

+ */

+public class CombinerHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(CombinerHeightMap.class.getName());

+    /**

+     * Constant mode to denote adding the two heightmaps.

+     */

+    public static final int ADDITION = 0;

+    /**

+     * Constant mode to denote subtracting the two heightmaps.

+     */

+    public static final int SUBTRACTION = 1;

+    //the two maps.

+    private AbstractHeightMap map1;

+    private AbstractHeightMap map2;

+    //the two factors

+    private float factor1 = 0.5f;

+    private float factor2 = 0.5f;

+    //the combine mode.

+    private int mode;

+

+    /**

+     * Constructor combines two given heightmaps by the specified mode.

+     * The heightmaps will be evenly distributed. The heightmaps

+     * must be of the same size.

+     *

+     * @param map1 the first heightmap to combine.

+     * @param map2 the second heightmap to combine.

+     * @param mode denotes whether to add or subtract the heightmaps, may

+     *              be either ADDITION or SUBTRACTION.

+     * @throws JmeException if either map is null, their size

+     *              do not match or the mode is invalid.

+     */

+    public CombinerHeightMap(

+            AbstractHeightMap map1,

+            AbstractHeightMap map2,

+            int mode) throws Exception {

+

+

+        //insure all parameters are valid.

+        if (null == map1 || null == map2) {

+            throw new Exception("Height map may not be null");

+        }

+

+

+        if (map1.getSize() != map2.getSize()) {

+            throw new Exception("The two maps must be of the same size");

+        }

+

+

+        if ((factor1 + factor2) != 1.0f) {

+            throw new Exception("factor1 and factor2 must add to 1.0");

+        }

+

+

+        this.size = map1.getSize();

+        this.map1 = map1;

+        this.map2 = map2;

+

+

+        setMode(mode);

+

+        load();

+    }

+

+    /**

+     * Constructor combines two given heightmaps by the specified mode.

+     * The heightmaps will be distributed based on the given factors.

+     * For example, if factor1 is 0.6 and factor2 is 0.4, then 60% of

+     * map1 will be used with 40% of map2. The two factors must add up

+     * to 1.0. The heightmaps must also be of the same size.

+     *

+     * @param map1 the first heightmap to combine.

+     * @param factor1 the factor for map1.

+     * @param map2 the second heightmap to combine.

+     * @param factor2 the factor for map2.

+     * @param mode denotes whether to add or subtract the heightmaps, may

+     *              be either ADDITION or SUBTRACTION.

+     * @throws JmeException if either map is null, their size

+     *              do not match, the mode is invalid, or the factors do not add

+     *              to 1.0.

+     */

+    public CombinerHeightMap(

+            AbstractHeightMap map1,

+            float factor1,

+            AbstractHeightMap map2,

+            float factor2,

+            int mode) throws Exception {

+

+

+        //insure all parameters are valid.

+        if (null == map1 || null == map2) {

+            throw new Exception("Height map may not be null");

+        }

+

+

+        if (map1.getSize() != map2.getSize()) {

+            throw new Exception("The two maps must be of the same size");

+        }

+

+

+        if ((factor1 + factor2) != 1.0f) {

+            throw new Exception("factor1 and factor2 must add to 1.0");

+        }

+

+

+        setMode(mode);

+

+

+        this.size = map1.getSize();

+        this.map1 = map1;

+        this.map2 = map2;

+        this.factor1 = factor1;

+        this.factor2 = factor2;

+

+

+        this.mode = mode;

+

+

+        load();

+    }

+

+    /**

+     * <code>setFactors</code> sets the distribution of heightmaps.

+     * For example, if factor1 is 0.6 and factor2 is 0.4, then 60% of

+     * map1 will be used with 40% of map2. The two factors must add up

+     * to 1.0.

+     * @param factor1 the factor for map1.

+     * @param factor2 the factor for map2.

+     * @throws JmeException if the factors do not add to 1.0.

+     */

+    public void setFactors(float factor1, float factor2) throws Exception {

+        if ((factor1 + factor2) != 1.0f) {

+            throw new Exception("factor1 and factor2 must add to 1.0");

+        }

+

+

+        this.factor1 = factor1;

+        this.factor2 = factor2;

+    }

+

+    /**

+     * <code>setHeightMaps</code> sets the height maps to combine.

+     * The size of the height maps must be the same.

+     * @param map1 the first height map.

+     * @param map2 the second height map.

+     * @throws JmeException if the either heightmap is null, or their

+     *              sizes do not match.

+     */

+    public void setHeightMaps(AbstractHeightMap map1, AbstractHeightMap map2) throws Exception {

+        if (null == map1 || null == map2) {

+            throw new Exception("Height map may not be null");

+        }

+

+

+        if (map1.getSize() != map2.getSize()) {

+            throw new Exception("The two maps must be of the same size");

+        }

+

+

+        this.size = map1.getSize();

+        this.map1 = map1;

+        this.map2 = map2;

+    }

+

+    /**

+     * <code>setMode</code> sets the mode of the combiner. This may either

+     * be ADDITION or SUBTRACTION.

+     * @param mode the mode of the combiner.

+     * @throws JmeException if mode is not ADDITION or SUBTRACTION.

+     */

+    public void setMode(int mode) throws Exception {

+        if (mode != ADDITION && mode != SUBTRACTION) {

+            throw new Exception("Invalid mode");

+        }

+        this.mode = mode;

+    }

+

+    /**

+     * <code>load</code> builds a new heightmap based on the combination of

+     * two other heightmaps. The conditions of the combiner determine the

+     * final outcome of the heightmap.

+     *

+     * @return boolean if the heightmap was successfully created.

+     */

+    public boolean load() {

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+

+

+        heightData = new float[size * size];

+

+

+        float[] temp1 = map1.getHeightMap();

+        float[] temp2 = map2.getHeightMap();

+

+

+        if (mode == ADDITION) {

+            for (int i = 0; i < size; i++) {

+                for (int j = 0; j < size; j++) {

+                    heightData[i + (j * size)] =

+                            (int) (temp1[i + (j * size)] * factor1

+                            + temp2[i + (j * size)] * factor2);

+                }

+            }

+        } else if (mode == SUBTRACTION) {

+            for (int i = 0; i < size; i++) {

+                for (int j = 0; j < size; j++) {

+                    heightData[i + (j * size)] =

+                            (int) (temp1[i + (j * size)] * factor1

+                            - temp2[i + (j * size)] * factor2);

+                }

+            }

+        }

+

+

+        logger.info("Created heightmap using Combiner");

+

+

+        return true;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/FaultHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/FaultHeightMap.java
new file mode 100644
index 0000000..194486a
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/FaultHeightMap.java
@@ -0,0 +1,326 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.terrain.heightmap;

+

+import com.jme3.math.FastMath;

+import java.util.Random;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+/**

+ * Creates an heightmap based on the fault algorithm. Each iteration, a random line

+ * crossing the map is generated. On one side height values are raised, on the other side

+ * lowered.

+ * @author cghislai

+ */

+public class FaultHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(FaultHeightMap.class.getName());

+    /**

+     * Values on one side are lowered, on the other side increased,

+     * creating a step at the fault line

+     */

+    public static final int FAULTTYPE_STEP = 0;

+    /**

+     * Values on one side are lowered, then increase lineary while crossing

+     * the fault line to the other side. The fault line will be a inclined

+     * plane

+     */

+    public static final int FAULTTYPE_LINEAR = 1;

+    /**

+     * Values are lowered on one side, increased on the other, creating a

+     * cosine curve on the fault line

+     */

+    public static final int FAULTTYPE_COSINE = 2;

+    /**

+     * Value are lowered on both side, but increased on the fault line

+     * creating a smooth ridge on the fault line.

+     */

+    public static final int FAULTTYPE_SINE = 3;

+    /**

+     * A linear fault is created

+     */

+    public static final int FAULTSHAPE_LINE = 10;

+    /**

+     * A circular fault is created.

+     */

+    public static final int FAULTSHAPE_CIRCLE = 11;

+    private long seed; // A seed to feed the random

+    private int iterations; // iterations to perform

+    private float minFaultHeight; // the height modification applied

+    private float maxFaultHeight; // the height modification applied

+    private float minRange; // The range for linear and trigo faults

+    private float maxRange; // The range for linear and trigo faults

+    private float minRadius; // radii for circular fault

+    private float maxRadius;

+    private int faultType; // The type of fault

+    private int faultShape; // The type of fault

+

+    /**

+     * Constructor creates the fault. For faulttype other than STEP, a range can

+     * be provided. For faultshape circle, min and max radii can be provided.

+     * Don't forget to reload the map if you have set parameters after the constructor

+     * call.

+     * @param size The size of the heightmap

+     * @param iterations Iterations to perform

+     * @param faultType Type of fault

+     * @param faultShape Shape of the fault -line or circle

+     * @param minFaultHeight Height modified on each side

+     * @param maxFaultHeight Height modified on each side

+     * @param seed A seed to feed the Random generator

+     * @see setFaultRange, setMinRadius, setMaxRadius

+     */

+    public FaultHeightMap(int size, int iterations, int faultType, int faultShape, float minFaultHeight, float maxFaultHeight, long seed) throws Exception {

+        if (size < 0 || iterations < 0) {

+            throw new Exception("Size and iterations must be greater than 0!");

+        }

+        this.size = size;

+        this.iterations = iterations;

+        this.faultType = faultType;

+        this.faultShape = faultShape;

+        this.minFaultHeight = minFaultHeight;

+        this.maxFaultHeight = maxFaultHeight;

+        this.seed = seed;

+        this.minRange = minFaultHeight;

+        this.maxRange = maxFaultHeight;

+        this.minRadius = size / 10;

+        this.maxRadius = size / 4;

+        load();

+    }

+

+    /**

+     * Create an heightmap with linear step faults.

+     * @param size size of heightmap

+     * @param iterations number of iterations

+     * @param minFaultHeight Height modified on each side

+     * @param maxFaultHeight Height modified on each side

+     */

+    public FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight) throws Exception {

+        this(size, iterations, FAULTTYPE_STEP, FAULTSHAPE_LINE, minFaultHeight, maxFaultHeight, new Random().nextLong());

+    }

+

+    @Override

+    public boolean load() {

+        // clean up data if needed.

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+        heightData = new float[size * size];

+        float[][] tempBuffer = new float[size][size];

+        Random random = new Random(seed);

+

+        for (int i = 0; i < iterations; i++) {

+            addFault(tempBuffer, random);

+        }

+

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                setHeightAtPoint(tempBuffer[i][j], i, j);

+            }

+        }

+

+        normalizeTerrain(NORMALIZE_RANGE);

+

+        logger.log(Level.INFO, "Fault heightmap generated");

+        return true;

+    }

+

+    protected void addFault(float[][] tempBuffer, Random random) {

+        float faultHeight = minFaultHeight + random.nextFloat() * (maxFaultHeight - minFaultHeight);

+        float range = minRange + random.nextFloat() * (maxRange - minRange);

+        switch (faultShape) {

+            case FAULTSHAPE_LINE:

+                addLineFault(tempBuffer, random, faultHeight, range);

+                break;

+            case FAULTSHAPE_CIRCLE:

+                addCircleFault(tempBuffer, random, faultHeight, range);

+                break;

+        }

+    }

+

+    protected void addLineFault(float[][] tempBuffer, Random random, float faultHeight, float range) {

+        int x1 = random.nextInt(size);

+        int x2 = random.nextInt(size);

+        int y1 = random.nextInt(size);

+        int y2 = random.nextInt(size);

+

+

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                float dist = ((x2 - x1) * (j - y1) - (y2 - y1) * (i - x1))

+                        / (FastMath.sqrt(FastMath.sqr(x2 - x1) + FastMath.sqr(y2 - y1)));

+                tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range);

+            }

+        }

+    }

+

+    protected void addCircleFault(float[][] tempBuffer, Random random, float faultHeight, float range) {

+        float radius = random.nextFloat() * (maxRadius - minRadius) + minRadius;

+        int intRadius = (int) FastMath.floor(radius);

+        // Allox circle center to be out of map if not by more than radius.

+        // Unlucky cases will put them in the far corner, with the circle

+        // entirely outside heightmap

+        int x = random.nextInt(size + 2 * intRadius) - intRadius;

+        int y = random.nextInt(size + 2 * intRadius) - intRadius;

+

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                float dist;

+                if (i != x || j != y) {

+                    int dx = i - x;

+                    int dy = j - y;

+                    float dmag = FastMath.sqrt(FastMath.sqr(dx) + FastMath.sqr(dy));

+                    float rx = x + dx / dmag * radius;

+                    float ry = y + dy / dmag * radius;

+                    dist = FastMath.sign(dmag - radius)

+                        * FastMath.sqrt(FastMath.sqr(i - rx) + FastMath.sqr(j - ry));

+                } else {

+                    dist = 0;

+                }

+                tempBuffer[i][j] += calcHeight(dist, random, faultHeight, range);

+            }

+        }

+    }

+

+    protected float calcHeight(float dist, Random random, float faultHeight, float range) {

+        switch (faultType) {

+            case FAULTTYPE_STEP: {

+                return FastMath.sign(dist) * faultHeight;

+            }

+            case FAULTTYPE_LINEAR: {

+                if (FastMath.abs(dist) > range) {

+                    return FastMath.sign(dist) * faultHeight;

+                }

+                float f = FastMath.abs(dist) / range;

+                return FastMath.sign(dist) * faultHeight * f;

+            }

+            case FAULTTYPE_SINE: {

+                if (FastMath.abs(dist) > range) {

+                    return -faultHeight;

+                }

+                float f = dist / range;

+                // We want -1 at f=-1 and f=1; 1 at f=0

+                return FastMath.sin((1 + 2 * f) * FastMath.PI / 2) * faultHeight;

+            }

+            case FAULTTYPE_COSINE: {

+                if (FastMath.abs(dist) > range) {

+                    return -FastMath.sign(dist) * faultHeight;

+                }

+                float f = dist / range;

+                float val =  FastMath.cos((1 + f) * FastMath.PI / 2) * faultHeight;

+                return val;

+            }

+        }

+        //shoudn't go here

+        throw new RuntimeException("Code needs update to switch allcases");

+    }

+

+    public int getFaultShape() {

+        return faultShape;

+    }

+

+    public void setFaultShape(int faultShape) {

+        this.faultShape = faultShape;

+    }

+

+    public int getFaultType() {

+        return faultType;

+    }

+

+    public void setFaultType(int faultType) {

+        this.faultType = faultType;

+    }

+

+    public int getIterations() {

+        return iterations;

+    }

+

+    public void setIterations(int iterations) {

+        this.iterations = iterations;

+    }

+

+    public float getMaxFaultHeight() {

+        return maxFaultHeight;

+    }

+

+    public void setMaxFaultHeight(float maxFaultHeight) {

+        this.maxFaultHeight = maxFaultHeight;

+    }

+

+    public float getMaxRadius() {

+        return maxRadius;

+    }

+

+    public void setMaxRadius(float maxRadius) {

+        this.maxRadius = maxRadius;

+    }

+

+    public float getMaxRange() {

+        return maxRange;

+    }

+

+    public void setMaxRange(float maxRange) {

+        this.maxRange = maxRange;

+    }

+

+    public float getMinFaultHeight() {

+        return minFaultHeight;

+    }

+

+    public void setMinFaultHeight(float minFaultHeight) {

+        this.minFaultHeight = minFaultHeight;

+    }

+

+    public float getMinRadius() {

+        return minRadius;

+    }

+

+    public void setMinRadius(float minRadius) {

+        this.minRadius = minRadius;

+    }

+

+    public float getMinRange() {

+        return minRange;

+    }

+

+    public void setMinRange(float minRange) {

+        this.minRange = minRange;

+    }

+

+    public long getSeed() {

+        return seed;

+    }

+

+    public void setSeed(long seed) {

+        this.seed = seed;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/FluidSimHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/FluidSimHeightMap.java
new file mode 100644
index 0000000..ec2dfdf
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/FluidSimHeightMap.java
@@ -0,0 +1,308 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import java.util.Random;

+import java.util.logging.Logger;

+

+/**

+ * <code>FluidSimHeightMap</code> generates a height map based using some

+ * sort of fluid simulation. The heightmap is treated as a highly viscous and

+ * rubbery fluid enabling to fine tune the generated heightmap using a number

+ * of parameters.

+ *

+ * @author Frederik Boelthoff

+ * @see <a href="http://www.gamedev.net/reference/articles/article2001.asp">Terrain Generation Using Fluid Simulation</a>

+ * @version $Id$

+ *

+ */

+public class FluidSimHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(FluidSimHeightMap.class.getName());

+    private float waveSpeed = 100.0f;  // speed at which the waves travel

+    private float timeStep = 0.033f;  // constant time-step between each iteration

+    private float nodeDistance = 10.0f;   // distance between each node of the surface

+    private float viscosity = 100.0f; // viscosity of the fluid

+    private int iterations;    // number of iterations

+    private float minInitialHeight = -500; // min initial height

+    private float maxInitialHeight = 500; // max initial height

+    private long seed; // the seed for the random number generator

+    float coefA, coefB, coefC; // pre-computed coefficients in the fluid equation

+

+    /**

+     * Constructor sets the attributes of the hill system and generates the

+     * height map. It gets passed a number of tweakable parameters which

+     * fine-tune the outcome.

+     *

+     * @param size

+     *            size the size of the terrain to be generated

+     * @param iterations

+     *            the number of iterations to do

+     * @param minInitialHeight

+     *                        the minimum initial height of a terrain value

+     * @param maxInitialHeight

+     *                        the maximum initial height of a terrain value

+     * @param viscosity

+     *                        the viscosity of the fluid

+     * @param waveSpeed

+     *                        the speed at which the waves travel

+     * @param timestep

+     *                        the constant time-step between each iteration

+     * @param nodeDistance

+     *                        the distance between each node of the heightmap

+     * @param seed

+     *            the seed to generate the same heightmap again

+     * @throws JmeException

+     *             if size of the terrain is not greater that zero, or number of

+     *             iterations is not greater that zero, or the minimum initial height

+     *             is greater than the maximum (or the other way around)

+     */

+    public FluidSimHeightMap(int size, int iterations, float minInitialHeight, float maxInitialHeight, float viscosity, float waveSpeed, float timestep, float nodeDistance, long seed) throws Exception {

+        if (size <= 0 || iterations <= 0 || minInitialHeight >= maxInitialHeight) {

+            throw new Exception(

+                    "Either size of the terrain is not greater that zero, "

+                    + "or number of iterations is not greater that zero, "

+                    + "or minimum height greater or equal as the maximum, "

+                    + "or maximum height smaller or equal as the minimum.");

+        }

+

+        this.size = size;

+        this.seed = seed;

+        this.iterations = iterations;

+        this.minInitialHeight = minInitialHeight;

+        this.maxInitialHeight = maxInitialHeight;

+        this.viscosity = viscosity;

+        this.waveSpeed = waveSpeed;

+        this.timeStep = timestep;

+        this.nodeDistance = nodeDistance;

+

+        load();

+    }

+

+    /**

+     * Constructor sets the attributes of the hill system and generates the

+     * height map.

+     *

+     * @param size

+     *            size the size of the terrain to be generated

+     * @param iterations

+     *            the number of iterations to do

+     * @throws JmeException

+     *             if size of the terrain is not greater that zero, or number of

+     *             iterations is not greater that zero

+     */

+    public FluidSimHeightMap(int size, int iterations) throws Exception {

+        if (size <= 0 || iterations <= 0) {

+            throw new Exception(

+                    "Either size of the terrain is not greater that zero, "

+                    + "or number of iterations is not greater that zero");

+        }

+

+        this.size = size;

+        this.iterations = iterations;

+

+        load();

+    }

+

+

+    /*

+     * Generates a heightmap using fluid simulation and the attributes set by

+     * the constructor or the setters.

+     */

+    public boolean load() {

+        // Clean up data if needed.

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+

+        heightData = new float[size * size];

+        float[][] tempBuffer = new float[2][size * size];

+        Random random = new Random(seed);

+

+        // pre-compute the coefficients in the fluid equation

+        coefA = (4 - (8 * waveSpeed * waveSpeed * timeStep * timeStep) / (nodeDistance * nodeDistance)) / (viscosity * timeStep + 2);

+        coefB = (viscosity * timeStep - 2) / (viscosity * timeStep + 2);

+        coefC = ((2 * waveSpeed * waveSpeed * timeStep * timeStep) / (nodeDistance * nodeDistance)) / (viscosity * timeStep + 2);

+

+        // initialize the heightmaps to random values except for the edges

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                tempBuffer[0][j + i * size] = tempBuffer[1][j + i * size] = randomRange(random, minInitialHeight, maxInitialHeight);

+            }

+        }

+

+        int curBuf = 0;

+        int ind;

+

+        float[] oldBuffer;

+        float[] newBuffer;

+

+        // Iterate over the heightmap, applying the fluid simulation equation.

+        // Although it requires knowledge of the two previous timesteps, it only

+        // accesses one pixel of the k-1 timestep, so using a simple trick we only

+        // need to store the heightmap twice, not three times, and we can avoid

+        // copying data every iteration.

+        for (int i = 0; i < iterations; i++) {

+            oldBuffer = tempBuffer[1 - curBuf];

+            newBuffer = tempBuffer[curBuf];

+

+            for (int y = 0; y < size; y++) {

+                for (int x = 0; x < size; x++) {

+                    ind = x + y * size;

+                    float neighborsValue = 0;

+                    int neighbors = 0;

+

+                    if (x > 0) {

+                        neighborsValue += newBuffer[ind - 1];

+                        neighbors++;

+                    }

+                    if (x < size - 1) {

+                        neighborsValue += newBuffer[ind + 1];

+                        neighbors++;

+                    }

+                    if (y > 0) {

+                        neighborsValue += newBuffer[ind - size];

+                        neighbors++;

+                    }

+                    if (y < size - 1) {

+                        neighborsValue += newBuffer[ind + size];

+                        neighbors++;

+                    }

+                    if (neighbors != 4) {

+                        neighborsValue *= 4 / neighbors;

+                    }

+                    oldBuffer[ind] = coefA * newBuffer[ind] + coefB

+                            * oldBuffer[ind] + coefC * (neighborsValue);

+                }

+            }

+

+            curBuf = 1 - curBuf;

+        }

+

+        // put the normalized heightmap into the range [0...255] and into the heightmap

+        for (int y = 0; y < size; y++) {

+            for (int x = 0; x < size; x++) {

+                heightData[x + y * size] = (float) (tempBuffer[curBuf][x + y * size]);

+            }

+        }

+        normalizeTerrain(NORMALIZE_RANGE);

+

+        logger.info("Created Heightmap using fluid simulation");

+

+        return true;

+    }

+

+    private float randomRange(Random random, float min, float max) {

+        return (random.nextFloat() * (max - min)) + min;

+    }

+

+    /**

+     * Sets the number of times the fluid simulation should be iterated over

+     * the heightmap. The more often this is, the less features (hills, etc)

+     * the terrain will have, and the smoother it will be.

+     *

+     * @param iterations

+     *            the number of iterations to do

+     * @throws JmeException

+     *             if iterations if not greater than zero

+     */

+    public void setIterations(int iterations) throws Exception {

+        if (iterations <= 0) {

+            throw new Exception(

+                    "Number of iterations is not greater than zero");

+        }

+        this.iterations = iterations;

+    }

+

+    /**

+     * Sets the maximum initial height of the terrain.

+     *

+     * @param maxInitialHeight

+     *                        the maximum initial height

+     * @see #setMinInitialHeight(int)

+     */

+    public void setMaxInitialHeight(float maxInitialHeight) {

+        this.maxInitialHeight = maxInitialHeight;

+    }

+

+    /**

+     * Sets the minimum initial height of the terrain.

+     *

+     * @param minInitialHeight

+     *                        the minimum initial height

+     * @see #setMaxInitialHeight(int)

+     */

+    public void setMinInitialHeight(float minInitialHeight) {

+        this.minInitialHeight = minInitialHeight;

+    }

+

+    /**

+     * Sets the distance between each node of the heightmap.

+     *

+     * @param nodeDistance

+     *                       the distance between each node

+     */

+    public void setNodeDistance(float nodeDistance) {

+        this.nodeDistance = nodeDistance;

+    }

+

+    /**

+     * Sets the time-speed between each iteration of the fluid

+     * simulation algortithm.

+     *

+     * @param timeStep

+     *                       the time-step between each iteration

+     */

+    public void setTimeStep(float timeStep) {

+        this.timeStep = timeStep;

+    }

+

+    /**

+     * Sets the viscosity of the simulated fuid.

+     *

+     * @param viscosity

+     *                      the viscosity of the fluid

+     */

+    public void setViscosity(float viscosity) {

+        this.viscosity = viscosity;

+    }

+

+    /**

+     * Sets the speed at which the waves trave.

+     *

+     * @param waveSpeed

+     *                      the speed at which the waves travel

+     */

+    public void setWaveSpeed(float waveSpeed) {

+        this.waveSpeed = waveSpeed;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/HeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/HeightMap.java
new file mode 100644
index 0000000..1bbed36
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/HeightMap.java
@@ -0,0 +1,158 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+/**

+ *

+ * @author cghislai

+ */

+public interface HeightMap {

+

+    /**

+     * <code>getHeightMap</code> returns the entire grid of height data.

+     *

+     * @return the grid of height data.

+     */

+    float[] getHeightMap();

+

+    float[] getScaledHeightMap();

+

+    /**

+     * <code>getInterpolatedHeight</code> returns the height of a point that

+     * does not fall directly on the height posts.

+     *

+     * @param x

+     * the x coordinate of the point.

+     * @param z

+     * the y coordinate of the point.

+     * @return the interpolated height at this point.

+     */

+    float getInterpolatedHeight(float x, float z);

+

+    /**

+     * <code>getScaledHeightAtPoint</code> returns the scaled value at the

+     * point provided.

+     *

+     * @param x

+     * the x (east/west) coordinate.

+     * @param z

+     * the z (north/south) coordinate.

+     * @return the scaled value at (x, z).

+     */

+    float getScaledHeightAtPoint(int x, int z);

+

+    /**

+     * <code>getSize</code> returns the size of one side the height map. Where

+     * the area of the height map is size x size.

+     *

+     * @return the size of a single side.

+     */

+    int getSize();

+

+    /**

+     * <code>getTrueHeightAtPoint</code> returns the non-scaled value at the

+     * point provided.

+     *

+     * @param x

+     * the x (east/west) coordinate.

+     * @param z

+     * the z (north/south) coordinate.

+     * @return the value at (x,z).

+     */

+    float getTrueHeightAtPoint(int x, int z);

+

+    /**

+     * <code>load</code> populates the height map data. This is dependent on

+     * the subclass's implementation.

+     *

+     * @return true if the load was successful, false otherwise.

+     */

+    boolean load();

+

+    /**

+     * <code>setHeightAtPoint</code> sets the height value for a given

+     * coordinate. It is recommended that the height value be within the 0 - 255

+     * range.

+     *

+     * @param height

+     * the new height for the coordinate.

+     * @param x

+     * the x (east/west) coordinate.

+     * @param z

+     * the z (north/south) coordinate.

+     */

+    void setHeightAtPoint(float height, int x, int z);

+

+    /**

+     * <code>setHeightScale</code> sets the scale of the height values.

+     * Typically, the height is a little too extreme and should be scaled to a

+     * smaller value (i.e. 0.25), to produce cleaner slopes.

+     *

+     * @param scale

+     * the scale to multiply height values by.

+     */

+    void setHeightScale(float scale);

+

+    /**

+     * <code>setFilter</code> sets the erosion value for the filter. This

+     * value must be between 0 and 1, where 0.2 - 0.4 produces arguably the best

+     * results.

+     *

+     * @param filter

+     * the erosion value.

+     * @throws Exception

+     * @throws JmeException

+     * if filter is less than 0 or greater than 1.

+     */

+    void setMagnificationFilter(float filter) throws Exception;

+

+    /**

+     * <code>setSize</code> sets the size of the terrain where the area is

+     * size x size.

+     *

+     * @param size

+     * the new size of the terrain.

+     * @throws Exception

+     *

+     * @throws JmeException

+     * if the size is less than or equal to zero.

+     */

+    void setSize(int size) throws Exception;

+

+    /**

+     * <code>unloadHeightMap</code> clears the data of the height map. This

+     * insures it is ready for reloading.

+     */

+    void unloadHeightMap();

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/HeightMapGrid.java b/engine/src/terrain/com/jme3/terrain/heightmap/HeightMapGrid.java
new file mode 100644
index 0000000..8d59429
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/HeightMapGrid.java
@@ -0,0 +1,23 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.heightmap;
+
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author Anthyon
+ */
+@Deprecated
+/**
+ * @Deprecated in favor of TerrainGridTileLoader
+ */
+public interface HeightMapGrid {
+
+    public HeightMap getHeightMapAt(Vector3f location);
+
+    public void setSize(int size);
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.java
new file mode 100644
index 0000000..038dcb2
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/HillHeightMap.java
@@ -0,0 +1,261 @@
+/*

+ * Copyright (c) 2009-2012 jMonkeyEngine

+ * All rights reserved.

+ *

+ * Redistribution and use in source and binary forms, with or without

+ * modification, are permitted provided that the following conditions are

+ * met:

+ *

+ * * Redistributions of source code must retain the above copyright

+ *   notice, this list of conditions and the following disclaimer.

+ *

+ * * Redistributions in binary form must reproduce the above copyright

+ *   notice, this list of conditions and the following disclaimer in the

+ *   documentation and/or other materials provided with the distribution.

+ *

+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors

+ *   may be used to endorse or promote products derived from this software

+ *   without specific prior written permission.

+ *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED

+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,

+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR

+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF

+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS

+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ */

+package com.jme3.terrain.heightmap;

+

+import java.util.Random;

+import java.util.logging.Logger;

+

+/**

+ * <code>HillHeightMap</code> generates a height map base on the Hill

+ * Algorithm. Terrain is generatd by growing hills of random size and height at

+ * random points in the heightmap. The terrain is then normalized and valleys

+ * can be flattened.

+ * 

+ * @author Frederik Blthoff

+ * @see <a href="http://www.robot-frog.com/3d/hills/hill.html">Hill Algorithm</a>

+ */

+public class HillHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(HillHeightMap.class.getName());

+    private int iterations; // how many hills to generate

+    private float minRadius; // the minimum size of a hill radius

+    private float maxRadius; // the maximum size of a hill radius

+    private long seed; // the seed for the random number generator

+

+    /**

+     * Constructor sets the attributes of the hill system and generates the

+     * height map.

+     *

+     * @param size

+     *            size the size of the terrain to be generated

+     * @param iterations

+     *            the number of hills to grow

+     * @param minRadius

+     *            the minimum radius of a hill

+     * @param maxRadius

+     *            the maximum radius of a hill

+     * @param seed

+     *            the seed to generate the same heightmap again

+     * @throws Exception

+     * @throws JmeException

+     *             if size of the terrain is not greater that zero, or number of

+     *             iterations is not greater that zero

+     */

+    public HillHeightMap(int size, int iterations, float minRadius,

+            float maxRadius, long seed) throws Exception {

+        if (size <= 0 || iterations <= 0 || minRadius <= 0 || maxRadius <= 0

+                || minRadius >= maxRadius) {

+            throw new Exception(

+                    "Either size of the terrain is not greater that zero, "

+                    + "or number of iterations is not greater that zero, "

+                    + "or minimum or maximum radius are not greater than zero, "

+                    + "or minimum radius is greater than maximum radius, "

+                    + "or power of flattening is below one");

+        }

+        logger.info("Contructing hill heightmap using seed: " + seed);

+        this.size = size;

+        this.seed = seed;

+        this.iterations = iterations;

+        this.minRadius = minRadius;

+        this.maxRadius = maxRadius;

+

+        load();

+    }

+

+    /**

+     * Constructor sets the attributes of the hill system and generates the

+     * height map by using a random seed.

+     *

+     * @param size

+     *            size the size of the terrain to be generated

+     * @param iterations

+     *            the number of hills to grow

+     * @param minRadius

+     *            the minimum radius of a hill

+     * @param maxRadius

+     *            the maximum radius of a hill

+     * @throws Exception

+     * @throws JmeException

+     *             if size of the terrain is not greater that zero, or number of

+     *             iterations is not greater that zero

+     */

+    public HillHeightMap(int size, int iterations, float minRadius,

+            float maxRadius) throws Exception {

+        this(size, iterations, minRadius, maxRadius, new Random().nextLong());

+    }

+

+    /*

+     * Generates a heightmap using the Hill Algorithm and the attributes set by

+     * the constructor or the setters.

+     */

+    public boolean load() {

+        // clean up data if needed.

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+        heightData = new float[size * size];

+        float[][] tempBuffer = new float[size][size];

+        Random random = new Random(seed);

+

+        // Add the hills

+        for (int i = 0; i < iterations; i++) {

+            addHill(tempBuffer, random);

+        }

+

+        // transfer temporary buffer to final heightmap

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                setHeightAtPoint((float) tempBuffer[i][j], j, i);

+            }

+        }

+

+        normalizeTerrain(NORMALIZE_RANGE);

+

+        logger.info("Created Heightmap using the Hill Algorithm");

+

+        return true;

+    }

+

+    /**

+     * Generates a new hill of random size and height at a random position in

+     * the heightmap. This is the actual Hill algorithm. The <code>Random</code>

+     * object is used to guarantee the same heightmap for the same seed and

+     * attributes.

+     *

+     * @param tempBuffer

+     *            the temporary height map buffer

+     * @param random

+     *            the random number generator

+     */

+    protected void addHill(float[][] tempBuffer, Random random) {

+        // Pick the radius for the hill

+        float radius = randomRange(random, minRadius, maxRadius);

+

+        // Pick a centerpoint for the hill

+        float x = randomRange(random, -radius, size + radius);

+        float y = randomRange(random, -radius, size + radius);

+

+        float radiusSq = radius * radius;

+        float distSq;

+        float height;

+

+        // Find the range of hills affected by this hill

+        int xMin = Math.round(x - radius - 1);

+        int xMax = Math.round(x + radius + 1);

+

+        int yMin = Math.round(y - radius - 1);

+        int yMax = Math.round(y + radius + 1);

+

+        // Don't try to affect points outside the heightmap

+        if (xMin < 0) {

+            xMin = 0;

+        }

+        if (xMax > size) {

+            xMax = size - 1;

+        }

+

+        if (yMin < 0) {

+            yMin = 0;

+        }

+        if (yMax > size) {

+            yMax = size - 1;

+        }

+

+        for (int i = xMin; i <= xMax; i++) {

+            for (int j = yMin; j <= yMax; j++) {

+                distSq = (x - i) * (x - i) + (y - j) * (y - j);

+                height = radiusSq - distSq;

+

+                if (height > 0) {

+                    tempBuffer[i][j] += height;

+                }

+            }

+        }

+    }

+

+    private float randomRange(Random random, float min, float max) {

+        return (random.nextInt() * (max - min) / Integer.MAX_VALUE) + min;

+    }

+

+    /**

+     * Sets the number of hills to grow. More hills usually mean a nicer

+     * heightmap.

+     *

+     * @param iterations

+     *            the number of hills to grow

+     * @throws Exception

+     * @throws JmeException

+     *             if iterations if not greater than zero

+     */

+    public void setIterations(int iterations) throws Exception {

+        if (iterations <= 0) {

+            throw new Exception(

+                    "Number of iterations is not greater than zero");

+        }

+        this.iterations = iterations;

+    }

+

+    /**

+     * Sets the minimum radius of a hill.

+     *

+     * @param maxRadius

+     *            the maximum radius of a hill

+     * @throws Exception

+     * @throws JmeException

+     *             if the maximum radius if not greater than zero or not greater

+     *             than the minimum radius

+     */

+    public void setMaxRadius(float maxRadius) throws Exception {

+        if (maxRadius <= 0 || maxRadius <= minRadius) {

+            throw new Exception("The maximum radius is not greater than 0, "

+                    + "or not greater than the minimum radius");

+        }

+        this.maxRadius = maxRadius;

+    }

+

+    /**

+     * Sets the maximum radius of a hill.

+     *

+     * @param minRadius

+     *            the minimum radius of a hill

+     * @throws Exception

+     * @throws JmeException if the minimum radius is not greater than zero or not

+     *        lower than the maximum radius

+     */

+    public void setMinRadius(float minRadius) throws Exception {

+        if (minRadius <= 0 || minRadius >= maxRadius) {

+            throw new Exception("The minimum radius is not greater than 0, "

+                    + "or not lower than the maximum radius");

+        }

+        this.minRadius = minRadius;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMap.java
new file mode 100644
index 0000000..84eb857
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMap.java
@@ -0,0 +1,177 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import java.nio.ByteBuffer;

+import com.jme3.math.ColorRGBA;

+import com.jme3.texture.Image;

+import java.nio.ShortBuffer;

+

+/**

+ * <code>ImageBasedHeightMap</code> is a height map created from the grayscale

+ * conversion of an image. The image used currently must have an equal height

+ * and width, although future work could scale an incoming image to a specific

+ * height and width.

+ * 

+ * @author Mike Kienenberger

+ * @version $id$

+ */

+public class ImageBasedHeightMap extends AbstractHeightMap implements ImageHeightmap {

+    

+    

+    protected Image colorImage;

+

+    

+    public void setImage(Image image) {

+        this.colorImage = image;

+    }

+    

+    /**

+     * Creates a HeightMap from an Image. The image will be converted to

+     * grayscale, and the grayscale values will be used to generate the height

+     * map. White is highest point while black is lowest point.

+     * 

+     * Currently, the Image used must be square (width == height), but future

+     * work could rescale the image.

+     * 

+     * @param colorImage

+     *            Image to map to the height map.

+     */

+    public ImageBasedHeightMap(Image colorImage) {

+        this.colorImage = colorImage;

+    }

+    

+    public ImageBasedHeightMap(Image colorImage, float heightScale) {

+    	this.colorImage = colorImage;

+        this.heightScale = heightScale;

+    }

+

+    /**

+     * Loads the image data from top left to bottom right

+     */

+    public boolean load() {

+        return load(false, false);

+    }

+

+    /**

+     * Get the grayscale value, or override in your own sub-classes

+     */

+    protected float calculateHeight(float red, float green, float blue) {

+        return (float) (0.299 * red + 0.587 * green + 0.114 * blue);

+    }

+    

+    public boolean load(boolean flipX, boolean flipY) {

+

+        int imageWidth = colorImage.getWidth();

+        int imageHeight = colorImage.getHeight();

+

+        if (imageWidth != imageHeight)

+                throw new RuntimeException("imageWidth: " + imageWidth

+                        + " != imageHeight: " + imageHeight);

+

+        size = imageWidth;

+

+        ByteBuffer buf = colorImage.getData(0);

+

+        heightData = new float[(imageWidth * imageHeight)];

+

+        ColorRGBA colorStore = new ColorRGBA();

+        

+        int index = 0;

+        if (flipY) {

+            for (int h = 0; h < imageHeight; ++h) {

+                if (flipX) {

+                    for (int w = imageWidth - 1; w >= 0; --w) {

+                        int baseIndex = (h * imageWidth)+ w;

+                        heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;

+                    }

+                } else {

+                    for (int w = 0; w < imageWidth; ++w) {

+                        int baseIndex = (h * imageWidth)+ w;

+                        heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;

+                    }

+                }

+            }

+        } else {

+            for (int h = imageHeight - 1; h >= 0; --h) {

+                if (flipX) {

+                    for (int w = imageWidth - 1; w >= 0; --w) {

+                        int baseIndex = (h * imageWidth)+ w;

+                        heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;

+                    }

+                } else {

+                    for (int w = 0; w < imageWidth; ++w) {

+                        int baseIndex = (h * imageWidth)+ w;

+                        heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;

+                    }

+                }

+            }

+        }

+

+        return true;

+    }

+    

+    protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store) {

+        switch (image.getFormat()){

+            case RGBA8:

+                buf.position( position * 4 );

+                store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()));

+                return calculateHeight(store.r, store.g, store.b);

+            case ABGR8:

+                buf.position( position * 4 );

+                float a = byte2float(buf.get());

+                float b = byte2float(buf.get());

+                float g = byte2float(buf.get());

+                float r = byte2float(buf.get());

+                store.set(r,g,b,a);

+                return calculateHeight(store.r, store.g, store.b);

+            case RGB8:

+                buf.position( position * 3 );

+                store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), 1);

+                return calculateHeight(store.r, store.g, store.b);

+            case Luminance8:

+                buf.position( position );

+                return byte2float(buf.get())*255*heightScale;

+            case Luminance16:

+                ShortBuffer sbuf = buf.asShortBuffer();

+                sbuf.position( position );

+                return (sbuf.get() & 0xFFFF) / 65535f * 255f * heightScale;

+            default:

+                throw new UnsupportedOperationException("Image format: "+image.getFormat());

+        }

+    }

+    

+    private float byte2float(byte b){

+        return ((float)(b & 0xFF)) / 255f;

+    }

+}
\ No newline at end of file
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java b/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java
new file mode 100644
index 0000000..1ba5f3e
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java
@@ -0,0 +1,79 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.heightmap;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.Texture;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Loads Terrain grid tiles with image heightmaps.
+ * By default it expects a 16-bit grayscale image as the heightmap, but
+ * you can also call setImageType(BufferedImage.TYPE_) to set it to be a different
+ * image type. If you do this, you must also set a custom ImageHeightmap that will
+ * understand and be able to parse the image. By default if you pass in an image of type
+ * BufferedImage.TYPE_3BYTE_BGR, it will use the ImageBasedHeightMap for you.
+ * 
+ * @author Anthyon, Brent Owens
+ */
+@Deprecated
+/**
+ * @Deprecated in favor of ImageTileLoader
+ */
+public class ImageBasedHeightMapGrid implements HeightMapGrid {
+
+    private static final Logger logger = Logger.getLogger(ImageBasedHeightMapGrid.class.getName());
+    private final AssetManager assetManager;
+    private final Namer namer;
+    private int size;
+    
+
+    public ImageBasedHeightMapGrid(final String textureBase, final String textureExt, AssetManager assetManager) {
+        this(assetManager, new Namer() {
+
+            public String getName(int x, int y) {
+                return textureBase + "_" + x + "_" + y + "." + textureExt;
+            }
+        });
+    }
+
+    public ImageBasedHeightMapGrid(AssetManager assetManager, Namer namer) {
+        this.assetManager = assetManager;
+        this.namer = namer;
+    }
+
+    public HeightMap getHeightMapAt(Vector3f location) {
+        // HEIGHTMAP image (for the terrain heightmap)
+        int x = (int) location.x;
+        int z = (int) location.z;
+        
+        AbstractHeightMap heightmap = null;
+        //BufferedImage im = null;
+        
+        try {
+            String name = namer.getName(x, z);
+            logger.log(Level.INFO, "Loading heightmap from file: {0}", name);
+            final Texture texture = assetManager.loadTexture(new TextureKey(name));
+            
+            // CREATE HEIGHTMAP
+            heightmap = new ImageBasedHeightMap(texture.getImage());
+            
+            heightmap.setHeightScale(1);
+            heightmap.load();
+        
+        } catch (AssetNotFoundException e) {
+            logger.log(Level.SEVERE, "Asset Not found! ", e);
+        }
+        return heightmap;
+    }
+
+    public void setSize(int size) {
+        this.size = size - 1;
+    }
+}
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/ImageHeightmap.java b/engine/src/terrain/com/jme3/terrain/heightmap/ImageHeightmap.java
new file mode 100644
index 0000000..76a222b
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/ImageHeightmap.java
@@ -0,0 +1,30 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.heightmap;
+
+import com.jme3.texture.Image;
+
+/**
+ * A heightmap that is built off an image.
+ * If you want to be able to supply different Image types to 
+ * ImageBaseHeightMapGrid, you need to implement this interface,
+ * and have that class extend Abstract heightmap.
+ * 
+ * @author bowens
+ * @deprecated
+ */
+public interface ImageHeightmap {
+    
+    /**
+     * Set the image to use for this heightmap
+     */
+    //public void setImage(Image image);
+    
+    /**
+     * The BufferedImage.TYPE_ that is supported
+     * by this ImageHeightmap
+     */
+    //public int getSupportedImageType();
+}
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java
new file mode 100644
index 0000000..acb2b12
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java
@@ -0,0 +1,273 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import com.jme3.math.FastMath;

+import java.util.Random;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import javax.management.JMException;

+

+/**

+ * <code>MidpointDisplacementHeightMap</code> generates an heightmap based on

+ * the midpoint displacement algorithm. See Constructor javadoc for more info.

+ * @author cghislai

+ */

+public class MidpointDisplacementHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(MidpointDisplacementHeightMap.class.getName());

+    private float range; // The offset in which randomness will be added

+    private float persistence; // How the random offset evolves with increasing passes

+    private long seed; // seed for random number generator

+

+    /**

+     * The constructor generates the heightmap. After the first 4 corners are

+     * randomly given an height, the center will be heighted to the average of

+     * the 4 corners to which a random value is added. Then other passes fill

+     * the heightmap by the same principle.

+     * The random value is generated between the values <code>-range</code>

+     * and <code>range</code>. The <code>range</code> parameter is multiplied by

+     * the <code>persistence</code> parameter each pass to smoothen close cell heights.

+     * Extends this class and override the getOffset function for more control of

+     * the randomness (you can use the coordinates and/or the computed average height

+     * to influence the random amount added.

+     *

+     * @param size

+     *          The size of the heightmap, must be 2^N+1

+     * @param range

+     *          The range in which randomness will be added. A value of 1 will

+     *          allow -1 to 1 value changes.

+     * @param persistence

+     *          The factor by which the range will evolve at each iteration.

+     *          A value of 0.5f will halve the range at each iteration and is

+     *          typically a good choice

+     * @param seed

+     *          A seed to feed the random number generator.

+     * @throw JMException if size is not a power of two plus one.

+     */

+    public MidpointDisplacementHeightMap(int size, float range, float persistence, long seed) throws Exception {

+        if (size < 0 || !FastMath.isPowerOfTwo(size - 1)) {

+            throw new JMException("The size is negative or not of the form 2^N +1"

+                    + " (a power of two plus one)");

+        }

+        this.size = size;

+        this.range = range;

+        this.persistence = persistence;

+        this.seed = seed;

+        load();

+    }

+

+    /**

+     * The constructor generates the heightmap. After the first 4 corners are

+     * randomly given an height, the center will be heighted to the average of

+     * the 4 corners to which a random value is added. Then other passes fill

+     * the heightmap by the same principle.

+     * The random value is generated between the values <code>-range</code>

+     * and <code>range</code>. The <code>range</code> parameter is multiplied by

+     * the <code>persistence</code> parameter each pass to smoothen close cell heights.

+     * @param size

+     *          The size of the heightmap, must be 2^N+1

+     * @param range

+     *          The range in which randomness will be added. A value of 1 will

+     *          allow -1 to 1 value changes. 

+     * @param persistence

+     *          The factor by which the range will evolve at each iteration.

+     *          A value of 0.5f will halve the range at each iteration and is

+     *          typically a good choice

+     * @throw JMException if size is not a power of two plus one.

+     */

+    public MidpointDisplacementHeightMap(int size, float range, float persistence) throws Exception {

+        this(size, range, persistence, new Random().nextLong());

+    }

+

+    /**

+     * Generate the heightmap.

+     * @return

+     */

+    @Override

+    public boolean load() {

+        // clean up data if needed.

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+        heightData = new float[size * size];

+        float[][] tempBuffer = new float[size][size];

+        Random random = new Random(seed);

+

+        tempBuffer[0][0] = random.nextFloat();

+        tempBuffer[0][size - 1] = random.nextFloat();

+        tempBuffer[size - 1][0] = random.nextFloat();

+        tempBuffer[size - 1][size - 1] = random.nextFloat();

+

+        float offsetRange = range;

+        int stepSize = size - 1;

+        while (stepSize > 1) {

+            int[] nextCoords = {0, 0};

+            while (nextCoords != null) {

+                nextCoords = doSquareStep(tempBuffer, nextCoords, stepSize, offsetRange, random);

+            }

+            nextCoords = new int[]{0, 0};

+            while (nextCoords != null) {

+                nextCoords = doDiamondStep(tempBuffer, nextCoords, stepSize, offsetRange, random);

+            }

+            stepSize /= 2;

+            offsetRange *= persistence;

+        }

+

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                setHeightAtPoint((float) tempBuffer[i][j], j, i);

+            }

+        }

+

+        normalizeTerrain(NORMALIZE_RANGE);

+

+        logger.log(Level.INFO, "Midpoint displacement heightmap generated");

+        return true;

+    }

+

+    /**

+     * Will fill the value at (coords[0]+stepSize/2, coords[1]+stepSize/2) with

+     * the average from the corners of the square with topleft corner at (coords[0],coords[1])

+     * and width of stepSize.

+     * @param tempBuffer the temprary heightmap

+     * @param coords an int array of lenght 2 with the x coord in position 0

+     * @param stepSize the size of the square

+     * @param offsetRange the offset range within a random value is picked and added to the average

+     * @param random the random generator

+     * @return

+     */

+    protected int[] doSquareStep(float[][] tempBuffer, int[] coords, int stepSize, float offsetRange, Random random) {

+        float cornerAverage = 0;

+        int x = coords[0];

+        int y = coords[1];

+        cornerAverage += tempBuffer[x][y];

+        cornerAverage += tempBuffer[x + stepSize][y];

+        cornerAverage += tempBuffer[x + stepSize][y + stepSize];

+        cornerAverage += tempBuffer[x][y + stepSize];

+        cornerAverage /= 4;

+        float offset = getOffset(random, offsetRange, coords, cornerAverage);

+        tempBuffer[x + stepSize / 2][y + stepSize / 2] = cornerAverage + offset;

+

+        // Only get to next square if the center is still in map

+        if (x + stepSize * 3 / 2 < size) {

+            return new int[]{x + stepSize, y};

+        }

+        if (y + stepSize * 3 / 2 < size) {

+            return new int[]{0, y + stepSize};

+        }

+        return null;

+    }

+

+    /**

+     * Will fill the cell at (x+stepSize/2, y) with the average of the 4 corners

+     * of the diamond centered on that point with width and height of stepSize.

+     * @param tempBuffer

+     * @param coords

+     * @param stepSize

+     * @param offsetRange

+     * @param random

+     * @return

+     */

+    protected int[] doDiamondStep(float[][] tempBuffer, int[] coords, int stepSize, float offsetRange, Random random) {

+        int cornerNbr = 0;

+        float cornerAverage = 0;

+        int x = coords[0];

+        int y = coords[1];

+        int[] dxs = new int[]{0, stepSize / 2, stepSize, stepSize / 2};

+        int[] dys = new int[]{0, -stepSize / 2, 0, stepSize / 2};

+

+        for (int d = 0; d < 4; d++) {

+            int i = x + dxs[d];

+            if (i < 0 || i > size - 1) {

+                continue;

+            }

+            int j = y + dys[d];

+            if (j < 0 || j > size - 1) {

+                continue;

+            }

+            cornerAverage += tempBuffer[i][j];

+            cornerNbr++;

+        }

+        cornerAverage /= cornerNbr;

+        float offset = getOffset(random, offsetRange, coords, cornerAverage);

+        tempBuffer[x + stepSize / 2][y] = cornerAverage + offset;

+

+        if (x + stepSize * 3 / 2 < size) {

+            return new int[]{x + stepSize, y};

+        }

+        if (y + stepSize / 2 < size) {

+            if (x + stepSize == size - 1) {

+                return new int[]{-stepSize / 2, y + stepSize / 2};

+            } else {

+                return new int[]{0, y + stepSize / 2};

+            }

+        }

+        return null;

+    }

+

+    /**

+     * Generate a random value to add  to the computed average

+     * @param random the random generator

+     * @param offsetRange

+     * @param coords

+     * @param average

+     * @return A semi-random value within offsetRange

+     */

+    protected float getOffset(Random random, float offsetRange, int[] coords, float average) {

+        return 2 * (random.nextFloat() - 0.5F) * offsetRange;

+    }

+

+    public float getPersistence() {

+        return persistence;

+    }

+

+    public void setPersistence(float persistence) {

+        this.persistence = persistence;

+    }

+

+    public float getRange() {

+        return range;

+    }

+

+    public void setRange(float range) {

+        this.range = range;

+    }

+

+    public long getSeed() {

+        return seed;

+    }

+

+    public void setSeed(long seed) {

+        this.seed = seed;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/Namer.java b/engine/src/terrain/com/jme3/terrain/heightmap/Namer.java
new file mode 100644
index 0000000..73171ae
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/Namer.java
@@ -0,0 +1,21 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.terrain.heightmap;
+
+/**
+ *
+ * @author Anthyon
+ */
+public interface Namer {
+
+    /**
+     * Gets a name for a heightmap tile given it's cell id
+     * @param x
+     * @param y
+     * @return
+     */
+    public String getName(int x, int y);
+
+}
diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java
new file mode 100644
index 0000000..63dccc6
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java
@@ -0,0 +1,397 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import java.util.logging.Logger;

+

+/**

+ * <code>ParticleDepositionHeightMap</code> creates a heightmap based on the

+ * Particle Deposition algorithm based on Jason Shankel's paper from

+ * "Game Programming Gems". A heightmap is created using a Molecular beam

+ * epitaxy, or MBE, for depositing thin layers of atoms on a substrate.

+ * We drop a sequence of particles and simulate their flow across a surface

+ * of previously dropped particles. This creates a few high peaks, for further

+ * realism we can define a caldera. Similar to the way volcano's form

+ * islands, rock is deposited via lava, when the lava cools, it recedes

+ * into the volcano, creating the caldera.

+ *

+ * @author Mark Powell

+ * @version $Id$

+ */

+public class ParticleDepositionHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(ParticleDepositionHeightMap.class.getName());

+    //Attributes.

+    private int jumps;

+    private int peakWalk;

+    private int minParticles;

+    private int maxParticles;

+    private float caldera;

+

+    /**

+     * Constructor sets the attributes of the Particle Deposition

+     * Height Map and then generates the map.

+     *

+     * @param size the size of the terrain where the area is size x size.

+     * @param jumps number of areas to drop particles. Can also think

+     *              of it as the number of peaks.

+     * @param peakWalk determines how much to agitate the drop point

+     *              during a creation of a single peak. The lower the number

+     *              the more the drop point will be agitated. 1 will insure

+     *              agitation every round.

+     * @param minParticles defines the minimum number of particles to

+     *              drop during a single jump.

+     * @param maxParticles defines the maximum number of particles to

+     *              drop during a single jump.

+     * @param caldera defines the altitude to invert a peak. This is

+     *              represented as a percentage, where 0.0 will not invert

+     *              anything, and 1.0 will invert all.

+     *

+     * @throws JmeException if any value is less than zero, and

+     *              if caldera is not between 0 and 1. If minParticles is greater than

+     *              max particles as well.

+     */

+    public ParticleDepositionHeightMap(

+            int size,

+            int jumps,

+            int peakWalk,

+            int minParticles,

+            int maxParticles,

+            float caldera) throws Exception {

+

+

+        if (size <= 0

+                || jumps < 0

+                || peakWalk < 0

+                || minParticles > maxParticles

+                || minParticles < 0

+                || maxParticles < 0) {

+

+

+            throw new Exception(

+                    "values must be greater than zero, "

+                    + "and minParticles must be greater than maxParticles");

+        }

+        if (caldera < 0.0f || caldera > 1.0f) {

+            throw new Exception(

+                    "Caldera level must be " + "between 0 and 1");

+        }

+

+

+        this.size = size;

+        this.jumps = jumps;

+        this.peakWalk = peakWalk;

+        this.minParticles = minParticles;

+        this.maxParticles = maxParticles;

+        this.caldera = caldera;

+

+

+        load();

+    }

+

+    /**

+     * <code>load</code> generates the heightfield using the Particle Deposition

+     * algorithm. <code>load</code> uses the latest attributes, so a call

+     * to <code>load</code> is recommended if attributes have changed using

+     * the set methods.

+     */

+    public boolean load() {

+        int x, y;

+        int calderaX, calderaY;

+        int sx, sy;

+        int tx, ty;

+        int m;

+        float calderaStartPoint;

+        float cutoff;

+        int dx[] = {0, 1, 0, size - 1, 1, 1, size - 1, size - 1};

+        int dy[] = {1, 0, size - 1, 0, size - 1, 1, size - 1, 1};

+        float[][] tempBuffer = new float[size][size];

+        //map 0 unmarked, unvisited, 1 marked, unvisited, 2 marked visited.

+        int[][] calderaMap = new int[size][size];

+        boolean done;

+

+

+        int minx, maxx;

+        int miny, maxy;

+

+

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+

+

+        heightData = new float[size * size];

+

+

+        //create peaks.

+        for (int i = 0; i < jumps; i++) {

+

+

+            //pick a random point.

+            x = (int) (Math.rint(Math.random() * (size - 1)));

+            y = (int) (Math.rint(Math.random() * (size - 1)));

+

+

+            //set the caldera point.

+            calderaX = x;

+            calderaY = y;

+

+

+            int numberParticles =

+                    (int) (Math.rint(

+                    (Math.random() * (maxParticles - minParticles))

+                    + minParticles));

+            //drop particles.

+            for (int j = 0; j < numberParticles; j++) {

+                //check to see if we should aggitate the drop point.

+                if (peakWalk != 0 && j % peakWalk == 0) {

+                    m = (int) (Math.rint(Math.random() * 7));

+                    x = (x + dx[m] + size) % size;

+                    y = (y + dy[m] + size) % size;

+                }

+

+

+                //add the particle to the piont.

+                tempBuffer[x][y] += 1;

+

+

+                sx = x;

+                sy = y;

+                done = false;

+

+

+                //cause the particle to "slide" down the slope and settle at

+                //a low point.

+                while (!done) {

+                    done = true;

+

+

+                    //check neighbors to see if we are higher.

+                    m = (int) (Math.rint((Math.random() * 8)));

+                    for (int jj = 0; jj < 8; jj++) {

+                        tx = (sx + dx[(jj + m) % 8]) % (size);

+                        ty = (sy + dy[(jj + m) % 8]) % (size);

+

+

+                        //move to the neighbor.

+                        if (tempBuffer[tx][ty] + 1.0f < tempBuffer[sx][sy]) {

+                            tempBuffer[tx][ty] += 1.0f;

+                            tempBuffer[sx][sy] -= 1.0f;

+                            sx = tx;

+                            sy = ty;

+                            done = false;

+                            break;

+                        }

+                    }

+                }

+

+

+                //This point is higher than the current caldera point,

+                //so move the caldera here.

+                if (tempBuffer[sx][sy] > tempBuffer[calderaX][calderaY]) {

+                    calderaX = sx;

+                    calderaY = sy;

+                }

+            }

+

+

+            //apply the caldera.

+            calderaStartPoint = tempBuffer[calderaX][calderaY];

+            cutoff = calderaStartPoint * (1.0f - caldera);

+            minx = calderaX;

+            maxx = calderaX;

+            miny = calderaY;

+            maxy = calderaY;

+

+

+            calderaMap[calderaX][calderaY] = 1;

+

+

+            done = false;

+            while (!done) {

+                done = true;

+                sx = minx;

+                sy = miny;

+                tx = maxx;

+                ty = maxy;

+

+

+                for (x = sx; x <= tx; x++) {

+                    for (y = sy; y <= ty; y++) {

+

+

+                        calderaX = (x + size) % size;

+                        calderaY = (y + size) % size;

+

+

+                        if (calderaMap[calderaX][calderaY] == 1) {

+                            calderaMap[calderaX][calderaY] = 2;

+

+

+                            if (tempBuffer[calderaX][calderaY] > cutoff

+                                    && tempBuffer[calderaX][calderaY]

+                                    <= calderaStartPoint) {

+

+

+                                done = false;

+                                tempBuffer[calderaX][calderaY] =

+                                        2 * cutoff - tempBuffer[calderaX][calderaY];

+

+

+                                //check the left and right neighbors

+                                calderaX = (calderaX + 1) % size;

+                                if (calderaMap[calderaX][calderaY] == 0) {

+                                    if (x + 1 > maxx) {

+                                        maxx = x + 1;

+                                    }

+                                    calderaMap[calderaX][calderaY] = 1;

+                                }

+

+

+                                calderaX = (calderaX + size - 2) % size;

+                                if (calderaMap[calderaX][calderaY] == 0) {

+                                    if (x - 1 < minx) {

+                                        minx = x - 1;

+                                    }

+                                    calderaMap[calderaX][calderaY] = 1;

+                                }

+

+

+                                //check the upper and lower neighbors.

+                                calderaX = (x + size) % size;

+                                calderaY = (calderaY + 1) % size;

+                                if (calderaMap[calderaX][calderaY] == 0) {

+                                    if (y + 1 > maxy) {

+                                        maxy = y + 1;

+                                    }

+                                    calderaMap[calderaX][calderaY] = 1;

+                                }

+                                calderaY = (calderaY + size - 2) % size;

+                                if (calderaMap[calderaX][calderaY] == 0) {

+                                    if (y - 1 < miny) {

+                                        miny = y - 1;

+                                    }

+                                    calderaMap[calderaX][calderaY] = 1;

+                                }

+                            }

+                        }

+                    }

+                }

+            }

+        }

+

+        //transfer the new terrain into the height map.

+        for (int i = 0; i < size; i++) {

+            for (int j = 0; j < size; j++) {

+                setHeightAtPoint((float) tempBuffer[i][j], j, i);

+            }

+        }

+        erodeTerrain();

+        normalizeTerrain(NORMALIZE_RANGE);

+

+        logger.info("Created heightmap using Particle Deposition");

+

+

+        return false;

+    }

+

+    /**

+     * <code>setJumps</code> sets the number of jumps or peaks that will

+     * be created during the next call to <code>load</code>.

+     * @param jumps the number of jumps to use for next load.

+     * @throws JmeException if jumps is less than zero.

+     */

+    public void setJumps(int jumps) throws Exception {

+        if (jumps < 0) {

+            throw new Exception("jumps must be positive");

+        }

+        this.jumps = jumps;

+    }

+

+    /**

+     * <code>setPeakWalk</code> sets how often the jump point will be

+     * aggitated. The lower the peakWalk, the more often the point will

+     * be aggitated.

+     *

+     * @param peakWalk the amount to aggitate the jump point.

+     * @throws JmeException if peakWalk is negative or zero.

+     */

+    public void setPeakWalk(int peakWalk) throws Exception {

+        if (peakWalk <= 0) {

+            throw new Exception(

+                    "peakWalk must be greater than " + "zero");

+        }

+        this.peakWalk = peakWalk;

+    }

+

+    /**

+     * <code>setCaldera</code> sets the level at which a peak will be

+     * inverted.

+     *

+     * @param caldera the level at which a peak will be inverted. This must be

+     *              between 0 and 1, as it is represented as a percentage.

+     * @throws JmeException if caldera is not between 0 and 1.

+     */

+    public void setCaldera(float caldera) throws Exception {

+        if (caldera < 0.0f || caldera > 1.0f) {

+            throw new Exception(

+                    "Caldera level must be " + "between 0 and 1");

+        }

+        this.caldera = caldera;

+    }

+

+    /**

+     * <code>setMaxParticles</code> sets the maximum number of particles

+     * for a single jump.

+     * @param maxParticles the maximum number of particles for a single jump.

+     * @throws JmeException if maxParticles is negative or less than

+     *              the current number of minParticles.

+     */

+    public void setMaxParticles(int maxParticles) {

+        this.maxParticles = maxParticles;

+    }

+

+    /**

+     * <code>setMinParticles</code> sets the minimum number of particles

+     * for a single jump.

+     * @param minParticles the minimum number of particles for a single jump.

+     * @throws JmeException if minParticles are greater than

+     *              the current maxParticles;

+     */

+    public void setMinParticles(int minParticles) throws Exception {

+        if (minParticles > maxParticles) {

+            throw new Exception(

+                    "minParticles must be less " + "than the current maxParticles");

+        }

+        this.minParticles = minParticles;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/heightmap/RawHeightMap.java b/engine/src/terrain/com/jme3/terrain/heightmap/RawHeightMap.java
new file mode 100644
index 0000000..0318961
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/heightmap/RawHeightMap.java
@@ -0,0 +1,248 @@
+/*

+ * Copyright (c) 2009-2010 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.terrain.heightmap;

+

+import com.jme3.math.FastMath;

+import com.jme3.util.LittleEndien;

+import java.io.*;

+import java.net.URL;

+import java.util.logging.Logger;

+

+/**

+ * <code>RawHeightMap</code> creates a height map from a RAW image file. The

+ * greyscale image denotes height based on the value of the pixel for each

+ * point. Where pure black the lowest point and pure white denotes the highest.

+ *

+ * @author Mark Powell

+ * @version $Id$

+ */

+public class RawHeightMap extends AbstractHeightMap {

+

+    private static final Logger logger = Logger.getLogger(RawHeightMap.class.getName());

+    /**

+     * Format specification for 8 bit precision heightmaps

+     */

+    public static final int FORMAT_8BIT = 0;

+    /**

+     * Format specification for 16 bit little endian heightmaps

+     */

+    public static final int FORMAT_16BITLE = 1;

+    /**

+     * Format specification for 16 bit big endian heightmaps

+     */

+    public static final int FORMAT_16BITBE = 2;

+    private int format;

+    private boolean swapxy;

+    private InputStream stream;

+

+    /**

+     * Constructor creates a new <code>RawHeightMap</code> object and loads a

+     * RAW image file to use as a height field. The greyscale image denotes the

+     * height of the terrain, where dark is low point and bright is high point.

+     * The values of the RAW correspond directly with the RAW values or 0 - 255.

+     *

+     * @param filename

+     *            the RAW file to use as the heightmap.

+     * @param size

+     *            the size of the RAW (must be square).

+     * @throws JmeException

+     *             if the filename is null or not RAW, and if the size is 0 or

+     *             less.

+     */

+    public RawHeightMap(String filename, int size) throws Exception {

+        this(filename, size, FORMAT_8BIT, false);

+    }

+

+    public RawHeightMap(float heightData[]) {

+        this.heightData = heightData;

+        this.size = (int) FastMath.sqrt(heightData.length);

+        this.format = FORMAT_8BIT;

+    }

+

+    public RawHeightMap(String filename, int size, int format, boolean swapxy) throws Exception {

+        // varify that filename and size are valid.

+        if (null == filename || size <= 0) {

+            throw new Exception("Must supply valid filename and "

+                    + "size (> 0)");

+        }

+        try {

+            setup(new FileInputStream(filename), size, format, swapxy);

+        } catch (FileNotFoundException e) {

+            throw new Exception("height file not found: " + filename);

+        }

+    }

+

+    public RawHeightMap(InputStream stream, int size, int format, boolean swapxy) throws Exception {

+        setup(stream, size, format, swapxy);

+    }

+

+    public RawHeightMap(URL resource, int size, int format, boolean swapxy) throws Exception {

+        // varify that resource and size are valid.

+        if (null == resource || size <= 0) {

+            throw new Exception("Must supply valid resource and "

+                    + "size (> 0)");

+        }

+

+

+        try {

+            setup(resource.openStream(), size, format, swapxy);

+        } catch (IOException e) {

+            throw new Exception("Unable to open height url: " + resource);

+        }

+    }

+

+    private void setup(InputStream stream, int size, int format, boolean swapxy) throws Exception {

+        // varify that filename and size are valid.

+        if (null == stream || size <= 0) {

+            throw new Exception("Must supply valid stream and "

+                    + "size (> 0)");

+        }

+

+

+        this.stream = stream;

+        this.size = size;

+        this.format = format;

+        this.swapxy = swapxy;

+        load();

+    }

+

+    /**

+     * <code>load</code> fills the height data array with the appropriate data

+     * from the set RAW image. If the RAW image has not been set a JmeException

+     * will be thrown.

+     *

+     * @return true if the load is successfull, false otherwise.

+     */

+    @Override

+    public boolean load() {

+        // confirm data has been set. Redundant check...

+        if (null == stream || size <= 0) {

+            throw new RuntimeException("Must supply valid stream and "

+                    + "size (> 0)");

+        }

+

+

+        // clean up

+        if (null != heightData) {

+            unloadHeightMap();

+        }

+

+

+        // initialize the height data attributes

+        heightData = new float[size * size];

+

+

+        // attempt to connect to the supplied file.

+        BufferedInputStream bis = null;

+

+

+        try {

+            bis = new BufferedInputStream(stream);

+            if (format == RawHeightMap.FORMAT_16BITLE) {

+                LittleEndien dis = new LittleEndien(bis);

+                int index;

+                // read the raw file

+                for (int i = 0; i < size; i++) {

+                    for (int j = 0; j < size; j++) {

+                        if (swapxy) {

+                            index = i + j * size;

+                        } else {

+                            index = (i * size) + j;

+                        }

+                        heightData[index] = dis.readUnsignedShort();

+                    }

+                }

+                dis.close();

+            } else {

+                DataInputStream dis = new DataInputStream(bis);

+                // read the raw file

+                for (int i = 0; i < size; i++) {

+                    for (int j = 0; j < size; j++) {

+                        int index;

+                        if (swapxy) {

+                            index = i + j * size;

+                        } else {

+                            index = (i * size) + j;

+                        }

+                        if (format == RawHeightMap.FORMAT_16BITBE) {

+                            heightData[index] = dis.readUnsignedShort();

+                        } else {

+                            heightData[index] = dis.readUnsignedByte();

+                        }

+                    }

+                }

+                dis.close();

+            }

+            bis.close();

+        } catch (IOException e1) {

+            logger.warning("Error reading height data from stream.");

+            return false;

+        }

+        return true;

+    }

+

+    /**

+     * <code>setFilename</code> sets the file to use for the RAW data. A call

+     * to <code>load</code> is required to put the changes into effect.

+     *

+     * @param filename

+     *            the new file to use for the height data.

+     * @throws JmeException

+     *             if the file is null or not RAW.

+     */

+    public void setFilename(String filename) throws Exception {

+        if (null == filename) {

+            throw new Exception("Must supply valid filename.");

+        }

+        try {

+            this.stream = new FileInputStream(filename);

+        } catch (FileNotFoundException e) {

+            throw new Exception("height file not found: " + filename);

+        }

+    }

+

+    /**

+     * <code>setHeightStream</code> sets the stream to use for the RAW data. A call

+     * to <code>load</code> is required to put the changes into effect.

+     *

+     * @param stream

+     *            the new stream to use for the height data.

+     * @throws JmeException

+     *             if the stream is null or not RAW.

+     */

+    public void setHeightStream(InputStream stream) throws Exception {

+        if (null == stream) {

+            throw new Exception("Must supply valid stream.");

+        }

+        this.stream = stream;

+    }

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/Basis.java b/engine/src/terrain/com/jme3/terrain/noise/Basis.java
new file mode 100644
index 0000000..9f310f6
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/Basis.java
@@ -0,0 +1,77 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise;

+

+import java.nio.FloatBuffer;

+

+import com.jme3.terrain.noise.basis.ImprovedNoise;

+import com.jme3.terrain.noise.modulator.Modulator;

+

+/**

+ * Interface for - basically 3D - noise generation algorithms, based on the

+ * book: Texturing & Modeling - A Procedural Approach

+ * 

+ * The main concept is to look at noise as a basis for generating fractals.

+ * Basis can be anything, like a simple:

+ * 

+ * <code>

+ * float value(float x, float y, float z) {

+ * 		return 0; // a flat noise with 0 value everywhere

+ * }

+ * </code>

+ * 

+ * or a more complex perlin noise ({@link ImprovedNoise}

+ * 

+ * Fractals use these functions to generate a more complex result based on some

+ * frequency, roughness, etc values.

+ * 

+ * Fractals themselves are implementing the Basis interface as well, opening

+ * an infinite range of results.

+ * 

+ * @author Anthyon

+ * 

+ * @since 2011

+ * 

+ */

+public interface Basis {

+

+	public void init();

+

+	public Basis setScale(float scale);

+

+	public float getScale();

+

+	public Basis addModulator(Modulator modulator);

+

+	public float value(float x, float y, float z);

+

+	public FloatBuffer getBuffer(float sx, float sy, float base, int size);

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/Color.java b/engine/src/terrain/com/jme3/terrain/noise/Color.java
new file mode 100644
index 0000000..719c0ff
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/Color.java
@@ -0,0 +1,134 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise;

+

+/**

+ * Helper class for working with colors and gradients

+ * 

+ * @author Anthyon

+ * 

+ */

+public class Color {

+

+	private final float[] rgba = new float[4];

+

+	public Color() {}

+

+	public Color(final int r, final int g, final int b) {

+		this(r, g, b, 255);

+	}

+

+	public Color(final int r, final int g, final int b, final int a) {

+		this.rgba[0] = (r & 255) / 256f;

+		this.rgba[1] = (g & 255) / 256f;

+		this.rgba[2] = (b & 255) / 256f;

+		this.rgba[3] = (a & 255) / 256f;

+	}

+

+	public Color(final float r, final float g, final float b) {

+		this(r, g, b, 1);

+	}

+

+	public Color(final float r, final float g, final float b, final float a) {

+		this.rgba[0] = ShaderUtils.clamp(r, 0, 1);

+		this.rgba[1] = ShaderUtils.clamp(g, 0, 1);

+		this.rgba[2] = ShaderUtils.clamp(b, 0, 1);

+		this.rgba[3] = ShaderUtils.clamp(a, 0, 1);

+	}

+

+	public Color(final int h, final float s, final float b) {

+		this(h, s, b, 1);

+	}

+

+	public Color(final int h, final float s, final float b, final float a) {

+		this.rgba[3] = a;

+		if (s == 0) {

+			// achromatic ( grey )

+			this.rgba[0] = b;

+			this.rgba[1] = b;

+			this.rgba[2] = b;

+			return;

+		}

+

+		float hh = h / 60.0f;

+		int i = ShaderUtils.floor(hh);

+		float f = hh - i;

+		float p = b * (1 - s);

+		float q = b * (1 - s * f);

+		float t = b * (1 - s * (1 - f));

+

+		if (i == 0) {

+			this.rgba[0] = b;

+			this.rgba[1] = t;

+			this.rgba[2] = p;

+		} else if (i == 1) {

+			this.rgba[0] = q;

+			this.rgba[1] = b;

+			this.rgba[2] = p;

+		} else if (i == 2) {

+			this.rgba[0] = p;

+			this.rgba[1] = b;

+			this.rgba[2] = t;

+		} else if (i == 3) {

+			this.rgba[0] = p;

+			this.rgba[1] = q;

+			this.rgba[2] = b;

+		} else if (i == 4) {

+			this.rgba[0] = t;

+			this.rgba[1] = p;

+			this.rgba[2] = b;

+		} else {

+			this.rgba[0] = b;

+			this.rgba[1] = p;

+			this.rgba[2] = q;

+		}

+	}

+

+	public int toInteger() {

+		return 0x00000000 | (int) (this.rgba[3] * 256) << 24 | (int) (this.rgba[0] * 256) << 16 | (int) (this.rgba[1] * 256) << 8

+				| (int) (this.rgba[2] * 256);

+	}

+

+	public String toWeb() {

+		return Integer.toHexString(this.toInteger());

+	}

+

+	public Color toGrayscale() {

+		float v = (this.rgba[0] + this.rgba[1] + this.rgba[2]) / 3f;

+		return new Color(v, v, v, this.rgba[3]);

+	}

+

+	public Color toSepia() {

+		float r = ShaderUtils.clamp(this.rgba[0] * 0.393f + this.rgba[1] * 0.769f + this.rgba[2] * 0.189f, 0, 1);

+		float g = ShaderUtils.clamp(this.rgba[0] * 0.349f + this.rgba[1] * 0.686f + this.rgba[2] * 0.168f, 0, 1);

+		float b = ShaderUtils.clamp(this.rgba[0] * 0.272f + this.rgba[1] * 0.534f + this.rgba[2] * 0.131f, 0, 1);

+		return new Color(r, g, b, this.rgba[3]);

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/Filter.java b/engine/src/terrain/com/jme3/terrain/noise/Filter.java
new file mode 100644
index 0000000..1dca8bf
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/Filter.java
@@ -0,0 +1,44 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise;

+

+import java.nio.FloatBuffer;

+

+public interface Filter {

+	public Filter addPreFilter(Filter filter);

+

+	public Filter addPostFilter(Filter filter);

+

+	public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size);

+

+	public int getMargin(int size, int margin);

+

+	public boolean isEnabled();

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/ShaderUtils.java b/engine/src/terrain/com/jme3/terrain/noise/ShaderUtils.java
new file mode 100644
index 0000000..d1c2f6e
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/ShaderUtils.java
@@ -0,0 +1,288 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise;

+

+import java.awt.Color;

+import java.awt.Graphics2D;

+import java.awt.image.BufferedImage;

+import java.awt.image.DataBuffer;

+import java.awt.image.DataBufferInt;

+import java.awt.image.WritableRaster;

+import java.nio.ByteBuffer;

+import java.nio.ByteOrder;

+

+/**

+ * Helper class containing useful functions explained in the book:

+ * Texturing & Modeling - A Procedural Approach

+ * 

+ * @author Anthyon

+ * 

+ */

+public class ShaderUtils {

+

+	public static final float[] i2c(final int color) {

+		return new float[] { (color & 0x00ff0000) / 256f, (color & 0x0000ff00) / 256f, (color & 0x000000ff) / 256f,

+				(color & 0xff000000) / 256f };

+	}

+

+	public static final int c2i(final float[] color) {

+		return (color.length == 4 ? (int) (color[3] * 256) : 0xff000000) | ((int) (color[0] * 256) << 16) | ((int) (color[1] * 256) << 8)

+				| (int) (color[2] * 256);

+	}

+

+	public static final float mix(final float a, final float b, final float f) {

+		return (1 - f) * a + f * b;

+	}

+

+	public static final Color mix(final Color a, final Color b, final float f) {

+		return new Color((int) ShaderUtils.clamp(ShaderUtils.mix(a.getRed(), b.getRed(), f), 0, 255), (int) ShaderUtils.clamp(

+				ShaderUtils.mix(a.getGreen(), b.getGreen(), f), 0, 255), (int) ShaderUtils.clamp(

+				ShaderUtils.mix(a.getBlue(), b.getBlue(), f), 0, 255));

+	}

+

+	public static final int mix(final int a, final int b, final float f) {

+		return (int) ((1 - f) * a + f * b);

+	}

+

+	public static final float[] mix(final float[] c1, final float[] c2, final float f) {

+		return new float[] { ShaderUtils.mix(c1[0], c2[0], f), ShaderUtils.mix(c1[1], c2[1], f), ShaderUtils.mix(c1[2], c2[2], f) };

+	}

+

+	public static final float step(final float a, final float x) {

+		return x < a ? 0 : 1;

+	}

+

+	public static final float boxstep(final float a, final float b, final float x) {

+		return ShaderUtils.clamp((x - a) / (b - a), 0, 1);

+	}

+

+	public static final float pulse(final float a, final float b, final float x) {

+		return ShaderUtils.step(a, x) - ShaderUtils.step(b, x);

+	}

+

+	public static final float clamp(final float x, final float a, final float b) {

+		return x < a ? a : x > b ? b : x;

+	}

+

+	public static final float min(final float a, final float b) {

+		return a < b ? a : b;

+	}

+

+	public static final float max(final float a, final float b) {

+		return a > b ? a : b;

+	}

+

+	public static final float abs(final float x) {

+		return x < 0 ? -x : x;

+	}

+

+	public static final float smoothstep(final float a, final float b, final float x) {

+		if (x < a) {

+			return 0;

+		} else if (x > b) {

+			return 1;

+		}

+		float xx = (x - a) / (b - a);

+		return xx * xx * (3 - 2 * xx);

+	}

+

+	public static final float mod(final float a, final float b) {

+		int n = (int) (a / b);

+		float aa = a - n * b;

+		if (aa < 0) {

+			aa += b;

+		}

+		return aa;

+	}

+

+	public static final int floor(final float x) {

+		return x > 0 ? (int) x : (int) x - 1;

+	}

+

+	public static final float ceil(final float x) {

+		return (int) x + (x > 0 && x != (int) x ? 1 : 0);

+	}

+

+	public static final float spline(float x, final float[] knot) {

+		float CR00 = -0.5f;

+		float CR01 = 1.5f;

+		float CR02 = -1.5f;

+		float CR03 = 0.5f;

+		float CR10 = 1.0f;

+		float CR11 = -2.5f;

+		float CR12 = 2.0f;

+		float CR13 = -0.5f;

+		float CR20 = -0.5f;

+		float CR21 = 0.0f;

+		float CR22 = 0.5f;

+		float CR23 = 0.0f;

+		float CR30 = 0.0f;

+		float CR31 = 1.0f;

+		float CR32 = 0.0f;

+		float CR33 = 0.0f;

+

+		int span;

+		int nspans = knot.length - 3;

+		float c0, c1, c2, c3; /* coefficients of the cubic. */

+		if (nspans < 1) {/* illegal */

+			throw new RuntimeException("Spline has too few knots.");

+		}

+		/* Find the appropriate 4-point span of the spline. */

+		x = ShaderUtils.clamp(x, 0, 1) * nspans;

+		span = (int) x;

+		if (span >= knot.length - 3) {

+			span = knot.length - 3;

+		}

+		x -= span;

+		/* Evaluate the span cubic at x using Horner’s rule. */

+		c3 = CR00 * knot[span + 0] + CR01 * knot[span + 1] + CR02 * knot[span + 2] + CR03 * knot[span + 3];

+		c2 = CR10 * knot[span + 0] + CR11 * knot[span + 1] + CR12 * knot[span + 2] + CR13 * knot[span + 3];

+		c1 = CR20 * knot[span + 0] + CR21 * knot[span + 1] + CR22 * knot[span + 2] + CR23 * knot[span + 3];

+		c0 = CR30 * knot[span + 0] + CR31 * knot[span + 1] + CR32 * knot[span + 2] + CR33 * knot[span + 3];

+		return ((c3 * x + c2) * x + c1) * x + c0;

+	}

+

+	public static final float[] spline(final float x, final float[][] knots) {

+		float[] retval = new float[knots.length];

+		for (int i = 0; i < knots.length; i++) {

+			retval[i] = ShaderUtils.spline(x, knots[i]);

+		}

+		return retval;

+	}

+

+	public static final float gammaCorrection(final float gamma, final float x) {

+		return (float) Math.pow(x, 1 / gamma);

+	}

+

+	public static final float bias(final float b, final float x) {

+		return (float) Math.pow(x, Math.log(b) / Math.log(0.5));

+	}

+

+	public static final float gain(final float g, final float x) {

+		return x < 0.5 ? ShaderUtils.bias(1 - g, 2 * x) / 2 : 1 - ShaderUtils.bias(1 - g, 2 - 2 * x) / 2;

+	}

+

+	public static final float sinValue(final float s, final float minFreq, final float maxFreq, final float swidth) {

+		float value = 0;

+		float cutoff = ShaderUtils.clamp(0.5f / swidth, 0, maxFreq);

+		float f;

+		for (f = minFreq; f < 0.5 * cutoff; f *= 2) {

+			value += Math.sin(2 * Math.PI * f * s) / f;

+		}

+		float fade = ShaderUtils.clamp(2 * (cutoff - f) / cutoff, 0, 1);

+		value += fade * Math.sin(2 * Math.PI * f * s) / f;

+		return value;

+	}

+

+	public static final float length(final float x, final float y, final float z) {

+		return (float) Math.sqrt(x * x + y * y + z * z);

+	}

+

+	public static final float[] rotate(final float[] v, final float[][] m) {

+		float x = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2];

+		float y = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2];

+		float z = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2];

+		return new float[] { x, y, z };

+	}

+

+	public static final float[][] calcRotationMatrix(final float ax, final float ay, final float az) {

+		float[][] retval = new float[3][3];

+		float cax = (float) Math.cos(ax);

+		float sax = (float) Math.sin(ax);

+		float cay = (float) Math.cos(ay);

+		float say = (float) Math.sin(ay);

+		float caz = (float) Math.cos(az);

+		float saz = (float) Math.sin(az);

+

+		retval[0][0] = cay * caz;

+		retval[0][1] = -cay * saz;

+		retval[0][2] = say;

+		retval[1][0] = sax * say * caz + cax * saz;

+		retval[1][1] = -sax * say * saz + cax * caz;

+		retval[1][2] = -sax * cay;

+		retval[2][0] = -cax * say * caz + sax * saz;

+		retval[2][1] = cax * say * saz + sax * caz;

+		retval[2][2] = cax * cay;

+

+		return retval;

+	}

+

+	public static final float[] normalize(final float[] v) {

+		float l = ShaderUtils.length(v);

+		float[] r = new float[v.length];

+		int i = 0;

+		for (float vv : v) {

+			r[i++] = vv / l;

+		}

+		return r;

+	}

+

+	public static final float length(final float[] v) {

+		float s = 0;

+		for (float vv : v) {

+			s += vv * vv;

+		}

+		return (float) Math.sqrt(s);

+	}

+

+	public static final ByteBuffer getImageDataFromImage(BufferedImage bufferedImage) {

+		WritableRaster wr;

+		DataBuffer db;

+

+		BufferedImage bi = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB);

+		Graphics2D g = bi.createGraphics();

+		g.drawImage(bufferedImage, null, null);

+		bufferedImage = bi;

+		wr = bi.getRaster();

+		db = wr.getDataBuffer();

+

+		DataBufferInt dbi = (DataBufferInt) db;

+		int[] data = dbi.getData();

+

+		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);

+		byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

+		byteBuffer.asIntBuffer().put(data);

+		byteBuffer.flip();

+

+		return byteBuffer;

+	}

+

+	public static float frac(float f) {

+		return f - ShaderUtils.floor(f);

+	}

+

+	public static float[] floor(float[] fs) {

+		float[] retval = new float[fs.length];

+		for (int i = 0; i < fs.length; i++) {

+			retval[i] = ShaderUtils.floor(fs[i]);

+		}

+		return retval;

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/basis/FilteredBasis.java b/engine/src/terrain/com/jme3/terrain/noise/basis/FilteredBasis.java
new file mode 100644
index 0000000..53d0938
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/basis/FilteredBasis.java
@@ -0,0 +1,111 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.basis;

+

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.List;

+

+import com.jme3.terrain.noise.Basis;

+import com.jme3.terrain.noise.filter.AbstractFilter;

+import com.jme3.terrain.noise.modulator.Modulator;

+

+public class FilteredBasis extends AbstractFilter implements Basis {

+

+	private Basis basis;

+	private List<Modulator> modulators = new ArrayList<Modulator>();

+	private float scale;

+

+	public FilteredBasis() {}

+

+	public FilteredBasis(Basis basis) {

+		this.basis = basis;

+	}

+

+	public Basis getBasis() {

+		return this.basis;

+	}

+

+	public void setBasis(Basis basis) {

+		this.basis = basis;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) {

+		return data;

+	}

+

+	@Override

+	public void init() {

+		this.basis.init();

+	}

+

+	@Override

+	public Basis setScale(float scale) {

+		this.scale = scale;

+		return this;

+	}

+

+	@Override

+	public float getScale() {

+		return this.scale;

+	}

+

+	@Override

+	public Basis addModulator(Modulator modulator) {

+		this.modulators.add(modulator);

+		return this;

+	}

+

+	@Override

+	public float value(float x, float y, float z) {

+		throw new UnsupportedOperationException(

+				"Method value cannot be called on FilteredBasis and its descendants. Use getBuffer instead!");

+	}

+

+	@Override

+	public FloatBuffer getBuffer(float sx, float sy, float base, int size) {

+		int margin = this.getMargin(size, 0);

+		int workSize = size + 2 * margin;

+		FloatBuffer retval = this.basis.getBuffer(sx - margin, sy - margin, base, workSize);

+		return this.clip(this.doFilter(sx, sy, base, retval, workSize), workSize, size, margin);

+	}

+

+	public FloatBuffer clip(FloatBuffer buf, int origSize, int newSize, int offset) {

+		FloatBuffer result = FloatBuffer.allocate(newSize * newSize);

+

+		float[] orig = buf.array();

+		for (int i = offset; i < offset + newSize; i++) {

+			result.put(orig, i * origSize + offset, newSize);

+		}

+

+		return result;

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/basis/ImprovedNoise.java b/engine/src/terrain/com/jme3/terrain/noise/basis/ImprovedNoise.java
new file mode 100644
index 0000000..9233ef8
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/basis/ImprovedNoise.java
@@ -0,0 +1,127 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.basis;

+

+import com.jme3.terrain.noise.ShaderUtils;

+

+// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.

+/**

+ * Perlin's default implementation of Improved Perlin Noise

+ * designed to work with Noise base

+ */

+public final class ImprovedNoise extends Noise {

+

+	@Override

+	public void init() {

+

+	}

+

+	static public float noise(float x, float y, float z) {

+		int X = ShaderUtils.floor(x), // FIND UNIT CUBE THAT

+		Y = ShaderUtils.floor(y), // CONTAINS POINT.

+		Z = ShaderUtils.floor(z);

+		x -= X; // FIND RELATIVE X,Y,Z

+		y -= Y; // OF POINT IN CUBE.

+		z -= Z;

+		X = X & 255;

+		Y = Y & 255;

+		Z = Z & 255;

+		float u = ImprovedNoise.fade(x), // COMPUTE FADE CURVES

+		v = ImprovedNoise.fade(y), // FOR EACH OF X,Y,Z.

+		w = ImprovedNoise.fade(z);

+		int A = ImprovedNoise.p[X] + Y;

+		int AA = ImprovedNoise.p[A] + Z;

+		int AB = ImprovedNoise.p[A + 1] + Z;

+		int B = ImprovedNoise.p[X + 1] + Y;

+		int BA = ImprovedNoise.p[B] + Z;

+		int BB = ImprovedNoise.p[B + 1] + Z;

+

+		return ImprovedNoise.lerp(

+				w,

+				ImprovedNoise.lerp(

+						v,

+						ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA], x, y, z),

+								ImprovedNoise.grad3(ImprovedNoise.p[BA], x - 1, y, z)), // BLENDED

+						ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB], x, y - 1, z), // RESULTS

+								ImprovedNoise.grad3(ImprovedNoise.p[BB], x - 1, y - 1, z))),// FROM

+				ImprovedNoise.lerp(v,

+						ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA + 1], x, y, z - 1), // CORNERS

+								ImprovedNoise.grad3(ImprovedNoise.p[BA + 1], x - 1, y, z - 1)), // OF

+						ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB + 1], x, y - 1, z - 1),

+								ImprovedNoise.grad3(ImprovedNoise.p[BB + 1], x - 1, y - 1, z - 1))));

+	}

+

+	static final float fade(final float t) {

+		return t * t * t * (t * (t * 6 - 15) + 10);

+	}

+

+	static final float lerp(final float t, final float a, final float b) {

+		return a + t * (b - a);

+	}

+

+	static float grad(final int hash, final float x, final float y, final float z) {

+		int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE

+		float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.

+		v = h < 4 ? y : h == 12 || h == 14 ? x : z;

+		return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);

+	}

+

+	static final float grad3(final int hash, final float x, final float y, final float z) {

+		int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE

+		return x * ImprovedNoise.GRAD3[h][0] + y * ImprovedNoise.GRAD3[h][1] + z * ImprovedNoise.GRAD3[h][2];

+	}

+

+	static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36,

+			103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11,

+			32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,

+			231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80,

+			73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,

+			226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183,

+			170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113,

+			224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235,

+			249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,

+			93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };

+

+	private static float[][] GRAD3 = new float[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 },

+			{ 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, -1, 1 },

+			{ 0, 1, 1 } };

+

+	static {

+		for (int i = 0; i < 256; i++) {

+			ImprovedNoise.p[256 + i] = ImprovedNoise.p[i] = ImprovedNoise.permutation[i];

+		}

+	}

+

+	@Override

+	public float value(final float x, final float y, final float z) {

+		return ImprovedNoise.noise(this.scale * x, this.scale * y, this.scale * z);

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/basis/Noise.java b/engine/src/terrain/com/jme3/terrain/noise/basis/Noise.java
new file mode 100644
index 0000000..c9de6f5
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/basis/Noise.java
@@ -0,0 +1,94 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.basis;

+

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.List;

+

+import com.jme3.terrain.noise.Basis;

+import com.jme3.terrain.noise.modulator.Modulator;

+import com.jme3.terrain.noise.modulator.NoiseModulator;

+

+/**

+ * Utility base class for Noise implementations

+ * 

+ * @author Anthyon

+ * 

+ */

+public abstract class Noise implements Basis {

+

+	protected List<Modulator> modulators = new ArrayList<Modulator>();

+

+	protected float scale = 1.0f;

+

+	@Override

+	public String toString() {

+		return this.getClass().getSimpleName();

+	}

+

+	@Override

+	public FloatBuffer getBuffer(float sx, float sy, float base, int size) {

+		FloatBuffer retval = FloatBuffer.allocate(size * size);

+		for (int y = 0; y < size; y++) {

+			for (int x = 0; x < size; x++) {

+				retval.put(this.modulate((sx + x) / size, (sy + y) / size, base));

+			}

+		}

+		return retval;

+	}

+

+	public float modulate(float x, float y, float z) {

+		float retval = this.value(x, y, z);

+		for (Modulator m : this.modulators) {

+			if (m instanceof NoiseModulator) {

+				retval = m.value(retval);

+			}

+		}

+		return retval;

+	}

+

+	@Override

+	public Basis addModulator(Modulator modulator) {

+		this.modulators.add(modulator);

+		return this;

+	}

+

+	@Override

+	public Basis setScale(float scale) {

+		this.scale = scale;

+		return this;

+	}

+

+	@Override

+	public float getScale() {

+		return this.scale;

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/basis/NoiseAggregator.java b/engine/src/terrain/com/jme3/terrain/noise/basis/NoiseAggregator.java
new file mode 100644
index 0000000..8a547a3
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/basis/NoiseAggregator.java
@@ -0,0 +1,64 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.basis;

+

+import com.jme3.terrain.noise.Basis;

+

+/**

+ * A simple aggregator basis. Takes two basis functions and a rate and return

+ * some mixed values

+ * 

+ * @author Anthyon

+ * 

+ */

+public class NoiseAggregator extends Noise {

+

+	private final float rate;

+	private final Basis a;

+	private final Basis b;

+

+	public NoiseAggregator(final Basis a, final Basis b, final float rate) {

+		this.a = a;

+		this.b = b;

+		this.rate = rate;

+	}

+

+	@Override

+	public void init() {

+		this.a.init();

+		this.b.init();

+	}

+

+	@Override

+	public float value(final float x, final float y, final float z) {

+		return this.a.value(x, y, z) * (1 - this.rate) + this.rate * this.b.value(x, y, z);

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/AbstractFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/AbstractFilter.java
new file mode 100644
index 0000000..4174ba9
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/AbstractFilter.java
@@ -0,0 +1,100 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.List;

+

+import com.jme3.terrain.noise.Filter;

+

+public abstract class AbstractFilter implements Filter {

+

+	protected List<Filter> preFilters = new ArrayList<Filter>();

+	protected List<Filter> postFilters = new ArrayList<Filter>();

+

+	private boolean enabled = true;

+

+	@Override

+	public Filter addPreFilter(Filter filter) {

+		this.preFilters.add(filter);

+		return this;

+	}

+

+	@Override

+	public Filter addPostFilter(Filter filter) {

+		this.postFilters.add(filter);

+		return this;

+	}

+

+	@Override

+	public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size) {

+		if (!this.isEnabled()) {

+			return data;

+		}

+		FloatBuffer retval = data;

+		for (Filter f : this.preFilters) {

+			retval = f.doFilter(sx, sy, base, retval, size);

+		}

+		retval = this.filter(sx, sy, base, retval, size);

+		for (Filter f : this.postFilters) {

+			retval = f.doFilter(sx, sy, base, retval, size);

+		}

+		return retval;

+	}

+

+	public abstract FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size);

+

+	@Override

+	public int getMargin(int size, int margin) {

+		// TODO sums up all the margins from filters... maybe there's a more

+		// efficient algorithm

+		if (!this.isEnabled()) {

+			return margin;

+		}

+		for (Filter f : this.preFilters) {

+			margin = f.getMargin(size, margin);

+		}

+		for (Filter f : this.postFilters) {

+			margin = f.getMargin(size, margin);

+		}

+		return margin;

+	}

+

+	@Override

+	public boolean isEnabled() {

+		return this.enabled;

+	}

+

+	public void setEnabled(boolean enabled) {

+		this.enabled = enabled;

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java
new file mode 100644
index 0000000..13e2c5e
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java
@@ -0,0 +1,154 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+

+import com.jme3.terrain.noise.Basis;

+

+public class HydraulicErodeFilter extends AbstractFilter {

+

+	private Basis waterMap;

+	private Basis sedimentMap;

+	private float Kr;

+	private float Ks;

+	private float Ke;

+	private float Kc;

+	private float T;

+

+	public void setKc(float kc) {

+		this.Kc = kc;

+	}

+

+	public void setKe(float ke) {

+		this.Ke = ke;

+	}

+

+	public void setKr(float kr) {

+		this.Kr = kr;

+	}

+

+	public void setKs(float ks) {

+		this.Ks = ks;

+	}

+

+	public void setSedimentMap(Basis sedimentMap) {

+		this.sedimentMap = sedimentMap;

+	}

+

+	public void setT(float t) {

+		this.T = t;

+	}

+

+	public void setWaterMap(Basis waterMap) {

+		this.waterMap = waterMap;

+	}

+

+	@Override

+	public int getMargin(int size, int margin) {

+		return super.getMargin(size, margin) + 1;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) {

+		float[] ga = buffer.array();

+		// float[] wa = this.waterMap.getBuffer(sx, sy, base, workSize).array();

+		// float[] sa = this.sedimentMap.getBuffer(sx, sy, base,

+		// workSize).array();

+		float[] wt = new float[workSize * workSize];

+		float[] st = new float[workSize * workSize];

+

+		int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 };

+

+		// step 1. water arrives and step 2. captures material

+		for (int y = 0; y < workSize; y++) {

+			for (int x = 0; x < workSize; x++) {

+				int idx = y * workSize + x;

+				float wtemp = this.Kr; // * wa[idx];

+				float stemp = this.Ks; // * sa[idx];

+				if (wtemp > 0) {

+					wt[idx] += wtemp;

+					if (stemp > 0) {

+						ga[idx] -= stemp * wt[idx];

+						st[idx] += stemp * wt[idx];

+					}

+				}

+

+				// step 3. water is transported to it's neighbours

+				float a = ga[idx] + wt[idx];

+				// float[] aj = new float[idxrel.length];

+				float amax = 0;

+				int amaxidx = -1;

+				float ac = 0;

+				float dtotal = 0;

+

+				for (int j = 0; j < idxrel.length; j++) {

+					if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize) {

+						float at = ga[idx + idxrel[j]] + wt[idx + idxrel[j]];

+						if (a - at > a - amax) {

+							dtotal += at;

+							amax = at;

+							amaxidx = j;

+							ac++;

+						}

+					}

+				}

+

+				float aa = (dtotal + a) / (ac + 1);

+				// for (int j = 0; j < idxrel.length; j++) {

+				// if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize && a -

+				// aj[j] > 0) {

+				if (amaxidx > -1) {

+					float dwj = Math.min(wt[idx], a - aa) * (a - amax) / dtotal;

+					float dsj = st[idx] * dwj / wt[idx];

+					wt[idx] -= dwj;

+					st[idx] -= dsj;

+					wt[idx + idxrel[amaxidx]] += dwj;

+					st[idx + idxrel[amaxidx]] += dsj;

+				}

+				// }

+

+				// step 4. water evaporates and deposits material

+				wt[idx] = wt[idx] * (1 - this.Ke);

+				if (wt[idx] < this.T) {

+					wt[idx] = 0;

+				}

+				float smax = this.Kc * wt[idx];

+				if (st[idx] > smax) {

+					ga[idx] += st[idx] - smax;

+					st[idx] -= st[idx] - smax;

+				}

+			}

+		}

+

+		return buffer;

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/IterativeFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/IterativeFilter.java
new file mode 100644
index 0000000..3764256
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/IterativeFilter.java
@@ -0,0 +1,102 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+import java.util.ArrayList;

+import java.util.List;

+

+import com.jme3.terrain.noise.Filter;

+

+public class IterativeFilter extends AbstractFilter {

+

+	private int iterations;

+

+	private List<Filter> preIterateFilters = new ArrayList<Filter>();

+	private List<Filter> postIterateFilters = new ArrayList<Filter>();

+	private Filter filter;

+

+	@Override

+	public int getMargin(int size, int margin) {

+		if (!this.isEnabled()) {

+			return margin;

+		}

+		for (Filter f : this.preIterateFilters) {

+			margin = f.getMargin(size, margin);

+		}

+		margin = this.filter.getMargin(size, margin);

+		for (Filter f : this.postIterateFilters) {

+			margin = f.getMargin(size, margin);

+		}

+		return this.iterations * margin + super.getMargin(size, margin);

+	}

+

+	public void setIterations(int iterations) {

+		this.iterations = iterations;

+	}

+

+	public int getIterations() {

+		return this.iterations;

+	}

+

+	public IterativeFilter addPostIterateFilter(Filter filter) {

+		this.postIterateFilters.add(filter);

+		return this;

+	}

+

+	public IterativeFilter addPreIterateFilter(Filter filter) {

+		this.preIterateFilters.add(filter);

+		return this;

+	}

+

+	public void setFilter(Filter filter) {

+		this.filter = filter;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) {

+		if (!this.isEnabled()) {

+			return data;

+		}

+		FloatBuffer retval = data;

+

+		for (int i = 0; i < this.iterations; i++) {

+			for (Filter f : this.preIterateFilters) {

+				retval = f.doFilter(sx, sy, base, retval, size);

+			}

+			retval = this.filter.doFilter(sx, sy, base, retval, size);

+			for (Filter f : this.postIterateFilters) {

+				retval = f.doFilter(sx, sy, base, retval, size);

+			}

+		}

+

+		return retval;

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/OptimizedErode.java b/engine/src/terrain/com/jme3/terrain/noise/filter/OptimizedErode.java
new file mode 100644
index 0000000..fc20da6
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/OptimizedErode.java
@@ -0,0 +1,113 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+

+public class OptimizedErode extends AbstractFilter {

+

+	private float talus;

+	private int radius;

+

+	public OptimizedErode setRadius(int radius) {

+		this.radius = radius;

+		return this;

+	}

+

+	public int getRadius() {

+		return this.radius;

+	}

+

+	public OptimizedErode setTalus(float talus) {

+		this.talus = talus;

+		return this;

+	}

+

+	public float getTalus() {

+		return this.talus;

+	}

+

+	@Override

+	public int getMargin(int size, int margin) {

+		return super.getMargin(size, margin) + this.radius;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) {

+		float[] tmp = buffer.array();

+		float[] retval = new float[tmp.length];

+

+		for (int y = this.radius + 1; y < size - this.radius; y++) {

+			for (int x = this.radius + 1; x < size - this.radius; x++) {

+				int idx = y * size + x;

+				float h = tmp[idx];

+

+				float horizAvg = 0;

+				int horizCount = 0;

+				float vertAvg = 0;

+				int vertCount = 0;

+

+				boolean horizT = false;

+				boolean vertT = false;

+

+				for (int i = 0; i >= -this.radius; i--) {

+					int idxV = (y + i) * size + x;

+					int idxVL = (y + i - 1) * size + x;

+					int idxH = y * size + x + i;

+					int idxHL = y * size + x + i - 1;

+					float hV = tmp[idxV];

+					float hH = tmp[idxH];

+

+					if (Math.abs(h - hV) > this.talus && Math.abs(h - tmp[idxVL]) > this.talus || vertT) {

+						vertT = true;

+					} else {

+						if (Math.abs(h - hV) <= this.talus) {

+							vertAvg += hV;

+							vertCount++;

+						}

+					}

+

+					if (Math.abs(h - hH) > this.talus && Math.abs(h - tmp[idxHL]) > this.talus || horizT) {

+						horizT = true;

+					} else {

+						if (Math.abs(h - hH) <= this.talus) {

+							horizAvg += hH;

+							horizCount++;

+						}

+					}

+				}

+

+				retval[idx] = 0.5f * (vertAvg / (vertCount > 0 ? vertCount : 1) + horizAvg / (horizCount > 0 ? horizCount : 1));

+			}

+		}

+		return FloatBuffer.wrap(retval);

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/PerturbFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/PerturbFilter.java
new file mode 100644
index 0000000..f510f90
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/PerturbFilter.java
@@ -0,0 +1,98 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+import java.util.logging.Logger;

+

+import com.jme3.terrain.noise.ShaderUtils;

+import com.jme3.terrain.noise.fractal.FractalSum;

+

+public class PerturbFilter extends AbstractFilter {

+

+	private float magnitude;

+

+	@Override

+	public int getMargin(int size, int margin) {

+		margin = super.getMargin(size, margin);

+		return (int) Math.floor(this.magnitude * (margin + size) + margin);

+	}

+

+	public void setMagnitude(float magnitude) {

+		this.magnitude = magnitude;

+	}

+

+	public float getMagnitude() {

+		return this.magnitude;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int workSize) {

+		float[] arr = data.array();

+		int origSize = (int) Math.ceil(workSize / (2 * this.magnitude + 1));

+		int offset = (workSize - origSize) / 2;

+		Logger.getLogger(PerturbFilter.class.getCanonicalName()).info(

+				"Found origSize : " + origSize + " and offset: " + offset + " for workSize : " + workSize + " and magnitude : "

+						+ this.magnitude);

+		float[] retval = new float[workSize * workSize];

+		float[] perturbx = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base, workSize).array();

+		float[] perturby = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base + 1, workSize).array();

+		for (int y = 0; y < workSize; y++) {

+			for (int x = 0; x < workSize; x++) {

+				// Perturb our coordinates

+				float noisex = perturbx[y * workSize + x];

+				float noisey = perturby[y * workSize + x];

+

+				int px = (int) (origSize * noisex * this.magnitude);

+				int py = (int) (origSize * noisey * this.magnitude);

+

+				float c00 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x - px, workSize)];

+				float c01 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x + px, workSize)];

+				float c10 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x - px, workSize)];

+				float c11 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x + px, workSize)];

+

+				float c0 = ShaderUtils.mix(c00, c01, noisex);

+				float c1 = ShaderUtils.mix(c10, c11, noisex);

+				retval[y * workSize + x] = ShaderUtils.mix(c0, c1, noisey);

+			}

+		}

+		return FloatBuffer.wrap(retval);

+	}

+

+	private int wrap(int v, int size) {

+		if (v < 0) {

+			return v + size - 1;

+		} else if (v >= size) {

+			return v - size;

+		} else {

+			return v;

+		}

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/SmoothFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/SmoothFilter.java
new file mode 100644
index 0000000..1c20c24
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/SmoothFilter.java
@@ -0,0 +1,80 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+

+public class SmoothFilter extends AbstractFilter {

+

+	private int radius;

+	private float effect;

+

+	public void setRadius(int radius) {

+		this.radius = radius;

+	}

+

+	public int getRadius() {

+		return this.radius;

+	}

+

+	public void setEffect(float effect) {

+		this.effect = effect;

+	}

+

+	public float getEffect() {

+		return this.effect;

+	}

+

+	@Override

+	public int getMargin(int size, int margin) {

+		return super.getMargin(size, margin) + this.radius;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) {

+		float[] data = buffer.array();

+		float[] retval = new float[data.length];

+

+		for (int y = this.radius; y < size - this.radius; y++) {

+			for (int x = this.radius; x < size - this.radius; x++) {

+				int idx = y * size + x;

+				float n = 0;

+				for (int i = -this.radius; i < this.radius + 1; i++) {

+					for (int j = -this.radius; j < this.radius + 1; j++) {

+						n += data[(y + i) * size + x + j];

+					}

+				}

+				retval[idx] = this.effect * n / (4 * this.radius * (this.radius + 1) + 1) + (1 - this.effect) * data[idx];

+			}

+		}

+

+		return FloatBuffer.wrap(retval);

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/filter/ThermalErodeFilter.java b/engine/src/terrain/com/jme3/terrain/noise/filter/ThermalErodeFilter.java
new file mode 100644
index 0000000..2e49679
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/filter/ThermalErodeFilter.java
@@ -0,0 +1,101 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.filter;

+

+import java.nio.FloatBuffer;

+

+public class ThermalErodeFilter extends AbstractFilter {

+

+	private float talus;

+	private float c;

+

+	public ThermalErodeFilter setC(float c) {

+		this.c = c;

+		return this;

+	}

+

+	public ThermalErodeFilter setTalus(float talus) {

+		this.talus = talus;

+		return this;

+	}

+

+	@Override

+	public int getMargin(int size, int margin) {

+		return super.getMargin(size, margin) + 1;

+	}

+

+	@Override

+	public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) {

+		float[] ga = buffer.array();

+		float[] sa = new float[workSize * workSize];

+

+		int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 };

+

+		for (int y = 0; y < workSize; y++) {

+			for (int x = 0; x < workSize; x++) {

+				int idx = y * workSize + x;

+				ga[idx] += sa[idx];

+				sa[idx] = 0;

+

+				float[] deltas = new float[idxrel.length];

+				float deltaMax = this.talus;

+				float deltaTotal = 0;

+

+				for (int j = 0; j < idxrel.length; j++) {

+					if (idx + idxrel[j] > 0 && idx + idxrel[j] < ga.length) {

+						float dj = ga[idx] - ga[idx + idxrel[j]];

+						if (dj > this.talus) {

+							deltas[j] = dj;

+							deltaTotal += dj;

+							if (dj > deltaMax) {

+								deltaMax = dj;

+							}

+						}

+					}

+				}

+

+				for (int j = 0; j < idxrel.length; j++) {

+					if (deltas[j] != 0) {

+						float d = this.c * (deltaMax - this.talus) * deltas[j] / deltaTotal;

+						if (d > ga[idx] + sa[idx]) {

+							d = ga[idx] + sa[idx];

+						}

+						sa[idx] -= d;

+						sa[idx + idxrel[j]] += d;

+					}

+					deltas[j] = 0;

+				}

+			}

+		}

+

+		return buffer;

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/fractal/Fractal.java b/engine/src/terrain/com/jme3/terrain/noise/fractal/Fractal.java
new file mode 100644
index 0000000..9b53447
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/fractal/Fractal.java
@@ -0,0 +1,57 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.fractal;

+

+import com.jme3.terrain.noise.Basis;

+

+/**

+ * Interface for a general fractal basis.

+ * 

+ * Takes any number of basis funcions to work with and a few common parameters

+ * for noise fractals

+ * 

+ * @author Anthyon

+ * 

+ */

+public interface Fractal extends Basis {

+

+	public Fractal setOctaves(final float octaves);

+

+	public Fractal setFrequency(final float frequency);

+

+	public Fractal setRoughness(final float roughness);

+

+	public Fractal setAmplitude(final float amplitude);

+

+	public Fractal setLacunarity(final float lacunarity);

+

+	public Fractal addBasis(Basis basis);

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/fractal/FractalSum.java b/engine/src/terrain/com/jme3/terrain/noise/fractal/FractalSum.java
new file mode 100644
index 0000000..5fe5dcb
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/fractal/FractalSum.java
@@ -0,0 +1,142 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.fractal;

+

+import com.jme3.terrain.noise.Basis;

+import com.jme3.terrain.noise.ShaderUtils;

+import com.jme3.terrain.noise.basis.ImprovedNoise;

+import com.jme3.terrain.noise.basis.Noise;

+

+/**

+ * FractalSum is the simplest form of fractal functions summing up a few octaves

+ * of the noise value with an ever decreasing (0 < roughness < 1) amplitude

+ * 

+ * lacunarity = 2.0f is the classical octave distance

+ * 

+ * Note: though noise basis functions are generally designed to return value

+ * between -1..1, there sum can easily be made to extend out of this range. To

+ * handle this is up to the user.

+ * 

+ * @author Anthyon

+ * 

+ */

+public class FractalSum extends Noise implements Fractal {

+

+	private Basis basis;

+	private float lacunarity;

+	private float amplitude;

+	private float roughness;

+	private float frequency;

+	private float octaves;

+	private int maxFreq;

+

+	public FractalSum() {

+		this.basis = new ImprovedNoise();

+		this.lacunarity = 2.124367f;

+		this.amplitude = 1.0f;

+		this.roughness = 0.6f;

+		this.frequency = 1f;

+		this.setOctaves(1);

+	}

+

+	@Override

+	public float value(final float x, final float y, final float z) {

+		float total = 0;

+

+		for (float f = this.frequency, a = this.amplitude; f < this.maxFreq; f *= this.lacunarity, a *= this.roughness) {

+			total += this.basis.value(this.scale * x * f, this.scale * y * f, this.scale * z * f) * a;

+		}

+

+		return ShaderUtils.clamp(total, -1, 1);

+	}

+

+	@Override

+	public Fractal addBasis(final Basis basis) {

+		this.basis = basis;

+		return this;

+	}

+

+	public float getOctaves() {

+		return this.octaves;

+	}

+

+	@Override

+	public Fractal setOctaves(final float octaves) {

+		this.octaves = octaves;

+		this.maxFreq = 1 << (int) octaves;

+		return this;

+	}

+

+	public float getFrequency() {

+		return this.frequency;

+	}

+

+	@Override

+	public Fractal setFrequency(final float frequency) {

+		this.frequency = frequency;

+		return this;

+	}

+

+	public float getRoughness() {

+		return this.roughness;

+	}

+

+	@Override

+	public Fractal setRoughness(final float roughness) {

+		this.roughness = roughness;

+		return this;

+	}

+

+	public float getAmplitude() {

+		return this.amplitude;

+	}

+

+	@Override

+	public Fractal setAmplitude(final float amplitude) {

+		this.amplitude = amplitude;

+		return this;

+	}

+

+	public float getLacunarity() {

+		return this.lacunarity;

+	}

+

+	@Override

+	public Fractal setLacunarity(final float lacunarity) {

+		this.lacunarity = lacunarity;

+		return this;

+	}

+

+	@Override

+	public void init() {

+

+	}

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/modulator/CatRom2.java b/engine/src/terrain/com/jme3/terrain/noise/modulator/CatRom2.java
new file mode 100644
index 0000000..3f09c29
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/modulator/CatRom2.java
@@ -0,0 +1,78 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.modulator;

+

+import java.util.HashMap;

+import java.util.Map;

+

+import com.jme3.terrain.noise.ShaderUtils;

+

+public class CatRom2 implements Modulator {

+

+	private int sampleRate = 100;

+

+	private final float[] table;

+

+	private static Map<Integer, CatRom2> instances = new HashMap<Integer, CatRom2>();

+

+	public CatRom2(final int sampleRate) {

+		this.sampleRate = sampleRate;

+		this.table = new float[4 * sampleRate + 1];

+		for (int i = 0; i < 4 * sampleRate + 1; i++) {

+			float x = i / (float) sampleRate;

+			x = (float) Math.sqrt(x);

+			if (x < 1) {

+				this.table[i] = 0.5f * (2 + x * x * (-5 + x * 3));

+			} else {

+				this.table[i] = 0.5f * (4 + x * (-8 + x * (5 - x)));

+			}

+		}

+	}

+

+	public static CatRom2 getInstance(final int sampleRate) {

+		if (!CatRom2.instances.containsKey(sampleRate)) {

+			CatRom2.instances.put(sampleRate, new CatRom2(sampleRate));

+		}

+		return CatRom2.instances.get(sampleRate);

+	}

+

+	@Override

+	public float value(final float... in) {

+		if (in[0] >= 4) {

+			return 0;

+		}

+		in[0] = in[0] * this.sampleRate + 0.5f;

+		int i = ShaderUtils.floor(in[0]);

+		if (i >= 4 * this.sampleRate + 1) {

+			return 0;

+		}

+		return this.table[i];

+	}

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/modulator/Modulator.java b/engine/src/terrain/com/jme3/terrain/noise/modulator/Modulator.java
new file mode 100644
index 0000000..28bfd1c
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/modulator/Modulator.java
@@ -0,0 +1,36 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.modulator;

+

+public interface Modulator {

+

+	public float value(float... in);

+

+}

diff --git a/engine/src/terrain/com/jme3/terrain/noise/modulator/NoiseModulator.java b/engine/src/terrain/com/jme3/terrain/noise/modulator/NoiseModulator.java
new file mode 100644
index 0000000..38a3bd6
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/noise/modulator/NoiseModulator.java
@@ -0,0 +1,34 @@
+/**

+ * Copyright (c) 2011, Novyon Events

+ * 

+ * 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.

+ * 

+ * 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 HOLDER 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.

+ * 

+ * @author Anthyon

+ */

+package com.jme3.terrain.noise.modulator;

+

+public interface NoiseModulator extends Modulator {

+

+}

diff --git a/engine/src/test/jme3test/TestChooser.java b/engine/src/test/jme3test/TestChooser.java
new file mode 100644
index 0000000..3d8c1f8
--- /dev/null
+++ b/engine/src/test/jme3test/TestChooser.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2009-2010 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;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.system.JmeContext;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+
+/**
+ * Class with a main method that displays a dialog to choose any jME demo to be
+ * started.
+ */
+public class TestChooser extends JDialog {
+    private static final Logger logger = Logger.getLogger(TestChooser.class
+            .getName());
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Only accessed from EDT
+     */
+    private Object[] selectedClass = null;
+    private boolean showSetting = true;
+
+    /**
+     * Constructs a new TestChooser that is initially invisible.
+     */
+    public TestChooser() throws HeadlessException {
+        super((JFrame) null, "TestChooser");
+    }
+
+    /**
+     * @param classes
+     *            vector that receives the found classes
+     * @return classes vector, list of all the classes in a given package (must
+     *         be found in classpath).
+     */
+    protected Vector<Class> find(String pckgname, boolean recursive,
+            Vector<Class> classes) {
+        URL url;
+
+        // Translate the package name into an absolute path
+        String name = pckgname;
+        if (!name.startsWith("/")) {
+            name = "/" + name;
+        }
+        name = name.replace('.', '/');
+
+        // Get a File object for the package
+        // URL url = UPBClassLoader.get().getResource(name);
+        url = this.getClass().getResource(name);
+        // URL url = ClassLoader.getSystemClassLoader().getResource(name);
+        pckgname = pckgname + ".";
+
+        File directory;
+        try {
+            directory = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e); // should never happen
+        }
+
+        if (directory.exists()) {
+            logger.info("Searching for Demo classes in \""
+                    + directory.getName() + "\".");
+            addAllFilesInDirectory(directory, classes, pckgname, recursive);
+        } else {
+            try {
+                // It does not work with the filesystem: we must
+                // be in the case of a package contained in a jar file.
+                logger.info("Searching for Demo classes in \"" + url + "\".");
+                URLConnection urlConnection = url.openConnection();
+                if (urlConnection instanceof JarURLConnection) {
+                    JarURLConnection conn = (JarURLConnection) urlConnection;
+
+                    JarFile jfile = conn.getJarFile();
+                    Enumeration e = jfile.entries();
+                    while (e.hasMoreElements()) {
+                        ZipEntry entry = (ZipEntry) e.nextElement();
+                        Class result = load(entry.getName());
+                        if (result != null && !classes.contains(result)) {
+                            classes.add(result);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                logger.logp(Level.SEVERE, this.getClass().toString(),
+                        "find(pckgname, recursive, classes)", "Exception", e);
+            } catch (Exception e) {
+                logger.logp(Level.SEVERE, this.getClass().toString(),
+                        "find(pckgname, recursive, classes)", "Exception", e);
+            }
+        }
+        return classes;
+    }
+
+    /**
+     * Load a class specified by a file- or entry-name
+     *
+     * @param name
+     *            name of a file or entry
+     * @return class file that was denoted by the name, null if no class or does
+     *         not contain a main method
+     */
+    private Class load(String name) {
+        if (name.endsWith(".class")
+         && name.indexOf("Test") >= 0
+         && name.indexOf('$') < 0) {
+            String classname = name.substring(0, name.length()
+                    - ".class".length());
+
+            if (classname.startsWith("/")) {
+                classname = classname.substring(1);
+            }
+            classname = classname.replace('/', '.');
+
+            try {
+                final Class<?> cls = Class.forName(classname);
+                cls.getMethod("main", new Class[] { String[].class });
+                if (!getClass().equals(cls)) {
+                    return cls;
+                }
+            } catch (NoClassDefFoundError e) {
+                // class has unresolved dependencies
+                return null;
+            } catch (ClassNotFoundException e) {
+                // class not in classpath
+                return null;
+            } catch (NoSuchMethodException e) {
+                // class does not have a main method
+                return null;
+            } catch (UnsupportedClassVersionError e){
+                // unsupported version
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Used to descent in directories, loads classes via {@link #load}
+     *
+     * @param directory
+     *            where to search for class files
+     * @param allClasses
+     *            add loaded classes to this collection
+     * @param packageName
+     *            current package name for the diven directory
+     * @param recursive
+     *            true to descent into subdirectories
+     */
+    private void addAllFilesInDirectory(File directory,
+            Collection<Class> allClasses, String packageName, boolean recursive) {
+        // Get the list of the files contained in the package
+        File[] files = directory.listFiles(getFileFilter());
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
+                // we are only interested in .class files
+                if (files[i].isDirectory()) {
+                    if (recursive) {
+                        addAllFilesInDirectory(files[i], allClasses,
+                                packageName + files[i].getName() + ".", true);
+                    }
+                } else {
+                    Class result = load(packageName + files[i].getName());
+                    if (result != null && !allClasses.contains(result)) {
+                        allClasses.add(result);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @return FileFilter for searching class files (no inner classes, only
+     *         those with "Test" in the name)
+     */
+    private FileFilter getFileFilter() {
+        return new FileFilter() {
+            public boolean accept(File pathname) {
+                return (pathname.isDirectory() && !pathname.getName().startsWith("."))
+                        || (pathname.getName().endsWith(".class")
+                            && (pathname.getName().indexOf("Test") >= 0)
+                            && pathname.getName().indexOf('$') < 0);
+            }
+        };
+    }
+
+    private void startApp(final Object[] appClass){
+        if (appClass == null){
+            JOptionPane.showMessageDialog(rootPane,
+                                          "Please select a test from the list",
+                                          "Error", 
+                                          JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+            new Thread(new Runnable(){
+                public void run(){
+                    for (int i = 0; i < appClass.length; i++) {
+                	    Class<?> clazz = (Class)appClass[i];
+                		try {
+                			Object app = clazz.newInstance();
+                			if (app instanceof Application) {
+                			    if (app instanceof SimpleApplication) {
+                			        final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
+                			        settingMethod.invoke(app, showSetting);
+                			    }
+                			    final Method mainMethod = clazz.getMethod("start");
+                			    mainMethod.invoke(app);
+                			    Field contextField = Application.class.getDeclaredField("context");
+                			    contextField.setAccessible(true);
+                			    JmeContext context = null; 
+                			    while (context == null) {
+                			        context = (JmeContext) contextField.get(app);
+                			        Thread.sleep(100);
+                			    }
+                			    while (!context.isCreated()) {
+                			        Thread.sleep(100);
+                			    }
+                			    while (context.isCreated()) {
+                			        Thread.sleep(100);
+                			    }
+                			} else {
+                                final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
+                                mainMethod.invoke(app, new Object[]{new String[0]});
+                			}
+                			// wait for destroy
+                			System.gc();
+                		} catch (IllegalAccessException ex) {
+                			logger.log(Level.SEVERE, "Cannot access constructor: "+clazz.getName(), ex);
+                		} catch (IllegalArgumentException ex) {
+                			logger.log(Level.SEVERE, "main() had illegal argument: "+clazz.getName(), ex);
+                		} catch (InvocationTargetException ex) {
+                			logger.log(Level.SEVERE, "main() method had exception: "+clazz.getName(), ex);
+                		} catch (InstantiationException ex) {
+                			logger.log(Level.SEVERE, "Failed to create app: "+clazz.getName(), ex);
+                		} catch (NoSuchMethodException ex){
+                			logger.log(Level.SEVERE, "Test class doesn't have main method: "+clazz.getName(), ex);
+                		} catch (Exception ex) {
+                		    logger.log(Level.SEVERE, "Cannot start test: "+clazz.getName(), ex);
+                            ex.printStackTrace();
+                        }
+                	}
+                }
+            }).start();
+    }
+
+    /**
+     * Code to create components and action listeners.
+     *
+     * @param classes
+     *            what Classes to show in the list box
+     */
+    private void setup(Vector<Class> classes) {
+        final JPanel mainPanel = new JPanel();
+        mainPanel.setLayout(new BorderLayout());
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(mainPanel, BorderLayout.CENTER);
+        mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+
+        final FilteredJList list = new FilteredJList();
+        list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        DefaultListModel model = new DefaultListModel();
+        for (Class c : classes) {
+            model.addElement(c);
+        }
+        list.setModel(model);
+
+        mainPanel.add(createSearchPanel(list), BorderLayout.NORTH);
+        mainPanel.add(new JScrollPane(list), BorderLayout.CENTER);
+
+        list.getSelectionModel().addListSelectionListener(
+                new ListSelectionListener() {
+                    public void valueChanged(ListSelectionEvent e) {
+                        selectedClass = list.getSelectedValues();
+                    }
+                });
+        list.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2 && selectedClass != null) {
+                    startApp(selectedClass);
+                }
+            }
+        });
+        list.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyTyped(KeyEvent e) {
+                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+                    startApp(selectedClass);
+                } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                    dispose();
+                }
+            }
+        });
+
+        final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+        mainPanel.add(buttonPanel, BorderLayout.PAGE_END);
+
+        final JButton okButton = new JButton("Ok");
+        okButton.setMnemonic('O');
+        buttonPanel.add(okButton);
+        getRootPane().setDefaultButton(okButton);
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                startApp(selectedClass);
+            }
+        });
+
+        final JButton cancelButton = new JButton("Cancel");
+        cancelButton.setMnemonic('C');
+        buttonPanel.add(cancelButton);
+        cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                dispose();
+            }
+        });
+
+        pack();
+        center();
+    }
+
+    private class FilteredJList extends JList {
+        private static final long serialVersionUID = 1L;
+
+        private String filter;
+        private ListModel originalModel;
+
+        public void setModel(ListModel m) {
+            originalModel = m;
+            super.setModel(m);
+        }
+
+        private void update() {
+            if (filter == null || filter.length() == 0) {
+                super.setModel(originalModel);
+            }
+
+            DefaultListModel v = new DefaultListModel();
+            for (int i = 0; i < originalModel.getSize(); i++) {
+                Object o = originalModel.getElementAt(i);
+                String s = String.valueOf(o).toLowerCase();
+                if (s.contains(filter)) {
+                    v.addElement(o);
+                }
+            }
+            super.setModel(v);
+            if (v.getSize() == 1) {
+                setSelectedIndex(0);
+            }
+            revalidate();
+        }
+
+        public String getFilter() {
+            return filter;
+        }
+
+        public void setFilter(String filter) {
+            this.filter = filter.toLowerCase();
+            update();
+        }
+    }
+
+    /**
+     * center the frame.
+     */
+    private void center() {
+        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+        Dimension frameSize = this.getSize();
+        if (frameSize.height > screenSize.height) {
+            frameSize.height = screenSize.height;
+        }
+        if (frameSize.width > screenSize.width) {
+            frameSize.width = screenSize.width;
+        }
+        this.setLocation((screenSize.width - frameSize.width) / 2,
+                (screenSize.height - frameSize.height) / 2);
+    }
+
+    /**
+     * Start the chooser.
+     *
+     * @param args
+     *            command line parameters
+     */
+    public static void main(final String[] args) {
+        try {
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        } catch (Exception e) {
+        }
+        new TestChooser().start(args);
+    }
+
+    protected void start(String[] args) {
+        final Vector<Class> classes = new Vector<Class>();
+        logger.info("Composing Test list...");
+        addDisplayedClasses(classes);
+        setup(classes);
+        Class<?> cls;
+        setVisible(true);
+    }
+
+    protected void addDisplayedClasses(Vector<Class> classes) {
+        find("jme3test", true, classes);
+    }
+
+    private JPanel createSearchPanel(final FilteredJList classes) {
+        JPanel search = new JPanel();
+        search.setLayout(new BorderLayout());
+        search.add(new JLabel("Choose a Demo to start:      Find: "),
+                BorderLayout.WEST);
+        final javax.swing.JTextField jtf = new javax.swing.JTextField();
+        jtf.getDocument().addDocumentListener(new DocumentListener() {
+            public void removeUpdate(DocumentEvent e) {
+                classes.setFilter(jtf.getText());
+            }
+
+            public void insertUpdate(DocumentEvent e) {
+                classes.setFilter(jtf.getText());
+            }
+
+            public void changedUpdate(DocumentEvent e) {
+                classes.setFilter(jtf.getText());
+            }
+        });
+        jtf.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                selectedClass = classes.getSelectedValues();
+                startApp(selectedClass);
+            }
+        });
+        final JCheckBox showSettingCheck = new JCheckBox("Show Setting");
+        showSettingCheck.setSelected(true);
+        showSettingCheck.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                showSetting = showSettingCheck.isSelected();
+            }
+        });
+        jtf.setPreferredSize(new Dimension(100, 25));
+        search.add(jtf, BorderLayout.CENTER);
+        search.add(showSettingCheck, BorderLayout.EAST);
+        return search;
+    }
+}
diff --git a/engine/src/test/jme3test/animation/SubtitleTrack.java b/engine/src/test/jme3test/animation/SubtitleTrack.java
new file mode 100644
index 0000000..afa6e74
--- /dev/null
+++ b/engine/src/test/jme3test/animation/SubtitleTrack.java
@@ -0,0 +1,37 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package jme3test.animation;
+
+import com.jme3.cinematic.events.GuiTrack;
+import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.elements.render.TextRenderer;
+
+/**
+ *
+ * @author Nehon
+ */
+public class SubtitleTrack extends GuiTrack{
+    private String text="";
+
+    public SubtitleTrack(Nifty nifty, String screen,float initialDuration, String text) {
+        super(nifty, screen, initialDuration);
+        this.text=text;
+    }
+
+    @Override
+    public void onPlay() {
+        super.onPlay();
+        nifty.getScreen(screen).findElementByName("text").getRenderer(TextRenderer.class).setText(text);
+    }
+
+
+
+
+
+
+
+
+}
diff --git a/engine/src/test/jme3test/animation/TestCameraMotionPath.java b/engine/src/test/jme3test/animation/TestCameraMotionPath.java
new file mode 100644
index 0000000..ba9688b
--- /dev/null
+++ b/engine/src/test/jme3test/animation/TestCameraMotionPath.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009-2010 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.animation;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.SimpleApplication;
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.MotionPathListener;
+import com.jme3.cinematic.events.MotionTrack;
+import com.jme3.font.BitmapText;
+import com.jme3.input.ChaseCamera;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.CameraNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+import com.jme3.scene.shape.Box;
+
+public class TestCameraMotionPath extends SimpleApplication {
+
+    private Spatial teapot;
+    private boolean active = true;
+    private boolean playing = false;
+    private MotionPath path;
+    private MotionTrack cameraMotionControl;
+    private ChaseCamera chaser;
+    private CameraNode camNode;
+
+    public static void main(String[] args) {
+        TestCameraMotionPath app = new TestCameraMotionPath();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        createScene();
+        cam.setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f));
+        camNode = new CameraNode("Motion cam", cam);
+        camNode.setControlDir(ControlDirection.SpatialToCamera);
+        camNode.getControl(0).setEnabled(false);
+        path = new MotionPath();
+        path.setCycle(true);
+        path.addWayPoint(new Vector3f(20, 3, 0));
+        path.addWayPoint(new Vector3f(0, 3, 20));
+        path.addWayPoint(new Vector3f(-20, 3, 0));
+        path.addWayPoint(new Vector3f(0, 3, -20));
+        path.setCurveTension(0.83f);
+        path.enableDebugShape(assetManager, rootNode);
+
+        cameraMotionControl = new MotionTrack(camNode, path);
+        cameraMotionControl.setLoopMode(LoopMode.Loop);
+        //cameraMotionControl.setDuration(15f);
+        cameraMotionControl.setLookAt(teapot.getWorldTranslation(), Vector3f.UNIT_Y);
+        cameraMotionControl.setDirectionType(MotionTrack.Direction.LookAt);
+
+        rootNode.attachChild(camNode);
+
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        final BitmapText wayPointsText = new BitmapText(guiFont, false);
+        wayPointsText.setSize(guiFont.getCharSet().getRenderedSize());
+
+        guiNode.attachChild(wayPointsText);
+
+        path.addListener(new MotionPathListener() {
+
+            public void onWayPointReach(MotionTrack control, int wayPointIndex) {
+                if (path.getNbWayPoints() == wayPointIndex + 1) {
+                    wayPointsText.setText(control.getSpatial().getName() + " Finish!!! ");
+                } else {
+                    wayPointsText.setText(control.getSpatial().getName() + " Reached way point " + wayPointIndex);
+                }
+                wayPointsText.setLocalTranslation((cam.getWidth() - wayPointsText.getLineWidth()) / 2, cam.getHeight(), 0);
+            }
+        });
+
+        flyCam.setEnabled(false);
+        chaser = new ChaseCamera(cam, teapot);
+        chaser.registerWithInput(inputManager);
+        chaser.setSmoothMotion(true);
+        chaser.setMaxDistance(50);
+        chaser.setDefaultDistance(50);
+        initInputs();
+
+    }
+
+    private void createScene() {
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 1f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient", ColorRGBA.Black);
+        mat.setColor("Diffuse", ColorRGBA.DarkGray);
+        mat.setColor("Specular", ColorRGBA.White.mult(0.6f));
+        Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        matSoil.setBoolean("UseMaterialColors", true);
+        matSoil.setColor("Ambient", ColorRGBA.Gray);
+        matSoil.setColor("Diffuse", ColorRGBA.Gray);
+        matSoil.setColor("Specular", ColorRGBA.Black);
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalScale(3);
+        teapot.setMaterial(mat);
+
+
+
+        rootNode.attachChild(teapot);
+        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -1.0f, 0), 50, 1, 50));
+        soil.setMaterial(matSoil);
+        rootNode.attachChild(soil);
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection(new Vector3f(0, -1, 0).normalizeLocal());
+        light.setColor(ColorRGBA.White.mult(1.5f));
+        rootNode.addLight(light);
+    }
+
+    private void initInputs() {
+        inputManager.addMapping("display_hidePath", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addMapping("SwitchPathInterpolation", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("tensionUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("tensionDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("play_stop", new KeyTrigger(KeyInput.KEY_SPACE));
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("display_hidePath") && keyPressed) {
+                    if (active) {
+                        active = false;
+                        path.disableDebugShape();
+                    } else {
+                        active = true;
+                        path.enableDebugShape(assetManager, rootNode);
+                    }
+                }
+                if (name.equals("play_stop") && keyPressed) {
+                    if (playing) {
+                        playing = false;
+                        cameraMotionControl.stop();
+                        chaser.setEnabled(true);
+                        camNode.getControl(0).setEnabled(false);
+                    } else {
+                        playing = true;
+                        chaser.setEnabled(false);
+                        camNode.getControl(0).setEnabled(true);
+                        cameraMotionControl.play();
+                    }
+                }
+
+                if (name.equals("SwitchPathInterpolation") && keyPressed) {
+                    if (path.getPathSplineType() == SplineType.CatmullRom){
+                        path.setPathSplineType(SplineType.Linear);
+                    } else {
+                        path.setPathSplineType(SplineType.CatmullRom);
+                    }
+                }
+
+                if (name.equals("tensionUp") && keyPressed) {
+                    path.setCurveTension(path.getCurveTension() + 0.1f);
+                    System.err.println("Tension : " + path.getCurveTension());
+                }
+                if (name.equals("tensionDown") && keyPressed) {
+                    path.setCurveTension(path.getCurveTension() - 0.1f);
+                    System.err.println("Tension : " + path.getCurveTension());
+                }
+
+
+            }
+        };
+
+        inputManager.addListener(acl, "display_hidePath", "play_stop", "SwitchPathInterpolation", "tensionUp", "tensionDown");
+
+    }
+}
diff --git a/engine/src/test/jme3test/animation/TestCinematic.java b/engine/src/test/jme3test/animation/TestCinematic.java
new file mode 100644
index 0000000..a213828
--- /dev/null
+++ b/engine/src/test/jme3test/animation/TestCinematic.java
@@ -0,0 +1,336 @@
+/*

+ * Copyright (c) 2009-2010 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.animation;

+

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.AnimationFactory;

+import com.jme3.animation.LoopMode;

+import com.jme3.app.SimpleApplication;

+import com.jme3.cinematic.Cinematic;

+import com.jme3.cinematic.MotionPath;

+import com.jme3.cinematic.PlayState;

+import com.jme3.cinematic.events.*;

+import com.jme3.font.BitmapText;

+import com.jme3.input.ChaseCamera;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.KeyTrigger;

+import com.jme3.light.DirectionalLight;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.FastMath;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.niftygui.NiftyJmeDisplay;

+import com.jme3.post.FilterPostProcessor;

+import com.jme3.post.filters.FadeFilter;

+import com.jme3.renderer.Caps;

+import com.jme3.renderer.queue.RenderQueue.ShadowMode;

+import com.jme3.scene.CameraNode;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.shape.Box;

+import com.jme3.shadow.PssmShadowRenderer;

+import de.lessvoid.nifty.Nifty;

+

+public class TestCinematic extends SimpleApplication {

+

+    private Spatial model;

+    private Spatial teapot;

+    private MotionPath path;

+    private MotionTrack cameraMotionTrack;

+    private Cinematic cinematic;

+    private ChaseCamera chaseCam;

+    private FilterPostProcessor fpp;

+    private FadeFilter fade;

+    private float time = 0;

+

+    public static void main(String[] args) {

+        TestCinematic app = new TestCinematic();

+        app.start();

+

+

+

+    }

+

+    @Override

+    public void simpleInitApp() {

+        //just some text

+        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(getAssetManager(),

+                getInputManager(),

+                getAudioRenderer(),

+                getGuiViewPort());

+        Nifty nifty;

+        nifty = niftyDisplay.getNifty();

+        nifty.fromXmlWithoutStartScreen("Interface/Nifty/CinematicTest.xml");

+        getGuiViewPort().addProcessor(niftyDisplay);

+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

+        final BitmapText text = new BitmapText(guiFont, false);

+        text.setSize(guiFont.getCharSet().getRenderedSize());

+        text.setText("Press enter to play/pause cinematic");

+        text.setLocalTranslation((cam.getWidth() - text.getLineWidth()) / 2, cam.getHeight(), 0);

+        guiNode.attachChild(text);

+

+

+        createScene();

+

+        cinematic = new Cinematic(rootNode, 20);

+        stateManager.attach(cinematic);

+

+        createCameraMotion();

+

+        //creating spatial animation for the teapot

+        AnimationFactory factory = new AnimationFactory(20, "teapotAnim");

+        factory.addTimeTranslation(0, new Vector3f(10, 0, 10));

+        factory.addTimeTranslation(20, new Vector3f(10, 0, -10));

+        factory.addTimeScale(10, new Vector3f(4, 4, 4));

+        factory.addTimeScale(20, new Vector3f(1, 1, 1));

+        factory.addTimeRotationAngles(20, 0, 4 * FastMath.TWO_PI, 0);

+        AnimControl control = new AnimControl();

+        control.addAnim(factory.buildAnimation());

+        teapot.addControl(control);

+

+        //fade in

+        cinematic.addCinematicEvent(0, new FadeEvent(true));

+       // cinematic.activateCamera(0, "aroundCam");

+        cinematic.addCinematicEvent(0, new AnimationTrack(teapot, "teapotAnim", LoopMode.DontLoop));

+        cinematic.addCinematicEvent(0, cameraMotionTrack);

+        cinematic.addCinematicEvent(0, new SoundTrack("Sound/Environment/Nature.ogg", LoopMode.Loop));

+        cinematic.addCinematicEvent(3f, new SoundTrack("Sound/Effects/kick.wav"));

+        cinematic.addCinematicEvent(3, new SubtitleTrack(nifty, "start", 3, "jMonkey engine really kicks A..."));

+        cinematic.addCinematicEvent(5.1f, new SoundTrack("Sound/Effects/Beep.ogg", 1));

+        cinematic.addCinematicEvent(2, new AnimationTrack(model, "Walk", LoopMode.Loop));

+        cinematic.activateCamera(0, "topView");

+      //  cinematic.activateCamera(10, "aroundCam");

+

+        //fade out

+        cinematic.addCinematicEvent(19, new FadeEvent(false));

+//        cinematic.addCinematicEvent(19, new AbstractCinematicEvent() {

+//

+//            @Override

+//            public void onPlay() {

+//                fade.setDuration(1f / cinematic.getSpeed());

+//                fade.fadeOut();

+//

+//            }

+//

+//            @Override

+//            public void onUpdate(float tpf) {

+//            }

+//

+//            @Override

+//            public void onStop() {

+//            }

+//

+//            @Override

+//            public void onPause() {

+//            }

+//        });

+

+        cinematic.addListener(new CinematicEventListener() {

+

+            public void onPlay(CinematicEvent cinematic) {

+                chaseCam.setEnabled(false);

+                System.out.println("play");

+            }

+

+            public void onPause(CinematicEvent cinematic) {

+                System.out.println("pause");

+            }

+

+            public void onStop(CinematicEvent cinematic) {

+                chaseCam.setEnabled(true);

+                fade.setValue(1);

+                System.out.println("stop");

+            }

+        });

+

+        //cinematic.setSpeed(2);

+        flyCam.setEnabled(false);

+        chaseCam = new ChaseCamera(cam, model, inputManager);

+        initInputs();

+

+    }

+

+    private void createCameraMotion() {

+

+        CameraNode camNode = cinematic.bindCamera("topView", cam);

+        camNode.setLocalTranslation(new Vector3f(0, 50, 0));

+        camNode.lookAt(teapot.getLocalTranslation(), Vector3f.UNIT_Y);

+

+        CameraNode camNode2 = cinematic.bindCamera("aroundCam", cam);

+        path = new MotionPath();

+        path.setCycle(true);

+        path.addWayPoint(new Vector3f(20, 3, 0));

+        path.addWayPoint(new Vector3f(0, 3, 20));

+        path.addWayPoint(new Vector3f(-20, 3, 0));

+        path.addWayPoint(new Vector3f(0, 3, -20));

+        path.setCurveTension(0.83f);

+        cameraMotionTrack = new MotionTrack(camNode2, path);

+        cameraMotionTrack.setLoopMode(LoopMode.Loop);

+        cameraMotionTrack.setLookAt(model.getWorldTranslation(), Vector3f.UNIT_Y);

+        cameraMotionTrack.setDirectionType(MotionTrack.Direction.LookAt);

+

+    }

+

+    private void createScene() {

+

+        model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml");

+        model.center();

+        model.setShadowMode(ShadowMode.CastAndReceive);

+        rootNode.attachChild(model);

+

+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+        mat.setColor("Color", ColorRGBA.Cyan);

+

+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");

+        teapot.setLocalTranslation(10, 0, 10);

+        teapot.setMaterial(mat);

+        teapot.setShadowMode(ShadowMode.CastAndReceive);

+        rootNode.attachChild(teapot);

+

+        Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");

+        matSoil.setBoolean("UseMaterialColors", true);

+        matSoil.setColor("Ambient", ColorRGBA.Gray);

+        matSoil.setColor("Diffuse", ColorRGBA.Green);

+        matSoil.setColor("Specular", ColorRGBA.Black);

+

+        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -6.0f, 0), 50, 1, 50));

+        soil.setMaterial(matSoil);

+        soil.setShadowMode(ShadowMode.Receive);

+        rootNode.attachChild(soil);

+        DirectionalLight light = new DirectionalLight();

+        light.setDirection(new Vector3f(0, -1, -1).normalizeLocal());

+        light.setColor(ColorRGBA.White.mult(1.5f));

+        rootNode.addLight(light);

+

+        fpp = new FilterPostProcessor(assetManager);

+        fade = new FadeFilter();

+        fpp.addFilter(fade);

+

+        if (renderer.getCaps().contains(Caps.GLSL100)) {

+            PssmShadowRenderer pssm = new PssmShadowRenderer(assetManager, 512, 1);

+            pssm.setDirection(new Vector3f(0, -1, -1).normalizeLocal());

+            pssm.setShadowIntensity(0.4f);

+            viewPort.addProcessor(pssm);

+            viewPort.addProcessor(fpp);

+        }

+    }

+

+    private void initInputs() {

+        inputManager.addMapping("togglePause", new KeyTrigger(keyInput.KEY_RETURN));

+        inputManager.addMapping("navFwd", new KeyTrigger(keyInput.KEY_RIGHT));

+        inputManager.addMapping("navBack", new KeyTrigger(keyInput.KEY_LEFT));

+        ActionListener acl = new ActionListener() {

+

+            public void onAction(String name, boolean keyPressed, float tpf) {

+                if (name.equals("togglePause") && keyPressed) {

+                    if (cinematic.getPlayState() == PlayState.Playing) {

+                        cinematic.pause();

+                        time = cinematic.getTime();

+                    } else {

+                        cinematic.play();

+                    }

+                }

+

+                if (cinematic.getPlayState() != PlayState.Playing) {

+                    if (name.equals("navFwd") && keyPressed) {

+                        time += 0.25;

+                        FastMath.clamp(time, 0, cinematic.getInitialDuration());

+                        cinematic.setTime(time);

+                    }

+                    if (name.equals("navBack") && keyPressed) {

+                        time -= 0.25;

+                        FastMath.clamp(time, 0, cinematic.getInitialDuration());

+                        cinematic.setTime(time);

+                    }

+

+                }

+            }

+        };

+        inputManager.addListener(acl, "togglePause", "navFwd", "navBack");

+    }

+

+    private class FadeEvent extends AbstractCinematicEvent {

+

+        boolean in = true;

+        float value = 0;

+

+        public FadeEvent(boolean in) {

+            super(1);

+            this.in = in;

+            value = in ? 0 : 1;

+        }

+

+        @Override

+        public void onPlay() {

+

+            fade.setDuration(1f / cinematic.getSpeed());

+            if (in) {

+                fade.fadeIn();

+            } else {

+                fade.fadeOut();

+            }

+            fade.setValue(value);

+

+        }

+

+        @Override

+        public void setTime(float time) {

+            super.setTime(time);

+            if (time >= fade.getDuration()) {

+                value = in ? 1 : 0;

+                fade.setValue(value);

+            } else {

+                value = time;

+                if (in) {

+                    fade.setValue(time / cinematic.getSpeed());

+                } else {

+                    fade.setValue(1 - time / cinematic.getSpeed());

+                }

+            }

+        }

+

+        @Override

+        public void onUpdate(float tpf) {

+        }

+

+        @Override

+        public void onStop() {

+        }

+

+        @Override

+        public void onPause() {

+            value = fade.getValue();

+            fade.pause();

+        }

+    }

+}

diff --git a/engine/src/test/jme3test/animation/TestMotionPath.java b/engine/src/test/jme3test/animation/TestMotionPath.java
new file mode 100644
index 0000000..50cafb2
--- /dev/null
+++ b/engine/src/test/jme3test/animation/TestMotionPath.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2009-2010 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.animation;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.MotionPathListener;
+import com.jme3.cinematic.events.MotionTrack;
+import com.jme3.font.BitmapText;
+import com.jme3.input.ChaseCamera;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+
+public class TestMotionPath extends SimpleApplication {
+
+    private Spatial teapot;
+    private boolean active = true;
+    private boolean playing = false;
+    private MotionPath path;
+    private MotionTrack motionControl;
+
+    public static void main(String[] args) {
+        TestMotionPath app = new TestMotionPath();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        createScene();
+        cam.setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f));
+        path = new MotionPath();
+        path.addWayPoint(new Vector3f(10, 3, 0));
+        path.addWayPoint(new Vector3f(10, 3, 10));
+        path.addWayPoint(new Vector3f(-40, 3, 10));
+        path.addWayPoint(new Vector3f(-40, 3, 0));
+        path.addWayPoint(new Vector3f(-40, 8, 0));
+        path.addWayPoint(new Vector3f(10, 8, 0));
+        path.addWayPoint(new Vector3f(10, 8, 10));
+        path.addWayPoint(new Vector3f(15, 8, 10));
+        path.enableDebugShape(assetManager, rootNode);
+
+        motionControl = new MotionTrack(teapot,path);
+        motionControl.setDirectionType(MotionTrack.Direction.PathAndRotation);
+        motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y));
+        motionControl.setInitialDuration(10f);
+        motionControl.setSpeed(2f);
+
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        final BitmapText wayPointsText = new BitmapText(guiFont, false);
+        wayPointsText.setSize(guiFont.getCharSet().getRenderedSize());
+
+        guiNode.attachChild(wayPointsText);
+
+        path.addListener(new MotionPathListener() {
+
+            public void onWayPointReach(MotionTrack control, int wayPointIndex) {
+                if (path.getNbWayPoints() == wayPointIndex + 1) {
+                    wayPointsText.setText(control.getSpatial().getName() + "Finished!!! ");
+                } else {
+                    wayPointsText.setText(control.getSpatial().getName() + " Reached way point " + wayPointIndex);
+                }
+                wayPointsText.setLocalTranslation((cam.getWidth() - wayPointsText.getLineWidth()) / 2, cam.getHeight(), 0);
+            }
+        });
+
+        flyCam.setEnabled(false);
+        ChaseCamera chaser = new ChaseCamera(cam, teapot);
+
+        // chaser.setEnabled(false);
+        chaser.registerWithInput(inputManager);
+        initInputs();
+
+    }
+
+    private void createScene() {
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 1f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient", ColorRGBA.Black);
+        mat.setColor("Diffuse", ColorRGBA.DarkGray);
+        mat.setColor("Specular", ColorRGBA.White.mult(0.6f));
+        Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        matSoil.setBoolean("UseMaterialColors", true);
+        matSoil.setColor("Ambient", ColorRGBA.Black);
+        matSoil.setColor("Diffuse", ColorRGBA.Black);
+        matSoil.setColor("Specular", ColorRGBA.Black);
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setName("Teapot");
+        teapot.setLocalScale(3);
+        teapot.setMaterial(mat);
+
+
+        rootNode.attachChild(teapot);
+        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -1.0f, 0), 50, 1, 50));
+        soil.setMaterial(matSoil);
+
+        rootNode.attachChild(soil);
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection(new Vector3f(0, -1, 0).normalizeLocal());
+        light.setColor(ColorRGBA.White.mult(1.5f));
+        rootNode.addLight(light);
+    }
+
+    private void initInputs() {
+        inputManager.addMapping("display_hidePath", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addMapping("SwitchPathInterpolation", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("tensionUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("tensionDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("play_stop", new KeyTrigger(KeyInput.KEY_SPACE));
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("display_hidePath") && keyPressed) {
+                    if (active) {
+                        active = false;
+                        path.disableDebugShape();
+                    } else {
+                        active = true;
+                        path.enableDebugShape(assetManager, rootNode);
+                    }
+                }
+                if (name.equals("play_stop") && keyPressed) {
+                    if (playing) {
+                        playing = false;
+                        motionControl.stop();
+                    } else {
+                        playing = true;
+                        motionControl.play();
+                    }
+                }
+
+                if (name.equals("SwitchPathInterpolation") && keyPressed) {
+                    if (path.getPathSplineType() == SplineType.CatmullRom){
+                        path.setPathSplineType(SplineType.Linear);
+                    } else {
+                        path.setPathSplineType(SplineType.CatmullRom);
+                    }
+                }
+
+                if (name.equals("tensionUp") && keyPressed) {
+                    path.setCurveTension(path.getCurveTension() + 0.1f);
+                    System.err.println("Tension : " + path.getCurveTension());
+                }
+                if (name.equals("tensionDown") && keyPressed) {
+                    path.setCurveTension(path.getCurveTension() - 0.1f);
+                    System.err.println("Tension : " + path.getCurveTension());
+                }
+
+
+            }
+        };
+
+        inputManager.addListener(acl, "display_hidePath", "play_stop", "SwitchPathInterpolation", "tensionUp", "tensionDown");
+
+    }
+}
diff --git a/engine/src/test/jme3test/app/TestApplication.java b/engine/src/test/jme3test/app/TestApplication.java
new file mode 100644
index 0000000..ae07f8a
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestApplication.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import com.jme3.app.Application;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext.Type;
+
+/**
+ * Test Application functionality, such as create, restart, destroy, etc.
+ * @author Kirill
+ */
+public class TestApplication {
+
+    public static void main(String[] args) throws InterruptedException{
+        System.out.println("Creating application..");
+        Application app = new Application();
+        System.out.println("Starting application in LWJGL mode..");
+        app.start();
+        System.out.println("Waiting 5 seconds");
+        Thread.sleep(5000);
+        System.out.println("Closing application..");
+        app.stop();
+
+        Thread.sleep(2000);
+        System.out.println("Starting in fullscreen mode");
+        app = new Application();
+        AppSettings settings = new AppSettings(true);
+        settings.setFullscreen(true);
+        settings.setResolution(-1,-1); // current width/height
+        app.setSettings(settings);
+        app.start();
+        Thread.sleep(5000);
+        app.stop();
+
+        Thread.sleep(2000);
+        System.out.println("Creating offscreen buffer application");
+        app = new Application();
+        app.start(Type.OffscreenSurface);
+        Thread.sleep(3000);
+        System.out.println("Destroying offscreen buffer");
+        app.stop();
+    }
+
+}
diff --git a/engine/src/test/jme3test/app/TestBareBonesApp.java b/engine/src/test/jme3test/app/TestBareBonesApp.java
new file mode 100644
index 0000000..ee46388
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestBareBonesApp.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import com.jme3.app.Application;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+/**
+ * Test a bare-bones application, without SimpleApplication.
+ */
+public class TestBareBonesApp extends Application {
+
+    private Geometry boxGeom;
+
+    public static void main(String[] args){
+        TestBareBonesApp app = new TestBareBonesApp();
+        app.start();
+    }
+
+    @Override
+    public void initialize(){
+        super.initialize();
+
+        System.out.println("Initialize");
+
+        // create a box
+        boxGeom = new Geometry("Box", new Box(Vector3f.ZERO, 2, 2, 2));
+
+        // load some default material
+        boxGeom.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m"));
+
+        // attach box to display in primary viewport
+        viewPort.attachScene(boxGeom);
+    }
+
+    @Override
+    public void update(){
+        super.update();
+
+        // do some animation
+        float tpf = timer.getTimePerFrame();
+        boxGeom.rotate(tpf * 2, tpf * 4, tpf * 3);
+        
+        // dont forget to update the scenes
+        boxGeom.updateLogicalState(tpf);
+        boxGeom.updateGeometricState();
+
+        // render the viewports
+        renderManager.render(tpf, context.isRenderable());
+    }
+
+    @Override
+    public void destroy(){
+        super.destroy();
+
+        System.out.println("Destroy");
+    }
+}
diff --git a/engine/src/test/jme3test/app/TestChangeAppIcon.java b/engine/src/test/jme3test/app/TestChangeAppIcon.java
new file mode 100644
index 0000000..004bd32
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestChangeAppIcon.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.app;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.system.AppSettings;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.logging.Logger;
+import javax.imageio.ImageIO;
+
+public class TestChangeAppIcon extends SimpleApplication {
+
+    private static final Logger log=Logger.getLogger(TestChangeAppIcon.class.getName());
+
+    public static void main(String[] args) {
+        TestChangeAppIcon app = new TestChangeAppIcon();
+        AppSettings settings = new AppSettings(true);
+
+        try {
+            Class<TestChangeAppIcon> clazz = TestChangeAppIcon.class;
+
+            settings.setIcons(new BufferedImage[]{
+                        ImageIO.read(clazz.getResourceAsStream("/Interface/icons/SmartMonkey256.png")),
+                        ImageIO.read(clazz.getResourceAsStream("/Interface/icons/SmartMonkey128.png")),
+                        ImageIO.read(clazz.getResourceAsStream("/Interface/icons/SmartMonkey32.png")),
+                        ImageIO.read(clazz.getResourceAsStream("/Interface/icons/SmartMonkey16.png")),
+                    });
+        } catch (IOException e) {
+            log.log(java.util.logging.Level.WARNING, "Unable to load program icons", e);
+        }
+        app.setSettings(settings);
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // Write text on the screen (HUD)
+        guiNode.detachAllChildren();
+        BitmapText helloText = new BitmapText(guiFont);
+        helloText.setText("The icon of the app should be a smart monkey!");
+        helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
+        guiNode.attachChild(helloText);
+    }
+}
diff --git a/engine/src/test/jme3test/app/TestContextRestart.java b/engine/src/test/jme3test/app/TestContextRestart.java
new file mode 100644
index 0000000..b83f336
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestContextRestart.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import com.jme3.app.Application;
+import com.jme3.system.AppSettings;
+
+public class TestContextRestart {
+
+    public static void main(String[] args) throws InterruptedException{
+        AppSettings settings = new AppSettings(true);
+
+        final Application app = new Application();
+        app.setSettings(settings);
+        app.start();
+
+        Thread.sleep(3000);
+
+        settings.setFullscreen(true);
+        settings.setResolution(-1, -1);
+        app.setSettings(settings);
+        app.restart();
+
+        Thread.sleep(3000);
+
+        app.stop();
+    }
+
+}
diff --git a/engine/src/test/jme3test/app/TestIDList.java b/engine/src/test/jme3test/app/TestIDList.java
new file mode 100644
index 0000000..4e33eaa
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestIDList.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import com.jme3.renderer.IDList;
+import java.util.*;
+
+public class TestIDList {
+
+    static class StateCol {
+
+        static Random rand = new Random();
+
+        Map<Integer, Object> objs = new HashMap<Integer, Object>();
+
+        public StateCol(){
+            // populate with free ids
+            List<Integer> freeIds = new ArrayList();
+            for (int i = 0; i < 16; i++){
+                freeIds.add(i);
+            }
+
+            // create random
+            int numStates = rand.nextInt(6) + 1;
+            for (int i = 0; i < numStates; i++){
+                // remove a random id from free id list
+                int idx = rand.nextInt(freeIds.size());
+                int id = freeIds.remove(idx);
+
+                objs.put(id, new Object());
+            }
+        }
+
+        public void print(){
+            System.out.println("-----------------");
+
+            Set<Integer> keys = objs.keySet();
+            Integer[] keysArr = keys.toArray(new Integer[0]);
+            Arrays.sort(keysArr);
+            for (int i = 0; i < keysArr.length; i++){
+                System.out.println(keysArr[i]+" => "+objs.get(keysArr[i]).hashCode());
+            }
+        }
+
+    }
+
+    static IDList list = new IDList();
+    static int boundSlot = 0;
+    
+    static Object[] slots = new Object[16];
+    static boolean[] enabledSlots = new boolean[16];
+
+    static void enable(int slot){
+        System.out.println("Enabled SLOT["+slot+"]");
+        if (enabledSlots[slot] == true){
+            System.err.println("FAIL! Extra state change");
+        }
+        enabledSlots[slot] = true;
+    }
+
+    static void disable(int slot){
+        System.out.println("Disabled SLOT["+slot+"]");
+        if (enabledSlots[slot] == false){
+            System.err.println("FAIL! Extra state change");
+        }
+        enabledSlots[slot] = false;
+    }
+
+    static void setSlot(int slot, Object val){
+        if (!list.moveToNew(slot)){
+            enable(slot);
+        }
+        if (slots[slot] != val){
+            System.out.println("SLOT["+slot+"] = "+val.hashCode());
+            slots[slot] = val;
+        }
+    }
+
+    static void checkSlots(StateCol state){
+        for (int i = 0; i < 16; i++){
+            if (slots[i] != null && enabledSlots[i] == false){
+                System.err.println("FAIL! SLOT["+i+"] assigned, but disabled");
+            }
+            if (slots[i] == null && enabledSlots[i] == true){
+                System.err.println("FAIL! SLOT["+i+"] enabled, but not assigned");
+            }
+
+            Object val = state.objs.get(i);
+            if (val != null){
+                if (slots[i] != val)
+                    System.err.println("FAIL! SLOT["+i+"] does not contain correct value");
+                if (!enabledSlots[i])
+                    System.err.println("FAIL! SLOT["+i+"] is not enabled");
+            }else{
+                if (slots[i] != null)
+                    System.err.println("FAIL! SLOT["+i+"] is not set");
+                if (enabledSlots[i])
+                    System.err.println("FAIL! SLOT["+i+"] is enabled");
+            }
+        }
+    }
+
+    static void clearSlots(){
+        for (int i = 0; i < list.oldLen; i++){
+            int slot = list.oldList[i];
+            disable(slot);
+            slots[slot] = null;
+        }
+        list.copyNewToOld();
+//        context.attribIndexList.print();
+    }
+    
+    static void setState(StateCol state){
+        state.print();
+        for (Map.Entry<Integer, Object> entry : state.objs.entrySet()){
+            setSlot(entry.getKey(), entry.getValue());
+        }
+        clearSlots();
+        checkSlots(state);
+    }
+
+    public static void main(String[] args){
+        StateCol[] states = new StateCol[20];
+        for (int i = 0; i < states.length; i++)
+            states[i] = new StateCol();
+
+        // shuffle would be useful here..
+
+        for (int i = 0; i < states.length; i++){
+            setState(states[i]);
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/app/TestReleaseDirectMemory.java b/engine/src/test/jme3test/app/TestReleaseDirectMemory.java
new file mode 100644
index 0000000..84a58ae
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestReleaseDirectMemory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.app;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+public class TestReleaseDirectMemory extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestReleaseDirectMemory app = new TestReleaseDirectMemory();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        ByteBuffer buf = BufferUtils.createByteBuffer(500000);
+        BufferUtils.destroyDirectBuffer(buf);
+        
+        FloatBuffer buf2 = BufferUtils.createFloatBuffer(500000);
+        BufferUtils.destroyDirectBuffer(buf2);
+    }
+    
+}
diff --git a/engine/src/test/jme3test/app/TestTempVars.java b/engine/src/test/jme3test/app/TestTempVars.java
new file mode 100644
index 0000000..7102888
--- /dev/null
+++ b/engine/src/test/jme3test/app/TestTempVars.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.app;
+
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+
+public class TestTempVars {
+
+    private static final int ITERATIONS = 10000000;
+    private static final int NANOS_TO_MS = 1000000;
+    
+    private static final Vector3f sumCompute = new Vector3f();
+    
+    public static void main(String[] args) {
+        long milliseconds, nanos;
+        
+        for (int i = 0; i < 4; i++){
+            System.gc();
+        }
+        
+//        sumCompute.set(0, 0, 0);
+//        long nanos = System.nanoTime();
+//        for (int i = 0; i < ITERATIONS; i++) {
+//            recursiveMethod(0);
+//        }
+//        long milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS;
+//        System.out.println("100 million TempVars calls with 5 recursions: " + milliseconds + " ms");
+//        System.out.println(sumCompute);
+        
+        sumCompute.set(0, 0, 0);
+        nanos = System.nanoTime();
+        for (int i = 0; i < ITERATIONS; i++) {
+            methodThatUsesTempVars();
+        }
+        milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS;
+        System.out.println("100 million TempVars calls: " + milliseconds + " ms");
+        System.out.println(sumCompute);
+
+        sumCompute.set(0, 0, 0);
+        nanos = System.nanoTime();
+        for (int i = 0; i < ITERATIONS; i++) {
+            methodThatUsesAllocation();
+        }
+        milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS;
+        System.out.println("100 million allocation calls: " + milliseconds + " ms");
+        System.out.println(sumCompute);
+        
+        nanos = System.nanoTime();
+        for (int i = 0; i < 10; i++){
+            System.gc();
+        }
+        milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS;
+        System.out.println("cleanup time after allocation calls: " + milliseconds + " ms");
+    }
+
+    public static void methodThatUsesAllocation(){
+        Vector3f vector = new Vector3f();
+        vector.set(0.1f, 0.2f, 0.3f);
+        sumCompute.addLocal(vector);
+    }
+    
+    public static void recursiveMethod(int recurse) {
+        TempVars vars = TempVars.get();
+        {
+            vars.vect1.set(0.1f, 0.2f, 0.3f);
+
+            if (recurse < 4) {
+                recursiveMethod(recurse + 1);
+            }
+
+            sumCompute.addLocal(vars.vect1);
+        }
+        vars.release();
+    }
+
+    public static void methodThatUsesTempVars() {
+        TempVars vars = TempVars.get();
+        {
+            vars.vect1.set(0.1f, 0.2f, 0.3f);
+            sumCompute.addLocal(vars.vect1);
+        }
+        vars.release();
+    }
+}
diff --git a/engine/src/test/jme3test/app/state/RootNodeState.java b/engine/src/test/jme3test/app/state/RootNodeState.java
new file mode 100644
index 0000000..fddb63a
--- /dev/null
+++ b/engine/src/test/jme3test/app/state/RootNodeState.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.state;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.scene.Node;
+
+public class RootNodeState extends AbstractAppState {
+
+    private Node rootNode = new Node("Root Node");
+
+    public Node getRootNode(){
+        return rootNode;
+    }
+
+    @Override
+    public void update(float tpf) {
+        super.update(tpf);
+
+        rootNode.updateLogicalState(tpf);
+        rootNode.updateGeometricState();
+    }
+
+}
diff --git a/engine/src/test/jme3test/app/state/TestAppStates.java b/engine/src/test/jme3test/app/state/TestAppStates.java
new file mode 100644
index 0000000..721bc04
--- /dev/null
+++ b/engine/src/test/jme3test/app/state/TestAppStates.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 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.state;
+
+import com.jme3.app.Application;
+import com.jme3.niftygui.NiftyJmeDisplay;
+import com.jme3.scene.Spatial;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+
+public class TestAppStates extends Application {
+
+    public static void main(String[] args){
+        TestAppStates app = new TestAppStates();
+        app.start();
+    }
+
+    @Override
+    public void start(JmeContext.Type contextType){
+        AppSettings settings = new AppSettings(true);
+        settings.setResolution(1024, 768);
+        setSettings(settings);
+        
+        super.start(contextType);
+    }
+
+    @Override
+    public void initialize(){
+        super.initialize();
+
+        System.out.println("Initialize");
+
+        RootNodeState state = new RootNodeState();
+        viewPort.attachScene(state.getRootNode());
+        stateManager.attach(state);
+
+        Spatial model = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        model.scale(3);
+        model.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m"));
+        state.getRootNode().attachChild(model);
+
+        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
+                                                           inputManager,
+                                                           audioRenderer,
+                                                           guiViewPort);
+        niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start");
+        guiViewPort.addProcessor(niftyDisplay);
+    }
+
+    @Override
+    public void update(){
+        super.update();
+
+        // do some animation
+        float tpf = timer.getTimePerFrame();
+
+        stateManager.update(tpf);
+        stateManager.render(renderManager);
+
+        // render the viewports
+        renderManager.render(tpf, context.isRenderable());
+    }
+
+    @Override
+    public void destroy(){
+        super.destroy();
+
+        System.out.println("Destroy");
+    }
+}
diff --git a/engine/src/test/jme3test/asset/TestAbsoluteLocators.java b/engine/src/test/jme3test/asset/TestAbsoluteLocators.java
new file mode 100644
index 0000000..b26a98c
--- /dev/null
+++ b/engine/src/test/jme3test/asset/TestAbsoluteLocators.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.audio.AudioData;
+import com.jme3.audio.plugins.WAVLoader;
+import com.jme3.texture.Texture;
+import com.jme3.texture.plugins.AWTLoader;
+
+public class TestAbsoluteLocators {
+    public static void main(String[] args){
+        AssetManager am = new DesktopAssetManager();
+
+        am.registerLoader(AWTLoader.class.getName(), "jpg");
+        am.registerLoader(WAVLoader.class.getName(), "wav");
+
+        // register absolute locator
+        am.registerLocator("/",  ClasspathLocator.class.getName());
+
+        // find a sound
+        AudioData audio = am.loadAudio("Sound/Effects/Gun.wav");
+
+        // find a texture
+        Texture tex = am.loadTexture("Textures/Terrain/Pond/Pond.jpg");
+
+        if (audio == null)
+            throw new RuntimeException("Cannot find audio!");
+        else
+            System.out.println("Audio loaded from Sounds/Effects/Gun.wav");
+
+        if (tex == null)
+            throw new RuntimeException("Cannot find texture!");
+        else
+            System.out.println("Texture loaded from Textures/Terrain/Pond/Pond.jpg");
+
+        System.out.println("Success!");
+    }
+}
diff --git a/engine/src/test/jme3test/asset/TestAssetCache.java b/engine/src/test/jme3test/asset/TestAssetCache.java
new file mode 100644
index 0000000..066fe21
--- /dev/null
+++ b/engine/src/test/jme3test/asset/TestAssetCache.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetCache;
+import com.jme3.asset.AssetKey;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestAssetCache {
+    
+    /**
+     * Keep references to loaded assets
+     */
+    private final static boolean KEEP_REFERENCES = false;
+    
+    /**
+     * Enable smart cache use
+     */
+    private final static boolean USE_SMART_CACHE = true;
+    
+    /**
+     * Enable cloneable asset use
+     */
+    private final static boolean CLONEABLE_ASSET = true;
+
+    private static int counter = 0;
+    
+    private static class DummyData implements Asset {
+
+        private AssetKey key;
+        private byte[] data = new byte[10000];
+
+        public byte[] getData(){
+            return data;
+        }
+        
+        public AssetKey getKey() {
+            return key;
+        }
+
+        public void setKey(AssetKey key) {
+            this.key = key;
+        }
+    }
+    
+    private static class SmartKey extends AssetKey {
+        
+        public SmartKey(){
+            super(".");
+            counter++;
+        }
+        
+        @Override
+        public int hashCode(){
+            return 0;
+        }
+        
+        @Override
+        public boolean equals(Object other){
+            return false;
+        }
+        
+        @Override
+        public boolean useSmartCache(){
+            return true;
+        }
+        
+        @Override
+        public Object createClonedInstance(Object asset){
+            DummyData data = new DummyData();
+            return data;
+        }
+    }
+    
+    private static class DumbKey extends AssetKey {
+        
+        public DumbKey(){
+            super(".");
+            counter++;
+        }
+        
+        @Override
+        public int hashCode(){
+            return 0;
+        }
+        
+        @Override
+        public boolean equals(Object other){
+            return false;
+        }
+        
+        @Override
+        public Object createClonedInstance(Object asset){
+            if (CLONEABLE_ASSET){
+                DummyData data = new DummyData();
+                return data;
+            }else{
+                return asset;
+            }
+        }
+    }
+    
+    public static void main(String[] args){
+        List<Object> refs = new ArrayList<Object>(5000);
+        
+        AssetCache cache = new AssetCache();
+        
+        System.gc();
+        System.gc();
+        System.gc();
+        System.gc();
+        
+        long memory = Runtime.getRuntime().freeMemory();
+        
+        while (true){
+            AssetKey key;
+            
+            if (USE_SMART_CACHE){
+                key = new SmartKey();
+            }else{
+                key = new DumbKey();
+            }
+            
+            DummyData data = new DummyData();
+            cache.addToCache(key, data);
+            
+            if (KEEP_REFERENCES){
+                refs.add(data);
+            }
+            
+            if ((counter % 100) == 0){
+                long newMem = Runtime.getRuntime().freeMemory();
+                System.out.println("Allocated objects: " + counter);
+                System.out.println("Allocated memory: " + ((memory - newMem)/1024) + "K" );
+                memory = newMem;
+            }
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/asset/TestManyLocators.java b/engine/src/test/jme3test/asset/TestManyLocators.java
new file mode 100644
index 0000000..a11741e
--- /dev/null
+++ b/engine/src/test/jme3test/asset/TestManyLocators.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.asset.*;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.UrlLocator;
+import com.jme3.asset.plugins.ZipLocator;
+
+public class TestManyLocators {
+    public static void main(String[] args){
+        AssetManager am = new DesktopAssetManager();
+
+        am.registerLocator("http://www.jmonkeyengine.com/wp-content/uploads/2010/09/",
+                           UrlLocator.class);
+
+        am.registerLocator("town.zip", ZipLocator.class);
+        am.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip",
+                           HttpZipLocator.class);
+        
+        
+        am.registerLocator("/", ClasspathLocator.class);
+        
+        
+
+        // Try loading from Core-Data source package
+        AssetInfo a = am.locateAsset(new AssetKey<Object>("Interface/Fonts/Default.fnt"));
+
+        // Try loading from town scene zip file
+        AssetInfo b = am.locateAsset(new ModelKey("casaamarela.jpg"));
+
+        // Try loading from wildhouse online scene zip file
+        AssetInfo c = am.locateAsset(new ModelKey("glasstile2.png"));
+
+        // Try loading directly from HTTP
+        AssetInfo d = am.locateAsset(new TextureKey("planet-2.jpg"));
+
+        if (a == null)
+            System.out.println("Failed to load from classpath");
+        else
+            System.out.println("Found classpath font: " + a.toString());
+
+        if (b == null)
+            System.out.println("Failed to load from town.zip");
+        else
+            System.out.println("Found zip image: " + b.toString());
+
+        if (c == null)
+            System.out.println("Failed to load from wildhouse.zip on googlecode.com");
+        else
+            System.out.println("Found online zip image: " + c.toString());
+
+        if (d == null)
+            System.out.println("Failed to load from HTTP");
+        else
+            System.out.println("Found HTTP showcase image: " + d.toString());
+    }
+}
diff --git a/engine/src/test/jme3test/asset/TestOnlineJar.java b/engine/src/test/jme3test/asset/TestOnlineJar.java
new file mode 100644
index 0000000..908188d
--- /dev/null
+++ b/engine/src/test/jme3test/asset/TestOnlineJar.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture;
+
+/**
+ * This tests loading a file from a jar stored online.
+ * @author Kirill Vainer
+ */
+public class TestOnlineJar extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestOnlineJar app = new TestOnlineJar();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // create a simple plane/quad
+        Quad quadMesh = new Quad(1, 1);
+        quadMesh.updateGeometry(1, 1, true);
+
+        Geometry quad = new Geometry("Textured Quad", quadMesh);
+        assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/town.zip",
+                           HttpZipLocator.class);
+
+        TextureKey key = new TextureKey("grass.jpg", false);
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", tex);
+        quad.setMaterial(mat);
+
+        float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight();
+        quad.setLocalScale(new Vector3f(aspect * 1.5f, 1.5f, 1));
+        quad.center();
+
+        rootNode.attachChild(quad);
+    }
+
+}
diff --git a/engine/src/test/jme3test/asset/TestUrlLoading.java b/engine/src/test/jme3test/asset/TestUrlLoading.java
new file mode 100644
index 0000000..6afd4df
--- /dev/null
+++ b/engine/src/test/jme3test/asset/TestUrlLoading.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.asset.plugins.UrlLocator;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture;
+
+/**
+ * Load an image and display it from the internet using the UrlLocator.
+ * @author Kirill Vainer
+ */
+public class TestUrlLoading extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestUrlLoading app = new TestUrlLoading();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // create a simple plane/quad
+        Quad quadMesh = new Quad(1, 1);
+        quadMesh.updateGeometry(1, 1, true);
+
+        Geometry quad = new Geometry("Textured Quad", quadMesh);
+
+        assetManager.registerLocator("http://www.jmonkeyengine.com/wp-content/uploads/2010/09/",
+                                UrlLocator.class);
+        TextureKey key = new TextureKey("planet-2.jpg", false);
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", tex);
+        quad.setMaterial(mat);
+
+        float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight();
+        quad.setLocalScale(new Vector3f(aspect * 1.5f, 1.5f, 1));
+        quad.center();
+
+        rootNode.attachChild(quad);
+    }
+
+}
diff --git a/engine/src/test/jme3test/audio/TestAmbient.java b/engine/src/test/jme3test/audio/TestAmbient.java
new file mode 100644
index 0000000..c6b38c2
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestAmbient.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.Environment;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+public class TestAmbient extends SimpleApplication {
+
+  private AudioNode nature, waves;
+
+  public static void main(String[] args) {
+    TestAmbient test = new TestAmbient();
+    test.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0,
+      1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f,
+      0.00f, -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f,
+      0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f};
+    Environment env = new Environment(eax);
+    audioRenderer.setEnvironment(env);
+
+    waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", false);
+    waves.setPositional(true);
+    waves.setLocalTranslation(new Vector3f(0, 0,0));
+    waves.setMaxDistance(100);
+    waves.setRefDistance(5);
+
+    nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);
+    nature.setVolume(3);
+    
+    waves.playInstance();
+    nature.play();
+    
+    // just a blue box to mark the spot
+    Box box1 = new Box(Vector3f.ZERO, .5f, .5f, .5f);
+    Geometry player = new Geometry("Player", box1);
+    Material mat1 = new Material(assetManager,
+            "Common/MatDefs/Misc/Unshaded.j3md");
+    mat1.setColor("Color", ColorRGBA.Blue);
+    player.setMaterial(mat1);
+    rootNode.attachChild(player);
+  }
+
+  @Override
+  public void simpleUpdate(float tpf) {
+  }
+}
diff --git a/engine/src/test/jme3test/audio/TestDoppler.java b/engine/src/test/jme3test/audio/TestDoppler.java
new file mode 100644
index 0000000..3368874
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestDoppler.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.Environment;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import org.lwjgl.openal.AL10;
+import org.lwjgl.openal.AL11;
+
+/**
+ * Test Doppler Effect
+ */
+public class TestDoppler extends SimpleApplication {
+
+    private AudioNode ufo;
+
+    private float x = 20, z = 0;
+    private float rate     = -0.05f;
+    private float xDist    = 20;
+    private float zDist    = 5;
+    private float angle    = FastMath.TWO_PI;
+    
+    public static void main(String[] args){
+        TestDoppler test = new TestDoppler();
+        test.start();
+    }
+
+    @Override
+    public void simpleInitApp(){
+        audioRenderer.setEnvironment(Environment.Dungeon);
+        AL10.alDistanceModel(AL11.AL_EXPONENT_DISTANCE);
+        
+        ufo  = new AudioNode(assetManager, "Sound/Effects/Beep.ogg", false);
+        ufo.setPositional(true);
+        ufo.setLooping(true);
+        ufo.setReverbEnabled(true);
+        ufo.setRefDistance(100000000);
+        ufo.setMaxDistance(100000000);
+        ufo.play();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        //float x  = (float) (Math.cos(angle) * xDist);
+        float dx = (float)  Math.sin(angle) * xDist; 
+        
+        //float z  = (float) (Math.sin(angle) * zDist);
+        float dz = (float)(-Math.cos(angle) * zDist);
+        
+        x += dx * tpf * 0.05f;
+        z += dz * tpf * 0.05f;
+        
+        angle += tpf * rate;
+        
+        if (angle > FastMath.TWO_PI){
+            angle = FastMath.TWO_PI;
+            rate = -rate;
+        }else if (angle < -0){
+            angle = -0;
+            rate = -rate;
+        }
+        
+        ufo.setVelocity(new Vector3f(dx, 0, dz));
+        ufo.setLocalTranslation(x, 0, z);
+        ufo.updateGeometricState();
+        
+        System.out.println("LOC: " + (int)x +", " + (int)z + 
+                ", VEL: " + (int)dx + ", " + (int)dz);
+    }
+
+}
diff --git a/engine/src/test/jme3test/audio/TestMusicPlayer.form b/engine/src/test/jme3test/audio/TestMusicPlayer.form
new file mode 100644
index 0000000..dced8a4
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestMusicPlayer.form
@@ -0,0 +1,117 @@
+<?xml version="1.1" encoding="UTF-8" ?>
+
+<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+  <Properties>
+    <Property name="defaultCloseOperation" type="int" value="3"/>
+  </Properties>
+  <SyntheticProperties>
+    <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+  </SyntheticProperties>
+  <Events>
+    <EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="formWindowClosing"/>
+  </Events>
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+    <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,82,0,0,1,-112"/>
+  </AuxValues>
+
+  <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+  <SubComponents>
+    <Container class="javax.swing.JPanel" name="pnlButtons">
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="Center"/>
+        </Constraint>
+      </Constraints>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"/>
+      <SubComponents>
+        <Component class="javax.swing.JSlider" name="sldVolume">
+          <Properties>
+            <Property name="majorTickSpacing" type="int" value="20"/>
+            <Property name="orientation" type="int" value="1"/>
+            <Property name="paintTicks" type="boolean" value="true"/>
+            <Property name="value" type="int" value="100"/>
+          </Properties>
+          <Events>
+            <EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="sldVolumeStateChanged"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="btnRewind">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="&lt;&lt;"/>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JButton" name="btnStop">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="[  ]"/>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnStopActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="btnPlay">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="II / &gt;"/>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnPlayActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="btnFF">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="&gt;&gt;"/>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnFFActionPerformed"/>
+          </Events>
+        </Component>
+        <Component class="javax.swing.JButton" name="btnOpen">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="Open ..."/>
+          </Properties>
+          <Events>
+            <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnOpenActionPerformed"/>
+          </Events>
+        </Component>
+      </SubComponents>
+    </Container>
+    <Container class="javax.swing.JPanel" name="pnlBar">
+      <Constraints>
+        <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+          <BorderConstraints direction="First"/>
+        </Constraint>
+      </Constraints>
+
+      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"/>
+      <SubComponents>
+        <Component class="javax.swing.JLabel" name="lblTime">
+          <Properties>
+            <Property name="text" type="java.lang.String" value="0:00-0:00"/>
+            <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+              <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+                <EmptyBorder bottom="3" left="3" right="3" top="3"/>
+              </Border>
+            </Property>
+          </Properties>
+        </Component>
+        <Component class="javax.swing.JSlider" name="sldBar">
+          <Properties>
+            <Property name="value" type="int" value="0"/>
+          </Properties>
+          <Events>
+            <EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="sldBarStateChanged"/>
+          </Events>
+        </Component>
+      </SubComponents>
+    </Container>
+  </SubComponents>
+</Form>
diff --git a/engine/src/test/jme3test/audio/TestMusicPlayer.java b/engine/src/test/jme3test/audio/TestMusicPlayer.java
new file mode 100644
index 0000000..9edafad
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestMusicPlayer.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.audio.AudioNode.Status;
+import com.jme3.audio.*;
+import com.jme3.audio.plugins.OGGLoader;
+import com.jme3.audio.plugins.WAVLoader;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeSystem;
+import java.io.*;
+import javax.swing.JFileChooser;
+
+public class TestMusicPlayer extends javax.swing.JFrame {
+
+    private AudioRenderer ar;
+    private AudioData musicData;
+    private AudioNode musicSource;
+    private float musicLength = 0;
+    private float curTime = 0;
+    private Listener listener = new Listener();
+
+    public TestMusicPlayer() {
+        initComponents();
+        setLocationRelativeTo(null);
+        initAudioPlayer();
+    }
+
+    private void initAudioPlayer(){
+        AppSettings settings = new AppSettings(true);
+        settings.setRenderer(null); // disable rendering
+        settings.setAudioRenderer("LWJGL");
+        ar = JmeSystem.newAudioRenderer(settings);
+        ar.initialize();
+        ar.setListener(listener);
+        AudioContext.setAudioRenderer(ar);
+    }
+
+    /** This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        pnlButtons = new javax.swing.JPanel();
+        sldVolume = new javax.swing.JSlider();
+        btnRewind = new javax.swing.JButton();
+        btnStop = new javax.swing.JButton();
+        btnPlay = new javax.swing.JButton();
+        btnFF = new javax.swing.JButton();
+        btnOpen = new javax.swing.JButton();
+        pnlBar = new javax.swing.JPanel();
+        lblTime = new javax.swing.JLabel();
+        sldBar = new javax.swing.JSlider();
+
+        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+        addWindowListener(new java.awt.event.WindowAdapter() {
+            public void windowClosing(java.awt.event.WindowEvent evt) {
+                formWindowClosing(evt);
+            }
+        });
+
+        pnlButtons.setLayout(new javax.swing.BoxLayout(pnlButtons, javax.swing.BoxLayout.LINE_AXIS));
+
+        sldVolume.setMajorTickSpacing(20);
+        sldVolume.setOrientation(javax.swing.JSlider.VERTICAL);
+        sldVolume.setPaintTicks(true);
+        sldVolume.setValue(100);
+        sldVolume.addChangeListener(new javax.swing.event.ChangeListener() {
+            public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                sldVolumeStateChanged(evt);
+            }
+        });
+        pnlButtons.add(sldVolume);
+
+        btnRewind.setText("<<");
+        pnlButtons.add(btnRewind);
+
+        btnStop.setText("[  ]");
+        btnStop.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                btnStopActionPerformed(evt);
+            }
+        });
+        pnlButtons.add(btnStop);
+
+        btnPlay.setText("II / >");
+        btnPlay.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                btnPlayActionPerformed(evt);
+            }
+        });
+        pnlButtons.add(btnPlay);
+
+        btnFF.setText(">>");
+        btnFF.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                btnFFActionPerformed(evt);
+            }
+        });
+        pnlButtons.add(btnFF);
+
+        btnOpen.setText("Open ...");
+        btnOpen.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                btnOpenActionPerformed(evt);
+            }
+        });
+        pnlButtons.add(btnOpen);
+
+        getContentPane().add(pnlButtons, java.awt.BorderLayout.CENTER);
+
+        pnlBar.setLayout(new javax.swing.BoxLayout(pnlBar, javax.swing.BoxLayout.LINE_AXIS));
+
+        lblTime.setText("0:00-0:00");
+        lblTime.setBorder(javax.swing.BorderFactory.createEmptyBorder(3, 3, 3, 3));
+        pnlBar.add(lblTime);
+
+        sldBar.setValue(0);
+        sldBar.addChangeListener(new javax.swing.event.ChangeListener() {
+            public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                sldBarStateChanged(evt);
+            }
+        });
+        pnlBar.add(sldBar);
+
+        getContentPane().add(pnlBar, java.awt.BorderLayout.PAGE_START);
+
+        pack();
+    }// </editor-fold>//GEN-END:initComponents
+
+    private void btnOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOpenActionPerformed
+        JFileChooser chooser = new JFileChooser();
+        chooser.setAcceptAllFileFilterUsed(true);
+        chooser.setDialogTitle("Select OGG file");
+        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        chooser.setMultiSelectionEnabled(false);
+        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
+            btnStopActionPerformed(null);
+            
+            final File selected = chooser.getSelectedFile();
+            AssetLoader loader = null;
+            if(selected.getName().endsWith(".wav")){
+                loader = new WAVLoader();
+            }else{
+                loader = new OGGLoader();
+            }
+             
+            AudioKey key = new AudioKey(selected.getName(), true, true);
+            try{
+                musicData = (AudioData) loader.load(new AssetInfo(null, key) {
+                    @Override
+                    public InputStream openStream() {
+                        try{
+                            return new FileInputStream(selected);
+                        }catch (FileNotFoundException ex){
+                            ex.printStackTrace();
+                        }
+                        return null;
+                    }
+                });
+            }catch (IOException ex){
+                ex.printStackTrace();
+            }
+
+            musicSource = new AudioNode(musicData, key);
+            musicLength = musicData.getDuration();
+            updateTime();
+        }
+    }//GEN-LAST:event_btnOpenActionPerformed
+
+    private void updateTime(){
+        int max = (int) (musicLength * 100);
+        int pos = (int) (curTime * 100);
+        sldBar.setMaximum(max);
+        sldBar.setValue(pos);
+
+        int minutesTotal = (int) (musicLength / 60);
+        int secondsTotal = (int) (musicLength % 60);
+        int minutesNow = (int) (curTime / 60);
+        int secondsNow = (int) (curTime % 60);
+        String txt = String.format("%01d:%02d-%01d:%02d", minutesNow, secondsNow,
+                                                      minutesTotal, secondsTotal);
+        lblTime.setText(txt);
+    }
+
+    private void btnPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPlayActionPerformed
+        if (musicSource == null){
+            btnOpenActionPerformed(evt);
+            return;
+        }
+
+        if (musicSource.getStatus() == Status.Playing){
+            musicSource.setPitch(1);
+            ar.pauseSource(musicSource);
+        }else{
+            musicSource.setPitch(1);
+            musicSource.play();
+        }
+    }//GEN-LAST:event_btnPlayActionPerformed
+
+    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
+        ar.cleanup();
+    }//GEN-LAST:event_formWindowClosing
+
+    private void sldVolumeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldVolumeStateChanged
+       listener.setVolume( (float) sldVolume.getValue() / 100f);
+       ar.setListener(listener);
+    }//GEN-LAST:event_sldVolumeStateChanged
+
+    private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopActionPerformed
+        if (musicSource != null){
+            musicSource.setPitch(1);
+            ar.stopSource(musicSource);
+        }
+    }//GEN-LAST:event_btnStopActionPerformed
+
+    private void btnFFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFFActionPerformed
+        if (musicSource.getStatus() == Status.Playing){
+            musicSource.setPitch(2);
+        }
+    }//GEN-LAST:event_btnFFActionPerformed
+
+    private void sldBarStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldBarStateChanged
+        if (musicSource != null && !sldBar.getValueIsAdjusting()){
+            curTime = sldBar.getValue() / 100f;
+            if (curTime < 0)
+                curTime = 0;
+            
+            musicSource.setTimeOffset(curTime);
+//            if (musicSource.getStatus() == Status.Playing){
+//                musicSource.stop();               
+//                musicSource.play();
+//            }
+            updateTime();
+        }
+    }//GEN-LAST:event_sldBarStateChanged
+
+    /**
+    * @param args the command line arguments
+    */
+    public static void main(String args[]) {
+        java.awt.EventQueue.invokeLater(new Runnable() {
+            public void run() {
+                new TestMusicPlayer().setVisible(true);
+            }
+        });
+    }
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JButton btnFF;
+    private javax.swing.JButton btnOpen;
+    private javax.swing.JButton btnPlay;
+    private javax.swing.JButton btnRewind;
+    private javax.swing.JButton btnStop;
+    private javax.swing.JLabel lblTime;
+    private javax.swing.JPanel pnlBar;
+    private javax.swing.JPanel pnlButtons;
+    private javax.swing.JSlider sldBar;
+    private javax.swing.JSlider sldVolume;
+    // End of variables declaration//GEN-END:variables
+
+}
diff --git a/engine/src/test/jme3test/audio/TestMusicStreaming.java b/engine/src/test/jme3test/audio/TestMusicStreaming.java
new file mode 100644
index 0000000..49159af
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestMusicStreaming.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.UrlLocator;
+import com.jme3.audio.AudioNode;
+
+public class TestMusicStreaming extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestMusicStreaming test = new TestMusicStreaming();
+        test.start();
+    }
+
+    @Override
+    public void simpleInitApp(){
+        assetManager.registerLocator("http://www.vorbis.com/music/", UrlLocator.class);
+        AudioNode audioSource = new AudioNode(assetManager, "Lumme-Badloop.ogg", true);
+        audioSource.play();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){}
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/audio/TestOgg.java b/engine/src/test/jme3test/audio/TestOgg.java
new file mode 100644
index 0000000..59b8d04
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestOgg.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.LowPassFilter;
+
+public class TestOgg extends SimpleApplication {
+
+    private AudioNode audioSource;
+
+    public static void main(String[] args){
+        TestOgg test = new TestOgg();
+        test.start();
+    }
+
+    @Override
+    public void simpleInitApp(){
+        System.out.println("Playing without filter");
+        audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true);
+        audioSource.play();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        if (audioSource.getStatus() != AudioNode.Status.Playing){
+            audioRenderer.deleteAudioData(audioSource.getAudioData());
+
+            System.out.println("Playing with low pass filter");
+            audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true);
+            audioSource.setDryFilter(new LowPassFilter(1f, .1f));
+            audioSource.setVolume(3);
+            audioSource.play();
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/audio/TestReverb.java b/engine/src/test/jme3test/audio/TestReverb.java
new file mode 100644
index 0000000..0a20096
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestReverb.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.Environment;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+
+public class TestReverb extends SimpleApplication {
+
+  private AudioNode audioSource;
+  private float time = 0;
+  private float nextTime = 1;
+
+  public static void main(String[] args) {
+    TestReverb test = new TestReverb();
+    test.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    audioSource = new AudioNode(assetManager, "Sound/Effects/Bang.wav");
+
+    float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0,
+      1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, 0.00f,
+      -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, 0.250f,
+      0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f};
+    audioRenderer.setEnvironment(new Environment(eax));
+    Environment env = Environment.Cavern;
+    audioRenderer.setEnvironment(env);
+  }
+
+  @Override
+  public void simpleUpdate(float tpf) {
+    time += tpf;
+
+    if (time > nextTime) {
+      Vector3f v = new Vector3f();
+      v.setX(FastMath.nextRandomFloat());
+      v.setY(FastMath.nextRandomFloat());
+      v.setZ(FastMath.nextRandomFloat());
+      v.multLocal(40, 2, 40);
+      v.subtractLocal(20, 1, 20);
+
+      audioSource.setLocalTranslation(v);
+      audioSource.playInstance();
+      time = 0;
+      nextTime = FastMath.nextRandomFloat() * 2 + 0.5f;
+    }
+  }
+}
diff --git a/engine/src/test/jme3test/audio/TestWav.java b/engine/src/test/jme3test/audio/TestWav.java
new file mode 100644
index 0000000..7e73ded
--- /dev/null
+++ b/engine/src/test/jme3test/audio/TestWav.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 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.audio;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+
+public class TestWav extends SimpleApplication {
+
+  private float time = 0;
+  private AudioNode audioSource;
+
+  public static void main(String[] args) {
+    TestWav test = new TestWav();
+    test.start();
+  }
+
+  @Override
+  public void simpleUpdate(float tpf) {
+    time += tpf;
+    if (time > 1f) {
+      audioSource.playInstance();
+      time = 0;
+    }
+
+  }
+
+  @Override
+  public void simpleInitApp() {
+    audioSource = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false);
+    audioSource.setLooping(false);
+  }
+}
diff --git a/engine/src/test/jme3test/awt/AppHarness.java b/engine/src/test/jme3test/awt/AppHarness.java
new file mode 100644
index 0000000..4a30d78
--- /dev/null
+++ b/engine/src/test/jme3test/awt/AppHarness.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2009-2010 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.awt;
+
+import com.jme3.app.Application;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeCanvasContext;
+import com.jme3.system.JmeSystem;
+import java.applet.Applet;
+import java.awt.Canvas;
+import java.awt.Graphics;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.swing.SwingUtilities;
+
+/**
+ *
+ * @author Kirill
+ */
+public class AppHarness extends Applet {
+
+    private JmeCanvasContext context;
+    private Canvas canvas;
+    private Application app;
+
+    private String appClass;
+    private URL appCfg = null;
+
+    private void createCanvas(){
+        AppSettings settings = new AppSettings(true);
+
+        // load app cfg
+        if (appCfg != null){
+            try {
+                InputStream in = appCfg.openStream();
+                settings.load(in);
+                in.close();
+            } catch (IOException ex){
+                ex.printStackTrace();
+            }
+        }
+
+        settings.setWidth(getWidth());
+        settings.setHeight(getHeight());
+        settings.setAudioRenderer(null);
+
+        JmeSystem.setLowPermissions(true);
+
+        try{
+            Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
+            app = clazz.newInstance();
+        }catch (ClassNotFoundException ex){
+            ex.printStackTrace();
+        }catch (InstantiationException ex){
+            ex.printStackTrace();
+        }catch (IllegalAccessException ex){
+            ex.printStackTrace();
+        }
+
+        app.setSettings(settings);
+        app.createCanvas();
+        
+        context = (JmeCanvasContext) app.getContext();
+        canvas = context.getCanvas();
+        canvas.setSize(getWidth(), getHeight());
+        
+        add(canvas);
+        app.startCanvas();
+    }
+
+    @Override
+    public final void update(Graphics g) {
+        canvas.setSize(getWidth(), getHeight());
+    }
+
+    @Override
+    public void init(){
+        appClass = getParameter("AppClass");
+        if (appClass == null)
+            throw new RuntimeException("The required parameter AppClass isn't specified!");
+        
+        try {
+            appCfg = new URL(getParameter("AppSettingsURL"));
+        } catch (MalformedURLException ex) {
+            ex.printStackTrace();
+            appCfg = null;
+        }
+        
+        createCanvas();
+        System.out.println("applet:init");
+    }
+
+    @Override
+    public void start(){
+        context.setAutoFlushFrames(true);
+        System.out.println("applet:start");
+    }
+
+    @Override
+    public void stop(){
+        context.setAutoFlushFrames(false);
+        System.out.println("applet:stop");
+    }
+
+    @Override
+    public void destroy(){
+        System.out.println("applet:destroyStart");
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                removeAll();
+                System.out.println("applet:destroyRemoved");
+            }
+        });
+        app.stop(true);
+        System.out.println("applet:destroyDone");
+    }
+
+}
diff --git a/engine/src/test/jme3test/awt/TestApplet.java b/engine/src/test/jme3test/awt/TestApplet.java
new file mode 100644
index 0000000..5606e74
--- /dev/null
+++ b/engine/src/test/jme3test/awt/TestApplet.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2009-2010 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.awt;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeCanvasContext;
+import com.jme3.system.JmeSystem;
+import java.applet.Applet;
+import java.awt.Canvas;
+import java.awt.Graphics;
+import java.util.concurrent.Callable;
+import javax.swing.SwingUtilities;
+
+public class TestApplet extends Applet {
+
+    private static JmeCanvasContext context;
+    private static Application app;
+    private static Canvas canvas;
+    private static TestApplet applet;
+
+    public TestApplet(){
+    }
+
+    public static void createCanvas(String appClass){
+        AppSettings settings = new AppSettings(true);
+        settings.setWidth(640);
+        settings.setHeight(480);
+//        settings.setRenderer(AppSettings.JOGL);
+
+        JmeSystem.setLowPermissions(true);
+
+        try{
+            Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
+            app = clazz.newInstance();
+        }catch (ClassNotFoundException ex){
+            ex.printStackTrace();
+        }catch (InstantiationException ex){
+            ex.printStackTrace();
+        }catch (IllegalAccessException ex){
+            ex.printStackTrace();
+        }
+
+        app.setSettings(settings);
+        app.createCanvas();
+        
+        context = (JmeCanvasContext) app.getContext();
+        canvas = context.getCanvas();
+        canvas.setSize(settings.getWidth(), settings.getHeight());
+    }
+
+    public static void startApp(){
+        applet.add(canvas);
+        app.startCanvas();
+
+        app.enqueue(new Callable<Void>(){
+            public Void call(){
+                if (app instanceof SimpleApplication){
+                    SimpleApplication simpleApp = (SimpleApplication) app;
+                    simpleApp.getFlyByCamera().setDragToRotate(true);
+                    simpleApp.getInputManager().setCursorVisible(true);
+                }
+                return null;
+            }
+        });
+    }
+
+    public void freezeApp(){
+        remove(canvas);
+    }
+
+    public void unfreezeApp(){
+        add(canvas);
+    }
+
+    @Override
+    public final void update(Graphics g) {
+//        canvas.setSize(getWidth(), getHeight());
+    }
+
+    @Override
+    public void init(){
+        applet = this;
+        createCanvas("jme3test.model.shape.TestBox");
+        startApp();
+        app.setPauseOnLostFocus(false);
+        System.out.println("applet:init");
+    }
+
+    @Override
+    public void start(){
+//        context.setAutoFlushFrames(true);
+        System.out.println("applet:start");
+    }
+
+    @Override
+    public void stop(){
+//        context.setAutoFlushFrames(false);
+        System.out.println("applet:stop");
+    }
+
+    @Override
+    public void destroy(){
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                removeAll();
+                System.out.println("applet:destroyStart");
+            }
+        });
+        app.stop(true);
+        System.out.println("applet:destroyEnd");
+    }
+
+}
diff --git a/engine/src/test/jme3test/awt/TestAwtPanels.java b/engine/src/test/jme3test/awt/TestAwtPanels.java
new file mode 100644
index 0000000..2c67867
--- /dev/null
+++ b/engine/src/test/jme3test/awt/TestAwtPanels.java
@@ -0,0 +1,88 @@
+package jme3test.awt;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.system.awt.AwtPanel;
+import com.jme3.system.awt.AwtPanelsContext;
+import com.jme3.system.awt.PaintMode;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+public class TestAwtPanels extends SimpleApplication {
+
+    private static TestAwtPanels app;
+    private static AwtPanel panel, panel2;
+    private static int panelsClosed = 0;
+    
+    private static void createWindowForPanel(AwtPanel panel, int location){
+        JFrame frame = new JFrame("Render Display " + location);
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(panel, BorderLayout.CENTER);
+        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        frame.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosed(WindowEvent e) {
+                if (++panelsClosed == 2){
+                    app.stop();
+                }
+            }
+        });
+        frame.pack();
+        frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400);
+        frame.setVisible(true);
+    }
+    
+    public static void main(String[] args){
+        Logger.getLogger("com.jme3").setLevel(Level.WARNING);
+        
+        app = new TestAwtPanels();
+        app.setShowSettings(false);
+        AppSettings settings = new AppSettings(true);
+        settings.setCustomRenderer(AwtPanelsContext.class);
+        settings.setFrameRate(60);
+        app.setSettings(settings);
+        app.start();
+        
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext();
+                panel = ctx.createPanel(PaintMode.Accelerated);
+                panel.setPreferredSize(new Dimension(400, 300));
+                ctx.setInputSource(panel);
+                
+                panel2 = ctx.createPanel(PaintMode.Accelerated);
+                panel2.setPreferredSize(new Dimension(400, 300));
+                
+                createWindowForPanel(panel, 300);
+                createWindowForPanel(panel2, 700);
+            }
+        });
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        flyCam.setDragToRotate(true);
+        
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+        
+        panel.attachTo(true, viewPort);
+        guiViewPort.setClearFlags(true, true, true);
+        panel2.attachTo(false, guiViewPort);
+    }
+}
diff --git a/engine/src/test/jme3test/awt/TestCanvas.java b/engine/src/test/jme3test/awt/TestCanvas.java
new file mode 100644
index 0000000..6351ee3
--- /dev/null
+++ b/engine/src/test/jme3test/awt/TestCanvas.java
@@ -0,0 +1,270 @@
+/*

+ * Copyright (c) 2009-2010 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.awt;

+

+import com.jme3.app.Application;

+import com.jme3.app.SimpleApplication;

+import com.jme3.system.AppSettings;

+import com.jme3.system.JmeCanvasContext;

+import com.jme3.util.JmeFormatter;

+import java.awt.BorderLayout;

+import java.awt.Canvas;

+import java.awt.Container;

+import java.awt.Dimension;

+import java.awt.event.ActionEvent;

+import java.awt.event.ActionListener;

+import java.awt.event.WindowAdapter;

+import java.awt.event.WindowEvent;

+import java.util.concurrent.Callable;

+import java.util.logging.ConsoleHandler;

+import java.util.logging.Handler;

+import java.util.logging.Logger;

+import javax.swing.*;

+

+public class TestCanvas {

+

+    private static JmeCanvasContext context;

+    private static Canvas canvas;

+    private static Application app;

+    private static JFrame frame;

+    private static Container canvasPanel1, canvasPanel2;

+    private static Container currentPanel;

+    private static JTabbedPane tabbedPane;

+    private static final String appClass = "jme3test.post.TestRenderToTexture";

+

+    private static void createTabs(){

+        tabbedPane = new JTabbedPane();

+        

+        canvasPanel1 = new JPanel();

+        canvasPanel1.setLayout(new BorderLayout());

+        tabbedPane.addTab("jME3 Canvas 1", canvasPanel1);

+        

+        canvasPanel2 = new JPanel();

+        canvasPanel2.setLayout(new BorderLayout());

+        tabbedPane.addTab("jME3 Canvas 2", canvasPanel2);

+        

+        frame.getContentPane().add(tabbedPane);

+        

+        currentPanel = canvasPanel1;

+    }

+    

+    private static void createMenu(){

+        JMenuBar menuBar = new JMenuBar();

+        frame.setJMenuBar(menuBar);

+

+        JMenu menuTortureMethods = new JMenu("Canvas Torture Methods");

+        menuBar.add(menuTortureMethods);

+

+        final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas");

+        menuTortureMethods.add(itemRemoveCanvas);

+        itemRemoveCanvas.addActionListener(new ActionListener() {

+            public void actionPerformed(ActionEvent e) {

+                if (itemRemoveCanvas.getText().equals("Remove Canvas")){

+                    currentPanel.remove(canvas);

+

+                    itemRemoveCanvas.setText("Add Canvas");

+                }else if (itemRemoveCanvas.getText().equals("Add Canvas")){

+                    currentPanel.add(canvas, BorderLayout.CENTER);

+                    

+                    itemRemoveCanvas.setText("Remove Canvas");

+                }

+            }

+        });

+        

+        final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas");

+        menuTortureMethods.add(itemHideCanvas);

+        itemHideCanvas.addActionListener(new ActionListener() {

+            public void actionPerformed(ActionEvent e) {

+                if (itemHideCanvas.getText().equals("Hide Canvas")){

+                    canvas.setVisible(false);

+                    itemHideCanvas.setText("Show Canvas");

+                }else if (itemHideCanvas.getText().equals("Show Canvas")){

+                    canvas.setVisible(true);

+                    itemHideCanvas.setText("Hide Canvas");

+                }

+            }

+        });

+        

+        final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2");

+        menuTortureMethods.add(itemSwitchTab);

+        itemSwitchTab.addActionListener(new ActionListener(){

+           public void actionPerformed(ActionEvent e){

+               if (itemSwitchTab.getText().equals("Switch to tab #2")){

+                   canvasPanel1.remove(canvas);

+                   canvasPanel2.add(canvas, BorderLayout.CENTER);

+                   currentPanel = canvasPanel2;

+                   itemSwitchTab.setText("Switch to tab #1");

+               }else if (itemSwitchTab.getText().equals("Switch to tab #1")){

+                   canvasPanel2.remove(canvas);

+                   canvasPanel1.add(canvas, BorderLayout.CENTER);

+                   currentPanel = canvasPanel1;

+                   itemSwitchTab.setText("Switch to tab #2");

+               }

+           } 

+        });

+        

+        JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel");

+        menuTortureMethods.add(itemSwitchLaf);

+        itemSwitchLaf.addActionListener(new ActionListener(){

+            public void actionPerformed(ActionEvent e){

+                try {

+                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

+                } catch (Throwable t){

+                    t.printStackTrace();

+                }

+                SwingUtilities.updateComponentTreeUI(frame);

+                frame.pack();

+            }

+        });

+        

+        JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)");

+        menuTortureMethods.add(itemSmallSize);

+        itemSmallSize.addActionListener(new ActionListener(){

+            public void actionPerformed(ActionEvent e){

+                Dimension preferred = frame.getPreferredSize();

+                frame.setPreferredSize(new Dimension(0, 0));

+                frame.pack();

+                frame.setPreferredSize(preferred);

+            }

+        });

+        

+        JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas");

+        menuTortureMethods.add(itemKillCanvas);

+        itemKillCanvas.addActionListener(new ActionListener() {

+            public void actionPerformed(ActionEvent e) {

+                currentPanel.remove(canvas);

+                app.stop(true);

+

+                createCanvas(appClass);

+                currentPanel.add(canvas, BorderLayout.CENTER);

+                frame.pack();

+                startApp();

+            }

+        });

+

+        JMenuItem itemExit = new JMenuItem("Exit");

+        menuTortureMethods.add(itemExit);

+        itemExit.addActionListener(new ActionListener() {

+            public void actionPerformed(ActionEvent ae) {

+                frame.dispose();

+                app.stop();

+            }

+        });

+    }

+    

+    private static void createFrame(){

+        frame = new JFrame("Test");

+        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

+        frame.addWindowListener(new WindowAdapter(){

+            @Override

+            public void windowClosed(WindowEvent e) {

+                app.stop();

+            }

+        });

+

+        createTabs();

+        createMenu();

+    }

+

+    public static void createCanvas(String appClass){

+        AppSettings settings = new AppSettings(true);

+        settings.setWidth(640);

+        settings.setHeight(480);

+

+        try{

+            Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);

+            app = clazz.newInstance();

+        }catch (ClassNotFoundException ex){

+            ex.printStackTrace();

+        }catch (InstantiationException ex){

+            ex.printStackTrace();

+        }catch (IllegalAccessException ex){

+            ex.printStackTrace();

+        }

+

+        app.setPauseOnLostFocus(false);

+        app.setSettings(settings);

+        app.createCanvas();

+        app.startCanvas();

+

+        context = (JmeCanvasContext) app.getContext();

+        canvas = context.getCanvas();

+        canvas.setSize(settings.getWidth(), settings.getHeight());

+    }

+

+    public static void startApp(){

+        app.startCanvas();

+        app.enqueue(new Callable<Void>(){

+            public Void call(){

+                if (app instanceof SimpleApplication){

+                    SimpleApplication simpleApp = (SimpleApplication) app;

+                    simpleApp.getFlyByCamera().setDragToRotate(true);

+                }

+                return null;

+            }

+        });

+        

+    }

+

+    public static void main(String[] args){

+        JmeFormatter formatter = new JmeFormatter();

+

+        Handler consoleHandler = new ConsoleHandler();

+        consoleHandler.setFormatter(formatter);

+

+        Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);

+        Logger.getLogger("").addHandler(consoleHandler);

+        

+        createCanvas(appClass);

+        

+        try {

+            Thread.sleep(500);

+        } catch (InterruptedException ex) {

+        }

+        

+        SwingUtilities.invokeLater(new Runnable(){

+            public void run(){

+                JPopupMenu.setDefaultLightWeightPopupEnabled(false);

+

+                createFrame();

+                

+                currentPanel.add(canvas, BorderLayout.CENTER);

+                frame.pack();

+                startApp();

+                frame.setLocationRelativeTo(null);

+                frame.setVisible(true);

+            }

+        });

+    }

+

+}

diff --git a/engine/src/test/jme3test/awt/TestSafeCanvas.java b/engine/src/test/jme3test/awt/TestSafeCanvas.java
new file mode 100644
index 0000000..88451f5
--- /dev/null
+++ b/engine/src/test/jme3test/awt/TestSafeCanvas.java
@@ -0,0 +1,69 @@
+package jme3test.awt;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeCanvasContext;
+import java.awt.Canvas;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.JFrame;
+
+public class TestSafeCanvas extends SimpleApplication {
+
+    public static void main(String[] args) throws InterruptedException{
+        AppSettings settings = new AppSettings(true);
+        settings.setWidth(640);
+        settings.setHeight(480);
+
+        final TestSafeCanvas app = new TestSafeCanvas();
+        app.setPauseOnLostFocus(false);
+        app.setSettings(settings);
+        app.createCanvas();
+        app.startCanvas(true);
+
+        JmeCanvasContext context = (JmeCanvasContext) app.getContext();
+        Canvas canvas = context.getCanvas();
+        canvas.setSize(settings.getWidth(), settings.getHeight());
+
+        
+
+        Thread.sleep(3000);
+
+        JFrame frame = new JFrame("Test");
+        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        frame.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                app.stop();
+            }
+        });
+        frame.getContentPane().add(canvas);
+        frame.pack();
+        frame.setLocationRelativeTo(null);
+        frame.setVisible(true);
+
+        Thread.sleep(3000);
+
+        frame.getContentPane().remove(canvas);
+
+        Thread.sleep(3000);
+
+        frame.getContentPane().add(canvas);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setDragToRotate(true);
+
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+    }
+}
diff --git a/engine/src/test/jme3test/batching/TestBatchNode.java b/engine/src/test/jme3test/batching/TestBatchNode.java
new file mode 100644
index 0000000..861536c
--- /dev/null
+++ b/engine/src/test/jme3test/batching/TestBatchNode.java
@@ -0,0 +1,97 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package jme3test.batching;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.BatchNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.NanoTimer;
+import com.jme3.util.TangentBinormalGenerator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestBatchNode extends SimpleApplication {
+
+    public static void main(String[] args) {
+
+        TestBatchNode app = new TestBatchNode();
+        app.start();
+    }
+    BatchNode batch;
+
+    @Override
+    public void simpleInitApp() {
+        timer = new NanoTimer();
+        batch = new BatchNode("theBatchNode");
+
+        /**
+         * A cube with a color "bleeding" through transparent texture. Uses
+         * Texture from jme3-test-data library!
+         */
+        Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f );
+        cube = new Geometry("cube1", boxshape4);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");     
+        cube.setMaterial(mat);
+//        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");        
+//        mat.setColor("Diffuse", ColorRGBA.Blue);
+//        mat.setBoolean("UseMaterialColors", true);
+        /**
+         * A cube with a color "bleeding" through transparent texture. Uses
+         * Texture from jme3-test-data library!
+         */
+        Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f);
+        cube2 = new Geometry("cube2", box);
+        cube2.setMaterial(mat);
+        
+        TangentBinormalGenerator.generate(cube);
+        TangentBinormalGenerator.generate(cube2);
+
+
+         n = new Node("aNode");
+       // n.attachChild(cube2);
+        batch.attachChild(cube);
+        batch.attachChild(cube2);
+      //  batch.setMaterial(mat);
+        batch.batch();
+        rootNode.attachChild(batch);
+        cube.setLocalTranslation(3, 0, 0);
+        cube2.setLocalTranslation(0, 3, 0);
+
+
+        dl=new DirectionalLight();
+        dl.setColor(ColorRGBA.White.mult(2));
+        dl.setDirection(new Vector3f(1, -1, -1));
+        rootNode.addLight(dl);
+        flyCam.setMoveSpeed(10);
+    }
+    Node n;
+    Geometry cube;
+    Geometry cube2;
+    float time = 0;
+    DirectionalLight dl;
+    @Override
+    public void simpleUpdate(float tpf) {
+        time += tpf;
+        dl.setDirection(cam.getDirection());
+        cube2.setLocalTranslation(FastMath.sin(-time)*3, FastMath.cos(time)*3, 0);        
+        cube2.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
+        cube2.setLocalScale(Math.max(FastMath.sin(time),0.5f));
+
+        batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
+        
+    }
+//    
+}
diff --git a/engine/src/test/jme3test/batching/TestBatchNodeCluster.java b/engine/src/test/jme3test/batching/TestBatchNodeCluster.java
new file mode 100644
index 0000000..1da3003
--- /dev/null
+++ b/engine/src/test/jme3test/batching/TestBatchNodeCluster.java
@@ -0,0 +1,345 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package jme3test.batching;
+
+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.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.scene.*;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.system.NanoTimer;
+import java.util.ArrayList;
+import java.util.Random;
+
+public class TestBatchNodeCluster extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestBatchNodeCluster app = new TestBatchNodeCluster();
+        settingst = new AppSettings(true);
+        //settingst.setFrameRate(75);
+        settingst.setResolution(640, 480);
+        settingst.setVSync(false);
+        settingst.setFullscreen(false);
+        app.setSettings(settingst);
+        app.setShowSettings(false);
+        app.start();
+    }
+    private ActionListener al = new ActionListener() {
+
+        public void onAction(String name, boolean isPressed, float tpf) {
+            if (name.equals("Start Game")) {
+//              randomGenerator();
+            }
+        }
+    };
+    protected Random rand = new Random();
+    protected int maxCubes = 2000;
+    protected int startAt = 0;
+    protected static int xPositions = 0, yPositions = 0, zPositions = 0;
+    protected int returner = 0;
+    protected ArrayList<Integer> xPosition = new ArrayList<Integer>();
+    protected ArrayList<Integer> yPosition = new ArrayList<Integer>();
+    protected ArrayList<Integer> zPosition = new ArrayList<Integer>();
+    protected int xLimitf = 60, xLimits = -60, yLimitf = 60, yLimits = -20, zLimitf = 60, zLimits = -60;
+    protected int circ = 8;//increases by 8 every time.
+    protected int dynamic = 4;
+    protected static AppSettings settingst;
+    protected boolean isTrue = true;
+    private int lineLength = 50;
+    protected BatchNode batchNode;
+    Material mat1;
+    Material mat2;
+    Material mat3;
+    Material mat4;
+    Node terrain;
+    //protected
+//    protected Geometry player;
+
+    @Override
+    public void simpleInitApp() {
+        timer = new NanoTimer();
+
+        batchNode = new SimpleBatchNode("BatchNode");
+
+
+        xPosition.add(0);
+        yPosition.add(0);
+        zPosition.add(0);
+
+        mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setColor("Color", ColorRGBA.White);
+        mat1.setColor("GlowColor", ColorRGBA.Blue.mult(10));
+
+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setColor("Color", ColorRGBA.White);
+        mat2.setColor("GlowColor", ColorRGBA.Red.mult(10));
+
+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat3.setColor("Color", ColorRGBA.White);
+        mat3.setColor("GlowColor", ColorRGBA.Yellow.mult(10));
+
+        mat4 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat4.setColor("Color", ColorRGBA.White);
+        mat4.setColor("GlowColor", ColorRGBA.Orange.mult(10));
+
+        randomGenerator();
+
+        //rootNode.attachChild(SkyFactory.createSky(
+        //  assetManager, "Textures/SKY02.zip", false));
+        inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addListener(al, new String[]{"Start Game"});
+
+
+        cam.setLocation(new Vector3f(-34.403286f, 126.65158f, 434.791f));
+        cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f));
+
+
+        batchNode.batch();
+
+
+        terrain = new Node("terrain");
+        terrain.setLocalTranslation(50, 0, 50);
+        terrain.attachChild(batchNode);
+
+        flyCam.setMoveSpeed(100);
+        rootNode.attachChild(terrain);
+        Vector3f pos = new Vector3f(-40, 0, -40);
+        batchNode.setLocalTranslation(pos);
+
+
+        Arrow a = new Arrow(new Vector3f(0, 50, 0));
+        Geometry g = new Geometry("a", a);
+        g.setLocalTranslation(terrain.getLocalTranslation());
+        Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        m.setColor("Color", ColorRGBA.Blue);
+        g.setMaterial(m);
+
+
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        fpp.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects));
+//        SSAOFilter ssao = new SSAOFilter(8.630104f,22.970434f,2.9299977f,0.2999997f);    
+//        fpp.addFilter(ssao);
+        viewPort.addProcessor(fpp);
+        //   viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+    }
+
+    public void randomGenerator() {
+        for (int i = startAt; i < maxCubes - 1; i++) {
+            randomize();
+            Geometry box = new Geometry("Box" + i, new Box(Vector3f.ZERO, 1, 1, 1));
+            box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1),
+                    yPosition.get(yPosition.size() - 1),
+                    zPosition.get(zPosition.size() - 1)));
+            batchNode.attachChild(box);
+            if (i < 500) {
+                box.setMaterial(mat1);
+            } else if (i < 1000) {
+
+                box.setMaterial(mat2);
+            } else if (i < 1500) {
+
+                box.setMaterial(mat3);
+            } else {
+
+                box.setMaterial(mat4);
+            }
+
+        }
+    }
+
+//    public BatchNode randomBatch() {
+//
+//        int randomn = rand.nextInt(4);
+//        if (randomn == 0) {
+//            return blue;
+//        } else if (randomn == 1) {
+//            return brown;
+//        } else if (randomn == 2) {
+//            return pink;
+//        } else if (randomn == 3) {
+//            return orange;
+//        }
+//        return null;
+//    }
+    public ColorRGBA randomColor() {
+        ColorRGBA color = ColorRGBA.Black;
+        int randomn = rand.nextInt(4);
+        if (randomn == 0) {
+            color = ColorRGBA.Orange;
+        } else if (randomn == 1) {
+            color = ColorRGBA.Blue;
+        } else if (randomn == 2) {
+            color = ColorRGBA.Brown;
+        } else if (randomn == 3) {
+            color = ColorRGBA.Magenta;
+        }
+        return color;
+    }
+
+    public void randomize() {
+        int xpos = xPosition.get(xPosition.size() - 1);
+        int ypos = yPosition.get(yPosition.size() - 1);
+        int zpos = zPosition.get(zPosition.size() - 1);
+        int x = 0;
+        int y = 0;
+        int z = 0;
+        boolean unTrue = true;
+        while (unTrue) {
+            unTrue = false;
+            boolean xChanged = false;
+            x = 0;
+            y = 0;
+            z = 0;
+            if (xpos >= lineLength * 2) {
+                x = 2;
+                xChanged = true;
+            } else {
+                x = xPosition.get(xPosition.size() - 1) + 2;
+            }
+            if (xChanged) {
+                //y = yPosition.get(yPosition.size() - lineLength) + 2;
+            } else {
+                y = rand.nextInt(3);
+                if (yPosition.size() > lineLength) {
+                    if (yPosition.size() > 51) {
+                        if (y == 0 && ypos < yLimitf && getym(lineLength) > ypos - 2) {
+                            y = ypos + 2;
+                        } else if (y == 1 && ypos > yLimits && getym(lineLength) < ypos + 2) {
+                            y = ypos - 2;
+                        } else if (y == 2 && getym(lineLength) > ypos - 2 && getym(lineLength) < ypos + 2) {
+                            y = ypos;
+                        } else {
+                            if (ypos >= yLimitf) {
+                                y = ypos - 2;
+                            } else if (ypos <= yLimits) {
+                                y = ypos + 2;
+                            } else if (y == 0 && getym(lineLength) >= ypos - 4) {
+                                y = ypos - 2;
+                            } else if (y == 0 && getym(lineLength) >= ypos - 2) {
+                                y = ypos;
+                            } else if (y == 1 && getym(lineLength) >= ypos + 4) {
+                                y = ypos + 2;
+                            } else if (y == 1 && getym(lineLength) >= ypos + 2) {
+                                y = ypos;
+                            } else if (y == 2 && getym(lineLength) <= ypos - 2) {
+                                y = ypos - 2;
+                            } else if (y == 2 && getym(lineLength) >= ypos + 2) {
+                                y = ypos + 2;
+                            } else {
+                                System.out.println("wtf");
+                            }
+                        }
+                    } else if (yPosition.size() == lineLength) {
+                        if (y == 0 && ypos < yLimitf) {
+                            y = getym(lineLength) + 2;
+                        } else if (y == 1 && ypos > yLimits) {
+                            y = getym(lineLength) - 2;
+                        }
+                    }
+                } else {
+                    if (y == 0 && ypos < yLimitf) {
+                        y = ypos + 2;
+                    } else if (y == 1 && ypos > yLimits) {
+                        y = ypos - 2;
+                    } else if (y == 2) {
+                        y = ypos;
+                    } else if (y == 0 && ypos >= yLimitf) {
+                        y = ypos - 2;
+                    } else if (y == 1 && ypos <= yLimits) {
+                        y = ypos + 2;
+                    }
+                }
+            }
+            if (xChanged) {
+                z = zpos + 2;
+            } else {
+                z = zpos;
+            }
+//          for (int i = 0; i < xPosition.size(); i++)
+//          {
+//              if (x - xPosition.get(i) <= 1 && x - xPosition.get(i) >= -1 &&
+//                      y - yPosition.get(i) <= 1 && y - yPosition.get(i) >= -1
+//                      &&z - zPosition.get(i) <= 1 && z - zPosition.get(i) >=
+//                      -1)
+//              {
+//                  unTrue = true;
+//              }
+//          }
+        }
+        xPosition.add(x);
+        yPosition.add(y);
+        zPosition.add(z);
+    }
+
+    public int getxm(int i) {
+        return xPosition.get(xPosition.size() - i);
+    }
+
+    public int getym(int i) {
+        return yPosition.get(yPosition.size() - i);
+    }
+
+    public int getzm(int i) {
+        return zPosition.get(zPosition.size() - i);
+    }
+
+    public int getx(int i) {
+        return xPosition.get(i);
+    }
+
+    public int gety(int i) {
+        return yPosition.get(i);
+    }
+
+    public int getz(int i) {
+        return zPosition.get(i);
+    }
+    long nbFrames = 0;
+    long cullTime = 0;
+    float time = 0;
+    Vector3f lookAtPos = new Vector3f(0, 0, 0);
+    float xpos = 0;
+    Spatial box;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        time += tpf;
+        int random = rand.nextInt(2000);
+        float mult1 = 1.0f;
+        float mult2 = 1.0f;
+        if (random < 500) {
+            mult1 = 1.0f;
+            mult2 = 1.0f;
+        } else if (random < 1000) {
+            mult1 = -1.0f;
+            mult2 = 1.0f;
+        } else if (random < 1500) {
+            mult1 = 1.0f;
+            mult2 = -1.0f;
+        } else if (random <= 2000) {
+            mult1 = -1.0f;
+            mult2 = -1.0f;
+        }
+        box = batchNode.getChild("Box" + random);
+        if (box != null) {
+            Vector3f v = box.getLocalTranslation();
+            box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y + (FastMath.sin(time * mult1) * FastMath.cos(time * mult1) * 20), v.z + FastMath.cos(time * mult2) * 20);
+        }
+        terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y));
+
+
+    }
+}
diff --git a/engine/src/test/jme3test/batching/TestBatchNodeTower.java b/engine/src/test/jme3test/batching/TestBatchNodeTower.java
new file mode 100644
index 0000000..57285d0
--- /dev/null
+++ b/engine/src/test/jme3test/batching/TestBatchNodeTower.java
@@ -0,0 +1,251 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package jme3test.batching;
+
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.font.BitmapText;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.BatchNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Sphere.TextureMode;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.shadow.PssmShadowRenderer.CompareMode;
+import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+import com.jme3.system.AppSettings;
+import com.jme3.system.NanoTimer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import jme3test.bullet.BombControl;
+
+/**
+ *
+ * @author double1984 (tower mod by atom)
+ */
+public class TestBatchNodeTower extends SimpleApplication {
+
+    int bricksPerLayer = 8;
+    int brickLayers = 30;
+
+    static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f;
+    float radius = 3f;
+    float angle = 0;
+
+
+    Material mat;
+    Material mat2;
+    Material mat3;
+    PssmShadowRenderer bsr;
+    private Sphere bullet;
+    private Box brick;
+    private SphereCollisionShape bulletCollisionShape;
+
+    private BulletAppState bulletAppState;
+    BatchNode batchNode = new BatchNode("batch Node");
+    
+    public static void main(String args[]) {
+        TestBatchNodeTower f = new TestBatchNodeTower();
+        AppSettings s = new AppSettings(true);
+        f.setSettings(s);
+        f.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        timer = new NanoTimer();
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+     //   bulletAppState.setEnabled(false);
+        stateManager.attach(bulletAppState);
+        bullet = new Sphere(32, 32, 0.4f, true, false);
+        bullet.setTextureMode(TextureMode.Projected);
+        bulletCollisionShape = new SphereCollisionShape(0.4f);
+
+        brick = new Box(Vector3f.ZERO, brickWidth, brickHeight, brickDepth);
+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
+        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        initMaterial();
+        initTower();
+        initFloor();
+        initCrossHairs();
+        this.cam.setLocation(new Vector3f(0, 25f, 8f));
+        cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
+        cam.setFrustumFar(80);
+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(actionListener, "shoot");
+        rootNode.setShadowMode(ShadowMode.Off);
+        
+        batchNode.batch();
+        batchNode.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(batchNode);
+        
+        
+        bsr = new PssmShadowRenderer(assetManager, 1024, 2);
+        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        bsr.setLambda(0.55f);
+        bsr.setShadowIntensity(0.6f);
+        bsr.setCompareMode(CompareMode.Hardware);
+        bsr.setFilterMode(FilterMode.PCF4);
+        viewPort.addProcessor(bsr);   
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean keyPressed, float tpf) {
+            if (name.equals("shoot") && !keyPressed) {
+                Geometry bulletg = new Geometry("bullet", bullet);
+                bulletg.setMaterial(mat2);
+                bulletg.setShadowMode(ShadowMode.CastAndReceive);
+                bulletg.setLocalTranslation(cam.getLocation());
+                RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);
+//                RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1);
+                bulletNode.setLinearVelocity(cam.getDirection().mult(25));
+                bulletg.addControl(bulletNode);
+                rootNode.attachChild(bulletg);
+                getPhysicsSpace().add(bulletNode);
+            }
+        }
+    };
+
+    public void initTower() {
+        double tempX = 0;
+        double tempY = 0;
+        double tempZ = 0;
+        angle = 0f;
+        for (int i = 0; i < brickLayers; i++){
+            // Increment rows
+            if(i!=0)
+                tempY+=brickHeight*2;
+            else
+                tempY=brickHeight;
+            // Alternate brick seams
+            angle = 360.0f / bricksPerLayer * i/2f;
+            for (int j = 0; j < bricksPerLayer; j++){
+              tempZ = Math.cos(Math.toRadians(angle))*radius;
+              tempX = Math.sin(Math.toRadians(angle))*radius;
+              System.out.println("x="+((float)(tempX))+" y="+((float)(tempY))+" z="+(float)(tempZ));
+              Vector3f vt = new Vector3f((float)(tempX), (float)(tempY), (float)(tempZ));
+              // Add crenelation
+              if (i==brickLayers-1){
+                if (j%2 == 0){
+                    addBrick(vt);
+                }
+              }
+              // Create main tower
+              else {
+                addBrick(vt);
+              }
+              angle += 360.0/bricksPerLayer;
+            }
+          }
+
+    }
+
+    public void initFloor() {
+        Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
+        floorBox.scaleTextureCoordinates(new Vector2f(3, 6));
+
+        Geometry floor = new Geometry("floor", floorBox);
+        floor.setMaterial(mat3);
+        floor.setShadowMode(ShadowMode.Receive);
+        floor.setLocalTranslation(0, 0, 0);
+        floor.addControl(new RigidBodyControl(0));
+        this.rootNode.attachChild(floor);
+        this.getPhysicsSpace().add(floor);
+    }
+
+    public void initMaterial() {
+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        mat.setTexture("ColorMap", tex);
+
+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
+        key2.setGenerateMips(true);
+        Texture tex2 = assetManager.loadTexture(key2);
+        mat2.setTexture("ColorMap", tex2);
+
+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
+        key3.setGenerateMips(true);
+        Texture tex3 = assetManager.loadTexture(key3);
+        tex3.setWrap(WrapMode.Repeat);
+        mat3.setTexture("ColorMap", tex3);
+    }
+int nbBrick =0;
+    public void addBrick(Vector3f ori) {
+        Geometry reBoxg = new Geometry("brick", brick);
+        reBoxg.setMaterial(mat);
+        reBoxg.setLocalTranslation(ori);
+        reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f );
+        reBoxg.addControl(new RigidBodyControl(1.5f));
+        reBoxg.setShadowMode(ShadowMode.CastAndReceive);
+        reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f);
+        this.batchNode.attachChild(reBoxg);
+        this.getPhysicsSpace().add(reBoxg);
+        nbBrick++;
+    }
+
+    protected void initCrossHairs() {
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/blender/TestBlenderLoader.java b/engine/src/test/jme3test/blender/TestBlenderLoader.java
new file mode 100644
index 0000000..4575aee
--- /dev/null
+++ b/engine/src/test/jme3test/blender/TestBlenderLoader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 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.blender;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+
+public class TestBlenderLoader extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestBlenderLoader app = new TestBlenderLoader();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        //load model with packed images
+        Spatial ogre = assetManager.loadModel("Blender/2.4x/Sinbad.blend");
+        rootNode.attachChild(ogre);
+
+        //load model with referenced images
+        Spatial track = assetManager.loadModel("Blender/2.4x/MountainValley_Track.blend");
+        rootNode.attachChild(track);
+        
+        // sunset light
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f));
+        rootNode.addLight(dl);
+
+        // skylight
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f));
+        rootNode.addLight(dl);
+
+        // white ambient light
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.80f, 0.70f, 0.80f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+    }
+    
+}
diff --git a/engine/src/test/jme3test/bounding/TestRayCollision.java b/engine/src/test/jme3test/bounding/TestRayCollision.java
new file mode 100644
index 0000000..922c2ed
--- /dev/null
+++ b/engine/src/test/jme3test/bounding/TestRayCollision.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 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.bounding;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.collision.CollisionResults;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+
+/**
+ * Tests picking/collision between bounds and shapes.
+ */
+public class TestRayCollision {
+
+    public static void main(String[] args){
+        Ray r = new Ray(Vector3f.ZERO, Vector3f.UNIT_X);
+        BoundingBox bbox = new BoundingBox(new Vector3f(5, 0, 0), 1, 1, 1);
+
+        CollisionResults res = new CollisionResults();
+        bbox.collideWith(r, res);
+
+        System.out.println("Bounding:" +bbox);
+        System.out.println("Ray: "+r);
+
+        System.out.println("Num collisions: "+res.size());
+        for (int i = 0; i < res.size(); i++){
+            System.out.println("--- Collision #"+i+" ---");
+            float dist = res.getCollision(i).getDistance();
+            Vector3f pt = res.getCollision(i).getContactPoint();
+            System.out.println("distance: "+dist);
+            System.out.println("point: "+pt);
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/bullet/BombControl.java b/engine/src/test/jme3test/bullet/BombControl.java
new file mode 100644
index 0000000..29d49c4
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/BombControl.java
@@ -0,0 +1,189 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.bullet;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.PhysicsTickListener;
+import com.jme3.bullet.collision.PhysicsCollisionEvent;
+import com.jme3.bullet.collision.PhysicsCollisionListener;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.shapes.EmitterSphereShape;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BombControl extends RigidBodyControl implements PhysicsCollisionListener, PhysicsTickListener {
+
+    private float explosionRadius = 10;
+    private PhysicsGhostObject ghostObject;
+    private Vector3f vector = new Vector3f();
+    private Vector3f vector2 = new Vector3f();
+    private float forceFactor = 1;
+    private ParticleEmitter effect;
+    private float fxTime = 0.5f;
+    private float maxTime = 4f;
+    private float curTime = -1.0f;
+    private float timer;
+
+    public BombControl(CollisionShape shape, float mass) {
+        super(shape, mass);
+        createGhostObject();
+    }
+
+    public BombControl(AssetManager manager, CollisionShape shape, float mass) {
+        super(shape, mass);
+        createGhostObject();
+        prepareEffect(manager);
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        super.setPhysicsSpace(space);
+        if (space != null) {
+            space.addCollisionListener(this);
+        }
+    }
+
+    private void prepareEffect(AssetManager assetManager) {
+        int COUNT_FACTOR = 1;
+        float COUNT_FACTOR_F = 1f;
+        effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR);
+        effect.setSelectRandomImage(true);
+        effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));
+        effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
+        effect.setStartSize(1.3f);
+        effect.setEndSize(2f);
+        effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
+        effect.setParticlesPerSec(0);
+        effect.setGravity(0, -5f, 0);
+        effect.setLowLife(.4f);
+        effect.setHighLife(.5f);
+        effect.setInitialVelocity(new Vector3f(0, 7, 0));
+        effect.setVelocityVariation(1f);
+        effect.setImagesX(2);
+        effect.setImagesY(2);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
+        effect.setMaterial(mat);
+    }
+
+    protected void createGhostObject() {
+        ghostObject = new PhysicsGhostObject(new SphereCollisionShape(explosionRadius));
+    }
+
+    public void collision(PhysicsCollisionEvent event) {
+        if (space == null) {
+            return;
+        }
+        if (event.getObjectA() == this || event.getObjectB() == this) {
+            space.add(ghostObject);
+            ghostObject.setPhysicsLocation(getPhysicsLocation(vector));
+            space.addTickListener(this);
+            if (effect != null && spatial.getParent() != null) {
+                curTime = 0;
+                effect.setLocalTranslation(spatial.getLocalTranslation());
+                spatial.getParent().attachChild(effect);
+                effect.emitAllParticles();
+            }
+            space.remove(this);
+            spatial.removeFromParent();
+        }
+    }
+    
+    public void prePhysicsTick(PhysicsSpace space, float f) {
+        space.removeCollisionListener(this);
+    }
+
+    public void physicsTick(PhysicsSpace space, float f) {
+        //get all overlapping objects and apply impulse to them
+        for (Iterator<PhysicsCollisionObject> it = ghostObject.getOverlappingObjects().iterator(); it.hasNext();) {            
+            PhysicsCollisionObject physicsCollisionObject = it.next();
+            if (physicsCollisionObject instanceof PhysicsRigidBody) {
+                PhysicsRigidBody rBody = (PhysicsRigidBody) physicsCollisionObject;
+                rBody.getPhysicsLocation(vector2);
+                vector2.subtractLocal(vector);
+                float force = explosionRadius - vector2.length();
+                force *= forceFactor;
+                force = force > 0 ? force : 0;
+                vector2.normalizeLocal();
+                vector2.multLocal(force);
+                ((PhysicsRigidBody) physicsCollisionObject).applyImpulse(vector2, Vector3f.ZERO);
+            }
+        }
+        space.removeTickListener(this);
+        space.remove(ghostObject);
+    }
+
+    @Override
+    public void update(float tpf) {
+        super.update(tpf);
+        if(enabled){
+            timer+=tpf;
+            if(timer>maxTime){
+                if(spatial.getParent()!=null){
+                    space.removeCollisionListener(this);
+                    space.remove(this);
+                    spatial.removeFromParent();
+                }
+            }
+        }
+        if (enabled && curTime >= 0) {
+            curTime += tpf;
+            if (curTime > fxTime) {
+                curTime = -1;
+                effect.removeFromParent();
+            }
+        }
+    }
+
+    /**
+     * @return the explosionRadius
+     */
+    public float getExplosionRadius() {
+        return explosionRadius;
+    }
+
+    /**
+     * @param explosionRadius the explosionRadius to set
+     */
+    public void setExplosionRadius(float explosionRadius) {
+        this.explosionRadius = explosionRadius;
+        createGhostObject();
+    }
+
+    public float getForceFactor() {
+        return forceFactor;
+    }
+
+    public void setForceFactor(float forceFactor) {
+        this.forceFactor = forceFactor;
+    }
+    
+    
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        throw new UnsupportedOperationException("Reading not supported.");
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        throw new UnsupportedOperationException("Saving not supported.");
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/PhysicsHoverControl.java b/engine/src/test/jme3test/bullet/PhysicsHoverControl.java
new file mode 100644
index 0000000..1464eec
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/PhysicsHoverControl.java
@@ -0,0 +1,240 @@
+/*
+ * 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.bullet;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.PhysicsTickListener;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.PhysicsControl;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+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 {
+
+    protected Spatial spatial;
+    protected boolean enabled = true;
+    protected PhysicsSpace space = null;
+    protected float steeringValue = 0;
+    protected float accelerationValue = 0;
+    protected int xw = 3;
+    protected int zw = 5;
+    protected int yw = 2;
+    protected Vector3f HOVER_HEIGHT_LF_START = new Vector3f(xw, 1, zw);
+    protected Vector3f HOVER_HEIGHT_RF_START = new Vector3f(-xw, 1, zw);
+    protected Vector3f HOVER_HEIGHT_LR_START = new Vector3f(xw, 1, -zw);
+    protected Vector3f HOVER_HEIGHT_RR_START = new Vector3f(-xw, 1, -zw);
+    protected Vector3f HOVER_HEIGHT_LF = new Vector3f(xw, -yw, zw);
+    protected Vector3f HOVER_HEIGHT_RF = new Vector3f(-xw, -yw, zw);
+    protected Vector3f HOVER_HEIGHT_LR = new Vector3f(xw, -yw, -zw);
+    protected Vector3f HOVER_HEIGHT_RR = new Vector3f(-xw, -yw, -zw);
+    protected Vector3f tempVect1 = new Vector3f(0, 0, 0);
+    protected Vector3f tempVect2 = new Vector3f(0, 0, 0);
+    protected Vector3f tempVect3 = new Vector3f(0, 0, 0);
+//    protected float rotationCounterForce = 10000f;
+//    protected float speedCounterMult = 2000f;
+//    protected float multiplier = 1000f;
+
+    public PhysicsHoverControl() {
+    }
+
+    /**
+     * Creates a new PhysicsNode with the supplied collision shape
+     * @param shape
+     */
+    public PhysicsHoverControl(CollisionShape shape) {
+        super(shape);
+        createWheels();
+    }
+
+    public PhysicsHoverControl(CollisionShape shape, float mass) {
+        super(shape, mass);
+        createWheels();
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void setSpatial(Spatial spatial) {
+        this.spatial = spatial;
+        setUserObject(spatial);
+        if (spatial == null) {
+            return;
+        }
+        setPhysicsLocation(spatial.getWorldTranslation());
+        setPhysicsRotation(spatial.getWorldRotation().toRotationMatrix());
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    private void createWheels() {
+        addWheel(HOVER_HEIGHT_LF_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false);
+        addWheel(HOVER_HEIGHT_RF_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false);
+        addWheel(HOVER_HEIGHT_LR_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false);
+        addWheel(HOVER_HEIGHT_RR_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false);
+        for (int i = 0; i < 4; i++) {
+            getWheel(i).setFrictionSlip(0.001f);
+        }
+    }
+
+    public void prePhysicsTick(PhysicsSpace space, float f) {
+        Vector3f angVel = getAngularVelocity();
+        float rotationVelocity = angVel.getY();
+        Vector3f dir = getForwardVector(tempVect2).multLocal(1, 0, 1).normalizeLocal();
+        getLinearVelocity(tempVect3);
+        Vector3f linearVelocity = tempVect3.multLocal(1, 0, 1);
+
+        if (steeringValue != 0) {
+            if (rotationVelocity < 1 && rotationVelocity > -1) {
+                applyTorque(tempVect1.set(0, steeringValue, 0));
+            }
+        } else {
+            // counter the steering value!
+            if (rotationVelocity > 0.2f) {
+                applyTorque(tempVect1.set(0, -mass * 20, 0));
+            } else if (rotationVelocity < -0.2f) {
+                applyTorque(tempVect1.set(0, mass * 20, 0));
+            }
+        }
+        if (accelerationValue > 0) {
+            // counter force that will adjust velocity
+            // if we are not going where we want to go.
+            // this will prevent "drifting" and thus improve control
+            // of the vehicle
+            float d = dir.dot(linearVelocity.normalize());
+            Vector3f counter = dir.project(linearVelocity).normalizeLocal().negateLocal().multLocal(1 - d);
+            applyForce(counter.multLocal(mass * 10), Vector3f.ZERO);
+
+            if (linearVelocity.length() < 30) {
+                applyForce(dir.multLocal(accelerationValue), Vector3f.ZERO);
+            }
+        } else {
+            // counter the acceleration value
+            if (linearVelocity.length() > FastMath.ZERO_TOLERANCE) {
+                linearVelocity.normalizeLocal().negateLocal();
+                applyForce(linearVelocity.mult(mass * 10), Vector3f.ZERO);
+            }
+        }
+    }
+
+    public void physicsTick(PhysicsSpace space, float f) {
+    }
+
+    public void update(float tpf) {
+        if (enabled && spatial != null) {
+            getMotionState().applyTransform(spatial);
+        }
+    }
+
+    public void render(RenderManager rm, ViewPort vp) {
+        if (enabled && space != null && space.getDebugManager() != null) {
+            if (debugShape == null) {
+                attachDebugShape(space.getDebugManager());
+            }
+            debugShape.setLocalTranslation(motionState.getWorldLocation());
+            debugShape.setLocalRotation(motionState.getWorldRotation());
+            debugShape.updateLogicalState(0);
+            debugShape.updateGeometricState();
+            rm.renderScene(debugShape, vp);
+        }
+    }
+
+    public void setPhysicsSpace(PhysicsSpace space) {
+        if (space == null) {
+            if (this.space != null) {
+                this.space.removeCollisionObject(this);
+                this.space.removeTickListener(this);
+            }
+            this.space = space;
+        } else {
+            space.addCollisionObject(this);
+            space.addTickListener(this);
+        }
+        this.space = space;
+    }
+
+    public PhysicsSpace getPhysicsSpace() {
+        return space;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(spatial, "spatial", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+    }
+
+    /**
+     * @param steeringValue the steeringValue to set
+     */
+    @Override
+    public void steer(float steeringValue) {
+        this.steeringValue = steeringValue * getMass();
+    }
+
+    /**
+     * @param accelerationValue the accelerationValue to set
+     */
+    @Override
+    public void accelerate(float accelerationValue) {
+        this.accelerationValue = accelerationValue * getMass();
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/PhysicsTestHelper.java b/engine/src/test/jme3test/bullet/PhysicsTestHelper.java
new file mode 100644
index 0000000..173a5a5
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/PhysicsTestHelper.java
@@ -0,0 +1,205 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.bullet;
+
+import com.jme3.app.Application;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Sphere.TextureMode;
+import com.jme3.texture.Texture;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class PhysicsTestHelper {
+
+    /**
+     * creates a simple physics test world with a floor, an obstacle and some test boxes
+     * @param rootNode
+     * @param assetManager
+     * @param space
+     */
+    public static void createPhysicsTestWorld(Node rootNode, AssetManager assetManager, PhysicsSpace space) {
+        AmbientLight light = new AmbientLight();
+        light.setColor(ColorRGBA.LightGray);
+        rootNode.addLight(light);
+
+        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+
+        Box floorBox = new Box(140, 0.25f, 140);
+        Geometry floorGeometry = new Geometry("Floor", floorBox);
+        floorGeometry.setMaterial(material);
+        floorGeometry.setLocalTranslation(0, -5, 0);
+//        Plane plane = new Plane();
+//        plane.setOriginNormal(new Vector3f(0, 0.25f, 0), Vector3f.UNIT_Y);
+//        floorGeometry.addControl(new RigidBodyControl(new PlaneCollisionShape(plane), 0));
+        floorGeometry.addControl(new RigidBodyControl(0));
+        rootNode.attachChild(floorGeometry);
+        space.add(floorGeometry);
+
+        //movable boxes
+        for (int i = 0; i < 12; i++) {
+            Box box = new Box(0.25f, 0.25f, 0.25f);
+            Geometry boxGeometry = new Geometry("Box", box);
+            boxGeometry.setMaterial(material);
+            boxGeometry.setLocalTranslation(i, 5, -3);
+            //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh
+            boxGeometry.addControl(new RigidBodyControl(2));
+            rootNode.attachChild(boxGeometry);
+            space.add(boxGeometry);
+        }
+
+        //immovable sphere with mesh collision shape
+        Sphere sphere = new Sphere(8, 8, 1);
+        Geometry sphereGeometry = new Geometry("Sphere", sphere);
+        sphereGeometry.setMaterial(material);
+        sphereGeometry.setLocalTranslation(4, -4, 2);
+        sphereGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
+        rootNode.attachChild(sphereGeometry);
+        space.add(sphereGeometry);
+
+    }
+    
+    public static void createPhysicsTestWorldSoccer(Node rootNode, AssetManager assetManager, PhysicsSpace space) {
+        AmbientLight light = new AmbientLight();
+        light.setColor(ColorRGBA.LightGray);
+        rootNode.addLight(light);
+
+        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+
+        Box floorBox = new Box(140, 0.25f, 140);
+        Geometry floorGeometry = new Geometry("Floor", floorBox);
+        floorGeometry.setMaterial(material);
+        floorGeometry.setLocalTranslation(0, -0.25f, 0);
+//        Plane plane = new Plane();
+//        plane.setOriginNormal(new Vector3f(0, 0.25f, 0), Vector3f.UNIT_Y);
+//        floorGeometry.addControl(new RigidBodyControl(new PlaneCollisionShape(plane), 0));
+        floorGeometry.addControl(new RigidBodyControl(0));
+        rootNode.attachChild(floorGeometry);
+        space.add(floorGeometry);
+
+        //movable spheres
+        for (int i = 0; i < 5; i++) {
+            Sphere sphere = new Sphere(16, 16, .5f);
+            Geometry ballGeometry = new Geometry("Soccer ball", sphere);
+            ballGeometry.setMaterial(material);
+            ballGeometry.setLocalTranslation(i, 2, -3);
+            //RigidBodyControl automatically uses Sphere collision shapes when attached to single geometry with sphere mesh
+            ballGeometry.addControl(new RigidBodyControl(.001f));
+            ballGeometry.getControl(RigidBodyControl.class).setRestitution(1);
+            rootNode.attachChild(ballGeometry);
+            space.add(ballGeometry);
+        }
+
+        //immovable Box with mesh collision shape
+        Box box = new Box(1, 1, 1);
+        Geometry boxGeometry = new Geometry("Box", box);
+        boxGeometry.setMaterial(material);
+        boxGeometry.setLocalTranslation(4, 1, 2);
+        boxGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(box), 0));
+        rootNode.attachChild(boxGeometry);
+        space.add(boxGeometry);
+
+    }
+
+    /**
+     * creates a box geometry with a RigidBodyControl
+     * @param assetManager
+     * @return
+     */
+    public static Geometry createPhysicsTestBox(AssetManager assetManager) {
+        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        Box box = new Box(0.25f, 0.25f, 0.25f);
+        Geometry boxGeometry = new Geometry("Box", box);
+        boxGeometry.setMaterial(material);
+        //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh
+        boxGeometry.addControl(new RigidBodyControl(2));
+        return boxGeometry;
+    }
+
+    /**
+     * creates a sphere geometry with a RigidBodyControl
+     * @param assetManager
+     * @return
+     */
+    public static Geometry createPhysicsTestSphere(AssetManager assetManager) {
+        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        Sphere sphere = new Sphere(8, 8, 0.25f);
+        Geometry boxGeometry = new Geometry("Sphere", sphere);
+        boxGeometry.setMaterial(material);
+        //RigidBodyControl automatically uses sphere collision shapes when attached to single geometry with sphere mesh
+        boxGeometry.addControl(new RigidBodyControl(2));
+        return boxGeometry;
+    }
+
+    /**
+     * creates an empty node with a RigidBodyControl
+     * @param manager
+     * @param shape
+     * @param mass
+     * @return
+     */
+    public static Node createPhysicsTestNode(AssetManager manager, CollisionShape shape, float mass) {
+        Node node = new Node("PhysicsNode");
+        RigidBodyControl control = new RigidBodyControl(shape, mass);
+        node.addControl(control);
+        return node;
+    }
+
+    /**
+     * creates the necessary inputlistener and action to shoot balls from teh camera
+     * @param app
+     * @param rootNode
+     * @param space
+     */
+    public static void createBallShooter(final Application app, final Node rootNode, final PhysicsSpace space) {
+        ActionListener actionListener = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                Sphere bullet = new Sphere(32, 32, 0.4f, true, false);
+                bullet.setTextureMode(TextureMode.Projected);
+                Material mat2 = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+                TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
+                key2.setGenerateMips(true);
+                Texture tex2 = app.getAssetManager().loadTexture(key2);
+                mat2.setTexture("ColorMap", tex2);
+                if (name.equals("shoot") && !keyPressed) {
+                    Geometry bulletg = new Geometry("bullet", bullet);
+                    bulletg.setMaterial(mat2);
+                    bulletg.setShadowMode(ShadowMode.CastAndReceive);
+                    bulletg.setLocalTranslation(app.getCamera().getLocation());
+                    RigidBodyControl bulletControl = new RigidBodyControl(1);
+                    bulletg.addControl(bulletControl);
+                    bulletControl.setLinearVelocity(app.getCamera().getDirection().mult(25));
+                    bulletg.addControl(bulletControl);
+                    rootNode.attachChild(bulletg);
+                    space.add(bulletControl);
+                }
+            }
+        };
+        app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        app.getInputManager().addListener(actionListener, "shoot");
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestAttachDriver.java b/engine/src/test/jme3test/bullet/TestAttachDriver.java
new file mode 100644
index 0000000..31f5f2f
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestAttachDriver.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.control.VehicleControl;
+import com.jme3.bullet.joints.SliderJoint;
+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.*;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Cylinder;
+import com.jme3.texture.Texture;
+
+/**
+ * Tests attaching/detaching nodes via joints
+ * @author normenhansen
+ */
+public class TestAttachDriver extends SimpleApplication implements ActionListener {
+
+    private VehicleControl vehicle;
+    private RigidBodyControl driver;
+    private RigidBodyControl bridge;
+    private SliderJoint slider;
+    private final float accelerationForce = 1000.0f;
+    private final float brakeForce = 100.0f;
+    private float steeringValue = 0;
+    private float accelerationValue = 0;
+    private Vector3f jumpForce = new Vector3f(0, 3000, 0);
+    private BulletAppState bulletAppState;
+    
+    public static void main(String[] args) {
+        TestAttachDriver app = new TestAttachDriver();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        setupKeys();
+        setupFloor();
+        buildPlayer();
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "Lefts");
+        inputManager.addListener(this, "Rights");
+        inputManager.addListener(this, "Ups");
+        inputManager.addListener(this, "Downs");
+        inputManager.addListener(this, "Space");
+        inputManager.addListener(this, "Reset");
+    }
+
+    public void setupFloor() {
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Interface/Logo/Monkey.jpg", true);
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        tex.setMinFilter(Texture.MinFilter.Trilinear);
+        mat.setTexture("ColorMap", tex);
+
+        Box floor = new Box(Vector3f.ZERO, 100, 1f, 100);
+        Geometry floorGeom = new Geometry("Floor", floor);
+        floorGeom.setMaterial(mat);
+        floorGeom.setLocalTranslation(new Vector3f(0f, -3, 0f));
+
+        floorGeom.addControl(new RigidBodyControl(new MeshCollisionShape(floorGeom.getMesh()), 0));
+        rootNode.attachChild(floorGeom);
+        getPhysicsSpace().add(floorGeom);
+    }
+
+    private void buildPlayer() {
+        Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Red);
+
+        //create a compound shape and attach the BoxCollisionShape for the car body at 0,1,0
+        //this shifts the effective center of mass of the BoxCollisionShape to 0,-1,0
+        CompoundCollisionShape compoundShape = new CompoundCollisionShape();
+        BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.2f, 0.5f, 2.4f));
+        compoundShape.addChildShape(box, new Vector3f(0, 1, 0));
+
+        //create vehicle node
+        Node vehicleNode=new Node("vehicleNode");
+        vehicle = new VehicleControl(compoundShape, 800);
+        vehicleNode.addControl(vehicle);
+
+        //setting suspension values for wheels, this can be a bit tricky
+        //see also https://docs.google.com/Doc?docid=0AXVUZ5xw6XpKZGNuZG56a3FfMzU0Z2NyZnF4Zmo&hl=en
+        float stiffness = 60.0f;//200=f1 car
+        float compValue = .3f; //(should be lower than damp)
+        float dampValue = .4f;
+        vehicle.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
+        vehicle.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
+        vehicle.setSuspensionStiffness(stiffness);
+        vehicle.setMaxSuspensionForce(10000.0f);
+
+        //Create four wheels and add them at their locations
+        Vector3f wheelDirection = new Vector3f(0, -1, 0); // was 0, -1, 0
+        Vector3f wheelAxle = new Vector3f(-1, 0, 0); // was -1, 0, 0
+        float radius = 0.5f;
+        float restLength = 0.3f;
+        float yOff = 0.5f;
+        float xOff = 1f;
+        float zOff = 2f;
+
+        Cylinder wheelMesh = new Cylinder(16, 16, radius, radius * 0.6f, true);
+
+        Node node1 = new Node("wheel 1 node");
+        Geometry wheels1 = new Geometry("wheel 1", wheelMesh);
+        node1.attachChild(wheels1);
+        wheels1.rotate(0, FastMath.HALF_PI, 0);
+        wheels1.setMaterial(mat);
+        vehicle.addWheel(node1, new Vector3f(-xOff, yOff, zOff),
+                wheelDirection, wheelAxle, restLength, radius, true);
+
+        Node node2 = new Node("wheel 2 node");
+        Geometry wheels2 = new Geometry("wheel 2", wheelMesh);
+        node2.attachChild(wheels2);
+        wheels2.rotate(0, FastMath.HALF_PI, 0);
+        wheels2.setMaterial(mat);
+        vehicle.addWheel(node2, new Vector3f(xOff, yOff, zOff),
+                wheelDirection, wheelAxle, restLength, radius, true);
+
+        Node node3 = new Node("wheel 3 node");
+        Geometry wheels3 = new Geometry("wheel 3", wheelMesh);
+        node3.attachChild(wheels3);
+        wheels3.rotate(0, FastMath.HALF_PI, 0);
+        wheels3.setMaterial(mat);
+        vehicle.addWheel(node3, new Vector3f(-xOff, yOff, -zOff),
+                wheelDirection, wheelAxle, restLength, radius, false);
+
+        Node node4 = new Node("wheel 4 node");
+        Geometry wheels4 = new Geometry("wheel 4", wheelMesh);
+        node4.attachChild(wheels4);
+        wheels4.rotate(0, FastMath.HALF_PI, 0);
+        wheels4.setMaterial(mat);
+        vehicle.addWheel(node4, new Vector3f(xOff, yOff, -zOff),
+                wheelDirection, wheelAxle, restLength, radius, false);
+
+        vehicleNode.attachChild(node1);
+        vehicleNode.attachChild(node2);
+        vehicleNode.attachChild(node3);
+        vehicleNode.attachChild(node4);
+
+        rootNode.attachChild(vehicleNode);
+        getPhysicsSpace().add(vehicle);
+
+        //driver
+        Node driverNode=new Node("driverNode");
+        driverNode.setLocalTranslation(0,2,0);
+        driver=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,.5f,0.2f)));
+        driverNode.addControl(driver);
+
+        rootNode.attachChild(driverNode);
+        getPhysicsSpace().add(driver);
+
+        //joint
+        slider=new SliderJoint(driver, vehicle, Vector3f.UNIT_Y.negate(), Vector3f.UNIT_Y, true);
+        slider.setUpperLinLimit(.1f);
+        slider.setLowerLinLimit(-.1f);
+
+        getPhysicsSpace().add(slider);
+
+        Node pole1Node=new Node("pole1Node");
+        Node pole2Node=new Node("pole1Node");
+        Node bridgeNode=new Node("pole1Node");
+        pole1Node.setLocalTranslation(new Vector3f(-2,-1,4));
+        pole2Node.setLocalTranslation(new Vector3f(2,-1,4));
+        bridgeNode.setLocalTranslation(new Vector3f(0,1.4f,4));
+
+        RigidBodyControl pole1=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,1.25f,0.2f)),0);
+        pole1Node.addControl(pole1);
+        RigidBodyControl pole2=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,1.25f,0.2f)),0);
+        pole2Node.addControl(pole2);
+        bridge=new RigidBodyControl(new BoxCollisionShape(new Vector3f(2.5f,0.2f,0.2f)));
+        bridgeNode.addControl(bridge);
+
+        rootNode.attachChild(pole1Node);
+        rootNode.attachChild(pole2Node);
+        rootNode.attachChild(bridgeNode);
+        getPhysicsSpace().add(pole1);
+        getPhysicsSpace().add(pole2);
+        getPhysicsSpace().add(bridge);
+
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        Quaternion quat=new Quaternion();
+        cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y);
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Lefts")) {
+            if (value) {
+                steeringValue += .5f;
+            } else {
+                steeringValue += -.5f;
+            }
+            vehicle.steer(steeringValue);
+        } else if (binding.equals("Rights")) {
+            if (value) {
+                steeringValue += -.5f;
+            } else {
+                steeringValue += .5f;
+            }
+            vehicle.steer(steeringValue);
+        } else if (binding.equals("Ups")) {
+            if (value) {
+                accelerationValue += accelerationForce;
+            } else {
+                accelerationValue -= accelerationForce;
+            }
+            vehicle.accelerate(accelerationValue);
+        } else if (binding.equals("Downs")) {
+            if (value) {
+                vehicle.brake(brakeForce);
+            } else {
+                vehicle.brake(0f);
+            }
+        } else if (binding.equals("Space")) {
+            if (value) {
+                getPhysicsSpace().remove(slider);
+                slider.destroy();
+                vehicle.applyImpulse(jumpForce, Vector3f.ZERO);
+            }
+        } else if (binding.equals("Reset")) {
+            if (value) {
+                System.out.println("Reset");
+                vehicle.setPhysicsLocation(new Vector3f(0, 0, 0));
+                vehicle.setPhysicsRotation(new Matrix3f());
+                vehicle.setLinearVelocity(Vector3f.ZERO);
+                vehicle.setAngularVelocity(Vector3f.ZERO);
+                vehicle.resetSuspension();
+                bridge.setPhysicsLocation(new Vector3f(0,1.4f,4));
+                bridge.setPhysicsRotation(Quaternion.DIRECTION_Z.toRotationMatrix());
+            }
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestAttachGhostObject.java b/engine/src/test/jme3test/bullet/TestAttachGhostObject.java
new file mode 100644
index 0000000..2a117f2
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestAttachGhostObject.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.GhostControl;
+import com.jme3.bullet.control.PhysicsControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.HingeJoint;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+/**
+ * Tests attaching ghost nodes to physicsnodes via the scenegraph
+ * @author normenhansen
+ */
+public class TestAttachGhostObject extends SimpleApplication implements AnalogListener {
+
+    private HingeJoint joint;
+    private GhostControl ghostControl;
+    private Node collisionNode;
+    private Node hammerNode;
+    private Vector3f tempVec = new Vector3f();
+    private BulletAppState bulletAppState;
+
+    public static void main(String[] args) {
+        TestAttachGhostObject app = new TestAttachGhostObject();
+        app.start();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this, "Lefts", "Rights", "Space");
+    }
+
+    public void onAnalog(String binding, float value, float tpf) {
+        if (binding.equals("Lefts")) {
+            joint.enableMotor(true, 1, .1f);
+        } else if (binding.equals("Rights")) {
+            joint.enableMotor(true, -1, .1f);
+        } else if (binding.equals("Space")) {
+            joint.enableMotor(false, 0, 0);
+        }
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        setupKeys();
+        setupJoint();
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    public void setupJoint() {
+        Node holderNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.1f, .1f, .1f)), 0);
+        holderNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, 0, 0f));
+        rootNode.attachChild(holderNode);
+        getPhysicsSpace().add(holderNode);
+
+        Node hammerNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.3f, .3f, .3f)), 1);
+        hammerNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -1, 0f));
+        rootNode.attachChild(hammerNode);
+        getPhysicsSpace().add(hammerNode);
+
+        //immovable
+        collisionNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.3f, .3f, .3f)), 0);
+        collisionNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1.8f, 0, 0f));
+        rootNode.attachChild(collisionNode);
+        getPhysicsSpace().add(collisionNode);
+
+        //ghost node
+        ghostControl = new GhostControl(new SphereCollisionShape(0.7f));
+
+        hammerNode.addControl(ghostControl);
+        getPhysicsSpace().add(ghostControl);
+
+        joint = new HingeJoint(holderNode.getControl(RigidBodyControl.class), hammerNode.getControl(RigidBodyControl.class), Vector3f.ZERO, new Vector3f(0f, -1, 0f), Vector3f.UNIT_Z, Vector3f.UNIT_Z);
+        getPhysicsSpace().add(joint);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        if (ghostControl.getOverlappingObjects().contains(collisionNode.getControl(PhysicsControl.class))) {
+            fpsText.setText("collide");
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestBoneRagdoll.java b/engine/src/test/jme3test/bullet/TestBoneRagdoll.java
new file mode 100644
index 0000000..cf62ca2
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestBoneRagdoll.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.animation.*;
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionEvent;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.RagdollCollisionListener;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.KinematicRagdollControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.debug.SkeletonDebugger;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Sphere.TextureMode;
+import com.jme3.texture.Texture;
+
+/**
+ * PHYSICS RAGDOLLS ARE NOT WORKING PROPERLY YET!
+ * @author normenhansen
+ */
+public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisionListener, AnimEventListener {
+
+    private BulletAppState bulletAppState;
+    Material matBullet;
+    Node model;
+    KinematicRagdollControl ragdoll;
+    float bulletSize = 1f;
+    Material mat;
+    Material mat3;
+    private Sphere bullet;
+    private SphereCollisionShape bulletCollisionShape;
+
+    public static void main(String[] args) {
+        TestBoneRagdoll app = new TestBoneRagdoll();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        initCrossHairs();
+        initMaterial();
+
+        cam.setLocation(new Vector3f(0.26924422f, 6.646658f, 22.265987f));
+        cam.setRotation(new Quaternion(-2.302544E-4f, 0.99302495f, -0.117888905f, -0.0019395084f));
+
+
+        bulletAppState = new BulletAppState();
+        bulletAppState.setEnabled(true);
+        stateManager.attach(bulletAppState);
+        bullet = new Sphere(32, 32, 1.0f, true, false);
+        bullet.setTextureMode(TextureMode.Projected);
+        bulletCollisionShape = new SphereCollisionShape(1.0f);
+
+//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
+        setupLight();
+
+        model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
+
+        //  model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X));
+
+        //debug view
+        AnimControl control = model.getControl(AnimControl.class);
+        SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton());
+        Material mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.getAdditionalRenderState().setWireframe(true);
+        mat2.setColor("Color", ColorRGBA.Green);
+        mat2.getAdditionalRenderState().setDepthTest(false);
+        skeletonDebug.setMaterial(mat2);
+        skeletonDebug.setLocalTranslation(model.getLocalTranslation());
+
+        //Note: PhysicsRagdollControl is still TODO, constructor will change
+        ragdoll = new KinematicRagdollControl(0.5f);
+        setupSinbad(ragdoll);
+        ragdoll.addCollisionListener(this);
+        model.addControl(ragdoll);
+
+        float eighth_pi = FastMath.PI * 0.125f;
+        ragdoll.setJointLimit("Waist", eighth_pi, eighth_pi, eighth_pi, eighth_pi, eighth_pi, eighth_pi);
+        ragdoll.setJointLimit("Chest", eighth_pi, eighth_pi, 0, 0, eighth_pi, eighth_pi);
+
+
+        //Oto's head is almost rigid
+        //    ragdoll.setJointLimit("head", 0, 0, eighth_pi, -eighth_pi, 0, 0);
+
+        getPhysicsSpace().add(ragdoll);
+        speed = 1.3f;
+
+        rootNode.attachChild(model);
+        // rootNode.attachChild(skeletonDebug);
+        flyCam.setMoveSpeed(50);
+
+
+        animChannel = control.createChannel();
+        animChannel.setAnim("Dance");
+        control.addListener(this);
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("toggle") && isPressed) {
+
+                    Vector3f v = new Vector3f();
+                    v.set(model.getLocalTranslation());
+                    v.y = 0;
+                    model.setLocalTranslation(v);
+                    Quaternion q = new Quaternion();
+                    float[] angles = new float[3];
+                    model.getLocalRotation().toAngles(angles);
+                    q.fromAngleAxis(angles[1], Vector3f.UNIT_Y);
+                    model.setLocalRotation(q);
+                    if (angles[0] < 0) {
+                        animChannel.setAnim("StandUpBack");
+                        ragdoll.blendToKinematicMode(0.5f);
+                    } else {
+                        animChannel.setAnim("StandUpFront");
+                        ragdoll.blendToKinematicMode(0.5f);
+                    }
+
+                }
+                if (name.equals("bullet+") && isPressed) {
+                    bulletSize += 0.1f;
+
+                }
+                if (name.equals("bullet-") && isPressed) {
+                    bulletSize -= 0.1f;
+
+                }
+
+                if (name.equals("stop") && isPressed) {
+                    ragdoll.setEnabled(!ragdoll.isEnabled());
+                    ragdoll.setRagdollMode();
+                }
+
+                if (name.equals("shoot") && !isPressed) {
+                    Geometry bulletg = new Geometry("bullet", bullet);
+                    bulletg.setMaterial(matBullet);
+                    bulletg.setLocalTranslation(cam.getLocation());
+                    bulletg.setLocalScale(bulletSize);
+                    bulletCollisionShape = new SphereCollisionShape(bulletSize);
+                    RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, bulletSize * 10);
+                    bulletNode.setCcdMotionThreshold(0.001f);
+                    bulletNode.setLinearVelocity(cam.getDirection().mult(80));
+                    bulletg.addControl(bulletNode);
+                    rootNode.attachChild(bulletg);
+                    getPhysicsSpace().add(bulletNode);
+                }
+                if (name.equals("boom") && !isPressed) {
+                    Geometry bulletg = new Geometry("bullet", bullet);
+                    bulletg.setMaterial(matBullet);
+                    bulletg.setLocalTranslation(cam.getLocation());
+                    bulletg.setLocalScale(bulletSize);
+                    bulletCollisionShape = new SphereCollisionShape(bulletSize);
+                    BombControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);
+                    bulletNode.setForceFactor(8);
+                    bulletNode.setExplosionRadius(20);
+                    bulletNode.setCcdMotionThreshold(0.001f);
+                    bulletNode.setLinearVelocity(cam.getDirection().mult(180));
+                    bulletg.addControl(bulletNode);
+                    rootNode.attachChild(bulletg);
+                    getPhysicsSpace().add(bulletNode);
+                }
+            }
+        }, "toggle", "shoot", "stop", "bullet+", "bullet-", "boom");
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addMapping("boom", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+        inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("bullet-", new KeyTrigger(KeyInput.KEY_COMMA));
+        inputManager.addMapping("bullet+", new KeyTrigger(KeyInput.KEY_PERIOD));
+
+
+    }
+
+    private void setupLight() {
+        // AmbientLight al = new AmbientLight();
+        //  al.setColor(ColorRGBA.White.mult(1));
+        //   rootNode.addLight(al);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    public void initMaterial() {
+
+        matBullet = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
+        key2.setGenerateMips(true);
+        Texture tex2 = assetManager.loadTexture(key2);
+        matBullet.setTexture("ColorMap", tex2);
+    }
+
+    protected void initCrossHairs() {
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+
+    public void collide(Bone bone, PhysicsCollisionObject object, PhysicsCollisionEvent event) {
+
+        if (object.getUserObject() != null && object.getUserObject() instanceof Geometry) {
+            Geometry geom = (Geometry) object.getUserObject();
+            if ("Floor".equals(geom.getName())) {
+                return;
+            }
+        }
+
+        ragdoll.setRagdollMode();
+
+    }
+
+    private void setupSinbad(KinematicRagdollControl ragdoll) {
+        ragdoll.addBoneName("Ulna.L");
+        ragdoll.addBoneName("Ulna.R");
+        ragdoll.addBoneName("Chest");
+        ragdoll.addBoneName("Foot.L");
+        ragdoll.addBoneName("Foot.R");
+        ragdoll.addBoneName("Hand.R");
+        ragdoll.addBoneName("Hand.L");
+        ragdoll.addBoneName("Neck");
+        ragdoll.addBoneName("Root");
+        ragdoll.addBoneName("Stomach");
+        ragdoll.addBoneName("Waist");
+        ragdoll.addBoneName("Humerus.L");
+        ragdoll.addBoneName("Humerus.R");
+        ragdoll.addBoneName("Thigh.L");
+        ragdoll.addBoneName("Thigh.R");
+        ragdoll.addBoneName("Calf.L");
+        ragdoll.addBoneName("Calf.R");
+        ragdoll.addBoneName("Clavicle.L");
+        ragdoll.addBoneName("Clavicle.R");
+
+    }
+    float elTime = 0;
+    boolean forward = true;
+    AnimControl animControl;
+    AnimChannel animChannel;
+    Vector3f direction = new Vector3f(0, 0, 1);
+    Quaternion rotate = new Quaternion().fromAngleAxis(FastMath.PI / 8, Vector3f.UNIT_Y);
+    boolean dance = true;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        // System.out.println(((BoundingBox) model.getWorldBound()).getYExtent());
+//        elTime += tpf;
+//        if (elTime > 3) {
+//            elTime = 0;
+//            if (dance) {
+//                rotate.multLocal(direction);
+//            }
+//            if (Math.random() > 0.80) {
+//                dance = true;
+//                animChannel.setAnim("Dance");
+//            } else {
+//                dance = false;
+//                animChannel.setAnim("RunBase");
+//                rotate.fromAngleAxis(FastMath.QUARTER_PI * ((float) Math.random() - 0.5f), Vector3f.UNIT_Y);
+//                rotate.multLocal(direction);
+//            }
+//        }
+//        if (!ragdoll.hasControl() && !dance) {
+//            if (model.getLocalTranslation().getZ() < -10) {
+//                direction.z = 1;
+//                direction.normalizeLocal();
+//            } else if (model.getLocalTranslation().getZ() > 10) {
+//                direction.z = -1;
+//                direction.normalizeLocal();
+//            }
+//            if (model.getLocalTranslation().getX() < -10) {
+//                direction.x = 1;
+//                direction.normalizeLocal();
+//            } else if (model.getLocalTranslation().getX() > 10) {
+//                direction.x = -1;
+//                direction.normalizeLocal();
+//            }
+//            model.move(direction.multLocal(tpf * 8));
+//            direction.normalizeLocal();
+//            model.lookAt(model.getLocalTranslation().add(direction), Vector3f.UNIT_Y);
+//        }
+    }
+
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+//        if(channel.getAnimationName().equals("StandUpFront")){
+//            channel.setAnim("Dance");
+//        }
+
+        if (channel.getAnimationName().equals("StandUpBack") || channel.getAnimationName().equals("StandUpFront")) {
+            channel.setLoopMode(LoopMode.DontLoop);
+            channel.setAnim("IdleTop", 5);
+            channel.setLoopMode(LoopMode.Loop);
+        }
+//        if(channel.getAnimationName().equals("IdleTop")){
+//            channel.setAnim("StandUpFront");
+//        }
+
+    }
+
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestBrickTower.java b/engine/src/test/jme3test/bullet/TestBrickTower.java
new file mode 100644
index 0000000..0e58714
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestBrickTower.java
@@ -0,0 +1,236 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package jme3test.bullet;
+
+/*
+ * Copyright (c) 2009-2010 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.
+ */
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.font.BitmapText;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Sphere.TextureMode;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.shadow.PssmShadowRenderer.CompareMode;
+import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+
+/**
+ *
+ * @author double1984 (tower mod by atom)
+ */
+public class TestBrickTower extends SimpleApplication {
+
+    int bricksPerLayer = 8;
+    int brickLayers = 30;
+
+    static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f;
+    float radius = 3f;
+    float angle = 0;
+
+
+    Material mat;
+    Material mat2;
+    Material mat3;
+    PssmShadowRenderer bsr;
+    private Sphere bullet;
+    private Box brick;
+    private SphereCollisionShape bulletCollisionShape;
+
+    private BulletAppState bulletAppState;
+
+    public static void main(String args[]) {
+        TestBrickTower f = new TestBrickTower();
+        f.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+     //   bulletAppState.setEnabled(false);
+        stateManager.attach(bulletAppState);
+        bullet = new Sphere(32, 32, 0.4f, true, false);
+        bullet.setTextureMode(TextureMode.Projected);
+        bulletCollisionShape = new SphereCollisionShape(0.4f);
+
+        brick = new Box(Vector3f.ZERO, brickWidth, brickHeight, brickDepth);
+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
+        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        initMaterial();
+        initTower();
+        initFloor();
+        initCrossHairs();
+        this.cam.setLocation(new Vector3f(0, 25f, 8f));
+        cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
+        cam.setFrustumFar(80);
+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(actionListener, "shoot");
+        rootNode.setShadowMode(ShadowMode.Off);
+        bsr = new PssmShadowRenderer(assetManager, 1024, 2);
+        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        bsr.setLambda(0.55f);
+        bsr.setShadowIntensity(0.6f);
+        bsr.setCompareMode(CompareMode.Hardware);
+        bsr.setFilterMode(FilterMode.PCF4);
+        viewPort.addProcessor(bsr);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean keyPressed, float tpf) {
+            if (name.equals("shoot") && !keyPressed) {
+                Geometry bulletg = new Geometry("bullet", bullet);
+                bulletg.setMaterial(mat2);
+                bulletg.setShadowMode(ShadowMode.CastAndReceive);
+                bulletg.setLocalTranslation(cam.getLocation());
+                RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);
+//                RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1);
+                bulletNode.setLinearVelocity(cam.getDirection().mult(25));
+                bulletg.addControl(bulletNode);
+                rootNode.attachChild(bulletg);
+                getPhysicsSpace().add(bulletNode);
+            }
+        }
+    };
+
+    public void initTower() {
+        double tempX = 0;
+        double tempY = 0;
+        double tempZ = 0;
+        angle = 0f;
+        for (int i = 0; i < brickLayers; i++){
+            // Increment rows
+            if(i!=0)
+                tempY+=brickHeight*2;
+            else
+                tempY=brickHeight;
+            // Alternate brick seams
+            angle = 360.0f / bricksPerLayer * i/2f;
+            for (int j = 0; j < bricksPerLayer; j++){
+              tempZ = Math.cos(Math.toRadians(angle))*radius;
+              tempX = Math.sin(Math.toRadians(angle))*radius;
+              System.out.println("x="+((float)(tempX))+" y="+((float)(tempY))+" z="+(float)(tempZ));
+              Vector3f vt = new Vector3f((float)(tempX), (float)(tempY), (float)(tempZ));
+              // Add crenelation
+              if (i==brickLayers-1){
+                if (j%2 == 0){
+                    addBrick(vt);
+                }
+              }
+              // Create main tower
+              else {
+                addBrick(vt);
+              }
+              angle += 360.0/bricksPerLayer;
+            }
+          }
+
+    }
+
+    public void initFloor() {
+        Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
+        floorBox.scaleTextureCoordinates(new Vector2f(3, 6));
+
+        Geometry floor = new Geometry("floor", floorBox);
+        floor.setMaterial(mat3);
+        floor.setShadowMode(ShadowMode.Receive);
+        floor.setLocalTranslation(0, 0, 0);
+        floor.addControl(new RigidBodyControl(0));
+        this.rootNode.attachChild(floor);
+        this.getPhysicsSpace().add(floor);
+    }
+
+    public void initMaterial() {
+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        mat.setTexture("ColorMap", tex);
+
+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
+        key2.setGenerateMips(true);
+        Texture tex2 = assetManager.loadTexture(key2);
+        mat2.setTexture("ColorMap", tex2);
+
+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
+        key3.setGenerateMips(true);
+        Texture tex3 = assetManager.loadTexture(key3);
+        tex3.setWrap(WrapMode.Repeat);
+        mat3.setTexture("ColorMap", tex3);
+    }
+
+    public void addBrick(Vector3f ori) {
+        Geometry reBoxg = new Geometry("brick", brick);
+        reBoxg.setMaterial(mat);
+        reBoxg.setLocalTranslation(ori);
+        reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f );
+        reBoxg.addControl(new RigidBodyControl(1.5f));
+        reBoxg.setShadowMode(ShadowMode.CastAndReceive);
+        reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f);
+        this.rootNode.attachChild(reBoxg);
+        this.getPhysicsSpace().add(reBoxg);
+    }
+
+    protected void initCrossHairs() {
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/bullet/TestBrickWall.java b/engine/src/test/jme3test/bullet/TestBrickWall.java
new file mode 100644
index 0000000..1b9a7d7
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestBrickWall.java
@@ -0,0 +1,210 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.asset.TextureKey;

+import com.jme3.bullet.BulletAppState;

+import com.jme3.bullet.PhysicsSpace;

+import com.jme3.bullet.collision.shapes.BoxCollisionShape;

+import com.jme3.bullet.collision.shapes.SphereCollisionShape;

+import com.jme3.bullet.control.RigidBodyControl;

+import com.jme3.font.BitmapText;

+import com.jme3.input.KeyInput;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.KeyTrigger;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.material.Material;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.queue.RenderQueue.ShadowMode;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.shape.Box;

+import com.jme3.scene.shape.Sphere;

+import com.jme3.scene.shape.Sphere.TextureMode;

+import com.jme3.shadow.BasicShadowRenderer;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.WrapMode;

+

+/**

+ *

+ * @author double1984

+ */

+public class TestBrickWall extends SimpleApplication {

+

+    static float bLength = 0.48f;

+    static float bWidth = 0.24f;

+    static float bHeight = 0.12f;

+    Material mat;

+    Material mat2;

+    Material mat3;

+    BasicShadowRenderer bsr;

+    private static Sphere bullet;

+    private static Box brick;

+    private static SphereCollisionShape bulletCollisionShape;

+

+    private BulletAppState bulletAppState;

+

+    public static void main(String args[]) {

+        TestBrickWall f = new TestBrickWall();

+        f.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+        

+        bulletAppState = new BulletAppState();

+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);

+        stateManager.attach(bulletAppState);

+

+        bullet = new Sphere(32, 32, 0.4f, true, false);

+        bullet.setTextureMode(TextureMode.Projected);

+        bulletCollisionShape = new SphereCollisionShape(0.4f);

+        brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);

+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));

+

+        initMaterial();

+        initWall();

+        initFloor();

+        initCrossHairs();

+        this.cam.setLocation(new Vector3f(0, 6f, 6f));

+        cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));

+        cam.setFrustumFar(15);

+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+        inputManager.addListener(actionListener, "shoot");

+        inputManager.addMapping("gc", new KeyTrigger(KeyInput.KEY_X));

+        inputManager.addListener(actionListener, "gc");

+

+        rootNode.setShadowMode(ShadowMode.Off);

+        bsr = new BasicShadowRenderer(assetManager, 256);

+        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());

+        viewPort.addProcessor(bsr);

+    }

+

+    private PhysicsSpace getPhysicsSpace() {

+        return bulletAppState.getPhysicsSpace();

+    }

+    private ActionListener actionListener = new ActionListener() {

+

+        public void onAction(String name, boolean keyPressed, float tpf) {

+            if (name.equals("shoot") && !keyPressed) {

+                Geometry bulletg = new Geometry("bullet", bullet);

+                bulletg.setMaterial(mat2);

+                bulletg.setShadowMode(ShadowMode.CastAndReceive);

+                bulletg.setLocalTranslation(cam.getLocation());

+                

+                SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.4f);

+                RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);

+//                RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1);

+                bulletNode.setLinearVelocity(cam.getDirection().mult(25));

+                bulletg.addControl(bulletNode);

+                rootNode.attachChild(bulletg);

+                getPhysicsSpace().add(bulletNode);

+            }

+            if (name.equals("gc") && !keyPressed) {

+                System.gc();

+            }

+        }

+    };

+

+    public void initWall() {

+        float startpt = bLength / 4;

+        float height = 0;

+        for (int j = 0; j < 15; j++) {

+            for (int i = 0; i < 4; i++) {

+                Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, 0);

+                addBrick(vt);

+            }

+            startpt = -startpt;

+            height += 2 * bHeight;

+        }

+    }

+

+    public void initFloor() {

+        Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);

+        floorBox.scaleTextureCoordinates(new Vector2f(3, 6));

+

+        Geometry floor = new Geometry("floor", floorBox);

+        floor.setMaterial(mat3);

+        floor.setShadowMode(ShadowMode.Receive);

+        floor.setLocalTranslation(0, -0.1f, 0);

+        floor.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)), 0));

+        this.rootNode.attachChild(floor);

+        this.getPhysicsSpace().add(floor);

+    }

+

+    public void initMaterial() {

+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");

+        key.setGenerateMips(true);

+        Texture tex = assetManager.loadTexture(key);

+        mat.setTexture("ColorMap", tex);

+

+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");

+        key2.setGenerateMips(true);

+        Texture tex2 = assetManager.loadTexture(key2);

+        mat2.setTexture("ColorMap", tex2);

+

+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+        TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");

+        key3.setGenerateMips(true);

+        Texture tex3 = assetManager.loadTexture(key3);

+        tex3.setWrap(WrapMode.Repeat);

+        mat3.setTexture("ColorMap", tex3);

+    }

+

+    public void addBrick(Vector3f ori) {

+

+        Geometry reBoxg = new Geometry("brick", brick);

+        reBoxg.setMaterial(mat);

+        reBoxg.setLocalTranslation(ori);

+        //for geometry with sphere mesh the physics system automatically uses a sphere collision shape

+        reBoxg.addControl(new RigidBodyControl(1.5f));

+        reBoxg.setShadowMode(ShadowMode.CastAndReceive);

+        reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f);

+        this.rootNode.attachChild(reBoxg);

+        this.getPhysicsSpace().add(reBoxg);

+    }

+

+    protected void initCrossHairs() {

+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

+        BitmapText ch = new BitmapText(guiFont, false);

+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

+        ch.setText("+"); // crosshairs

+        ch.setLocalTranslation( // center

+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,

+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

+        guiNode.attachChild(ch);

+    }

+}

diff --git a/engine/src/test/jme3test/bullet/TestCcd.java b/engine/src/test/jme3test/bullet/TestCcd.java
new file mode 100644
index 0000000..1e9949a
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestCcd.java
@@ -0,0 +1,152 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.bullet.BulletAppState;

+import com.jme3.bullet.PhysicsSpace;

+import com.jme3.bullet.collision.shapes.BoxCollisionShape;

+import com.jme3.bullet.collision.shapes.MeshCollisionShape;

+import com.jme3.bullet.collision.shapes.SphereCollisionShape;

+import com.jme3.bullet.control.RigidBodyControl;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.RenderManager;

+import com.jme3.renderer.queue.RenderQueue.ShadowMode;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.shape.Box;

+import com.jme3.scene.shape.Sphere;

+import com.jme3.scene.shape.Sphere.TextureMode;

+

+/**

+ *

+ * @author normenhansen

+ */

+public class TestCcd extends SimpleApplication implements ActionListener {

+

+    private Material mat;

+    private Material mat2;

+    private Sphere bullet;

+    private SphereCollisionShape bulletCollisionShape;

+    private BulletAppState bulletAppState;

+

+    public static void main(String[] args) {

+        TestCcd app = new TestCcd();

+        app.start();

+    }

+

+    private void setupKeys() {

+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+        inputManager.addMapping("shoot2", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));

+        inputManager.addListener(this, "shoot");

+        inputManager.addListener(this, "shoot2");

+    }

+

+    @Override

+    public void simpleInitApp() {

+        bulletAppState = new BulletAppState();

+        stateManager.attach(bulletAppState);

+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

+        bullet = new Sphere(32, 32, 0.4f, true, false);

+        bullet.setTextureMode(TextureMode.Projected);

+        bulletCollisionShape = new SphereCollisionShape(0.1f);

+        setupKeys();

+

+        mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");

+        mat.getAdditionalRenderState().setWireframe(true);

+        mat.setColor("Color", ColorRGBA.Green);

+

+        mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");

+        mat2.getAdditionalRenderState().setWireframe(true);

+        mat2.setColor("Color", ColorRGBA.Red);

+

+        // An obstacle mesh, does not move (mass=0)

+        Node node2 = new Node();

+        node2.setName("mesh");

+        node2.setLocalTranslation(new Vector3f(2.5f, 0, 0f));

+        node2.addControl(new RigidBodyControl(new MeshCollisionShape(new Box(Vector3f.ZERO, 4, 4, 0.1f)), 0));

+        rootNode.attachChild(node2);

+        getPhysicsSpace().add(node2);

+

+        // The floor, does not move (mass=0)

+        Node node3 = new Node();

+        node3.setLocalTranslation(new Vector3f(0f, -6, 0f));

+        node3.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(100, 1, 100)), 0));

+        rootNode.attachChild(node3);

+        getPhysicsSpace().add(node3);

+

+    }

+

+    private PhysicsSpace getPhysicsSpace() {

+        return bulletAppState.getPhysicsSpace();

+    }

+

+    @Override

+    public void simpleUpdate(float tpf) {

+        //TODO: add update code

+    }

+

+    @Override

+    public void simpleRender(RenderManager rm) {

+        //TODO: add render code

+    }

+

+    public void onAction(String binding, boolean value, float tpf) {

+        if (binding.equals("shoot") && !value) {

+            Geometry bulletg = new Geometry("bullet", bullet);

+            bulletg.setMaterial(mat);

+            bulletg.setName("bullet");

+            bulletg.setLocalTranslation(cam.getLocation());

+            bulletg.setShadowMode(ShadowMode.CastAndReceive);

+            bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1));

+            bulletg.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f);

+            bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40));

+            rootNode.attachChild(bulletg);

+            getPhysicsSpace().add(bulletg);

+        } else if (binding.equals("shoot2") && !value) {

+            Geometry bulletg = new Geometry("bullet", bullet);

+            bulletg.setMaterial(mat2);

+            bulletg.setName("bullet");

+            bulletg.setLocalTranslation(cam.getLocation());

+            bulletg.setShadowMode(ShadowMode.CastAndReceive);

+            bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1));

+            bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40));

+            rootNode.attachChild(bulletg);

+            getPhysicsSpace().add(bulletg);

+        }

+    }

+}

diff --git a/engine/src/test/jme3test/bullet/TestCollisionGroups.java b/engine/src/test/jme3test/bullet/TestCollisionGroups.java
new file mode 100644
index 0000000..68b11d1
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestCollisionGroups.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class TestCollisionGroups extends SimpleApplication {
+
+    private BulletAppState bulletAppState;
+
+    public static void main(String[] args) {
+        TestCollisionGroups app = new TestCollisionGroups();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        
+        // Add a physics sphere to the world
+        Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0));
+        rootNode.attachChild(physicsSphere);
+        getPhysicsSpace().add(physicsSphere);
+
+        // Add a physics sphere to the world
+        Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0));
+        physicsSphere2.getControl(RigidBodyControl.class).addCollideWithGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
+        rootNode.attachChild(physicsSphere2);
+        getPhysicsSpace().add(physicsSphere2);
+
+        // an obstacle mesh, does not move (mass=0)
+        Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0);
+        node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f));
+        node2.getControl(RigidBodyControl.class).setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
+        node2.getControl(RigidBodyControl.class).setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_02);
+        rootNode.attachChild(node2);
+        getPhysicsSpace().add(node2);
+
+        // the floor, does not move (mass=0)
+        Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Box(Vector3f.ZERO, 100f, 0.2f, 100f)), 0);
+        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        rootNode.attachChild(node3);
+        getPhysicsSpace().add(node3);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        //TODO: add update code
+    }
+
+    @Override
+    public void simpleRender(RenderManager rm) {
+        //TODO: add render code
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestCollisionListener.java b/engine/src/test/jme3test/bullet/TestCollisionListener.java
new file mode 100644
index 0000000..bad8794
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestCollisionListener.java
@@ -0,0 +1,98 @@
+/*

+ * Copyright (c) 2009-2010 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.bullet;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.bullet.BulletAppState;

+import com.jme3.bullet.PhysicsSpace;

+import com.jme3.bullet.collision.PhysicsCollisionEvent;

+import com.jme3.bullet.collision.PhysicsCollisionListener;

+import com.jme3.bullet.collision.shapes.SphereCollisionShape;

+import com.jme3.renderer.RenderManager;

+import com.jme3.scene.shape.Sphere;

+import com.jme3.scene.shape.Sphere.TextureMode;

+

+/**

+ *

+ * @author normenhansen

+ */

+public class TestCollisionListener extends SimpleApplication implements PhysicsCollisionListener {

+

+    private BulletAppState bulletAppState;

+    private Sphere bullet;

+    private SphereCollisionShape bulletCollisionShape;

+

+    public static void main(String[] args) {

+        TestCollisionListener app = new TestCollisionListener();

+        app.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+        bulletAppState = new BulletAppState();

+        stateManager.attach(bulletAppState);

+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

+        bullet = new Sphere(32, 32, 0.4f, true, false);

+        bullet.setTextureMode(TextureMode.Projected);

+        bulletCollisionShape = new SphereCollisionShape(0.4f);

+

+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());

+        PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());

+

+        // add ourselves as collision listener

+        getPhysicsSpace().addCollisionListener(this);

+    }

+

+    private PhysicsSpace getPhysicsSpace(){

+        return bulletAppState.getPhysicsSpace();

+    }

+

+    @Override

+    public void simpleUpdate(float tpf) {

+        //TODO: add update code

+    }

+

+    @Override

+    public void simpleRender(RenderManager rm) {

+        //TODO: add render code

+    }

+

+    public void collision(PhysicsCollisionEvent event) {

+        if ("Box".equals(event.getNodeA().getName()) || "Box".equals(event.getNodeB().getName())) {

+            if ("bullet".equals(event.getNodeA().getName()) || "bullet".equals(event.getNodeB().getName())) {

+                fpsText.setText("You hit the box!");

+            }

+        }

+    }

+

+}

diff --git a/engine/src/test/jme3test/bullet/TestCollisionShapeFactory.java b/engine/src/test/jme3test/bullet/TestCollisionShapeFactory.java
new file mode 100644
index 0000000..3702091
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestCollisionShapeFactory.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Cylinder;
+import com.jme3.scene.shape.Torus;
+
+/**
+ * This is a basic Test of jbullet-jme functions
+ *
+ * @author normenhansen
+ */
+public class TestCollisionShapeFactory extends SimpleApplication {
+
+    private BulletAppState bulletAppState;
+    private Material mat1;
+    private Material mat2;
+    private Material mat3;
+
+    public static void main(String[] args) {
+        TestCollisionShapeFactory app = new TestCollisionShapeFactory();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        createMaterial();
+
+        Node node = new Node("node1");
+        attachRandomGeometry(node, mat1);
+        randomizeTransform(node);
+
+        Node node2 = new Node("node2");
+        attachRandomGeometry(node2, mat2);
+        randomizeTransform(node2);
+
+        node.attachChild(node2);
+        rootNode.attachChild(node);
+
+        RigidBodyControl control = new RigidBodyControl(0);
+        node.addControl(control);
+        getPhysicsSpace().add(control);
+
+        //test single geometry too
+        Geometry myGeom = new Geometry("cylinder", new Cylinder(16, 16, 0.5f, 1));
+        myGeom.setMaterial(mat3);
+        randomizeTransform(myGeom);
+        rootNode.attachChild(myGeom);
+        RigidBodyControl control3 = new RigidBodyControl(0);
+        myGeom.addControl(control3);
+        getPhysicsSpace().add(control3);
+    }
+
+    private void attachRandomGeometry(Node node, Material mat) {
+        Box box = new Box(0.25f, 0.25f, 0.25f);
+        Torus torus = new Torus(16, 16, 0.2f, 0.8f);
+        Geometry[] boxes = new Geometry[]{
+            new Geometry("box1", box),
+            new Geometry("box2", box),
+            new Geometry("box3", box),
+            new Geometry("torus1", torus),
+            new Geometry("torus2", torus),
+            new Geometry("torus3", torus)
+        };
+        for (int i = 0; i < boxes.length; i++) {
+            Geometry geometry = boxes[i];
+            geometry.setLocalTranslation((float) Math.random() * 10 -10, (float) Math.random() * 10 -10, (float) Math.random() * 10 -10);
+            geometry.setLocalRotation(new Quaternion().fromAngles((float) Math.random() * FastMath.PI, (float) Math.random() * FastMath.PI, (float) Math.random() * FastMath.PI));
+            geometry.setLocalScale((float) Math.random() * 10 -10, (float) Math.random() * 10 -10, (float) Math.random() * 10 -10);
+            geometry.setMaterial(mat);
+            node.attachChild(geometry);
+        }
+    }
+
+    private void randomizeTransform(Spatial spat){
+        spat.setLocalTranslation((float) Math.random() * 10, (float) Math.random() * 10, (float) Math.random() * 10);
+        spat.setLocalTranslation((float) Math.random() * 10, (float) Math.random() * 10, (float) Math.random() * 10);
+        spat.setLocalScale((float) Math.random() * 2, (float) Math.random() * 2, (float) Math.random() * 2);
+    }
+
+    private void createMaterial() {
+        mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setColor("Color", ColorRGBA.Green);
+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setColor("Color", ColorRGBA.Red);
+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat3.setColor("Color", ColorRGBA.Yellow);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestFancyCar.java b/engine/src/test/jme3test/bullet/TestFancyCar.java
new file mode 100644
index 0000000..9d9a6a5
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestFancyCar.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.VehicleControl;
+import com.jme3.bullet.objects.VehicleWheel;
+import com.jme3.bullet.util.CollisionShapeFactory;
+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.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.shadow.BasicShadowRenderer;
+
+public class TestFancyCar extends SimpleApplication implements ActionListener {
+
+    private BulletAppState bulletAppState;
+    private VehicleControl player;
+    private VehicleWheel fr, fl, br, bl;
+    private Node node_fr, node_fl, node_br, node_bl;
+    private float wheelRadius;
+    private float steeringValue = 0;
+    private float accelerationValue = 0;
+    private Node carNode;
+
+    public static void main(String[] args) {
+        TestFancyCar app = new TestFancyCar();
+        app.start();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "Lefts");
+        inputManager.addListener(this, "Rights");
+        inputManager.addListener(this, "Ups");
+        inputManager.addListener(this, "Downs");
+        inputManager.addListener(this, "Space");
+        inputManager.addListener(this, "Reset");
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        if (settings.getRenderer().startsWith("LWJGL")) {
+            BasicShadowRenderer bsr = new BasicShadowRenderer(assetManager, 512);
+            bsr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
+            viewPort.addProcessor(bsr);
+        }
+        cam.setFrustumFar(150f);
+        flyCam.setMoveSpeed(10);
+
+        setupKeys();
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
+//        setupFloor();
+        buildPlayer();
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal());
+        rootNode.addLight(dl);
+
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(0.5f, -0.1f, 0.3f).normalizeLocal());
+        rootNode.addLight(dl);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+//    public void setupFloor() {
+//        Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m");
+//        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
+////        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
+////        mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
+//
+//        Box floor = new Box(Vector3f.ZERO, 140, 1f, 140);
+//        floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f));
+//        Geometry floorGeom = new Geometry("Floor", floor);
+//        floorGeom.setShadowMode(ShadowMode.Receive);
+//        floorGeom.setMaterial(mat);
+//
+//        PhysicsNode tb = new PhysicsNode(floorGeom, new MeshCollisionShape(floorGeom.getMesh()), 0);
+//        tb.setLocalTranslation(new Vector3f(0f, -6, 0f));
+////        tb.attachDebugShape(assetManager);
+//        rootNode.attachChild(tb);
+//        getPhysicsSpace().add(tb);
+//    }
+
+    private Geometry findGeom(Spatial spatial, String name) {
+        if (spatial instanceof Node) {
+            Node node = (Node) spatial;
+            for (int i = 0; i < node.getQuantity(); i++) {
+                Spatial child = node.getChild(i);
+                Geometry result = findGeom(child, name);
+                if (result != null) {
+                    return result;
+                }
+            }
+        } else if (spatial instanceof Geometry) {
+            if (spatial.getName().startsWith(name)) {
+                return (Geometry) spatial;
+            }
+        }
+        return null;
+    }
+
+    private void buildPlayer() {
+        float stiffness = 120.0f;//200=f1 car
+        float compValue = 0.2f; //(lower than damp!)
+        float dampValue = 0.3f;
+        final float mass = 400;
+
+        //Load model and get chassis Geometry
+        carNode = (Node)assetManager.loadModel("Models/Ferrari/Car.scene");
+        carNode.setShadowMode(ShadowMode.Cast);
+        Geometry chasis = findGeom(carNode, "Car");
+        BoundingBox box = (BoundingBox) chasis.getModelBound();
+
+        //Create a hull collision shape for the chassis
+        CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);
+
+        //Create a vehicle control
+        player = new VehicleControl(carHull, mass);
+        carNode.addControl(player);
+
+        //Setting default values for wheels
+        player.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
+        player.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
+        player.setSuspensionStiffness(stiffness);
+        player.setMaxSuspensionForce(10000);
+
+        //Create four wheels and add them at their locations
+        //note that our fancy car actually goes backwards..
+        Vector3f wheelDirection = new Vector3f(0, -1, 0);
+        Vector3f wheelAxle = new Vector3f(-1, 0, 0);
+
+        Geometry wheel_fr = findGeom(carNode, "WheelFrontRight");
+        wheel_fr.center();
+        box = (BoundingBox) wheel_fr.getModelBound();
+        wheelRadius = box.getYExtent();
+        float back_wheel_h = (wheelRadius * 1.7f) - 1f;
+        float front_wheel_h = (wheelRadius * 1.9f) - 1f;
+        player.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
+                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);
+
+        Geometry wheel_fl = findGeom(carNode, "WheelFrontLeft");
+        wheel_fl.center();
+        box = (BoundingBox) wheel_fl.getModelBound();
+        player.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
+                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);
+
+        Geometry wheel_br = findGeom(carNode, "WheelBackRight");
+        wheel_br.center();
+        box = (BoundingBox) wheel_br.getModelBound();
+        player.addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
+                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
+
+        Geometry wheel_bl = findGeom(carNode, "WheelBackLeft");
+        wheel_bl.center();
+        box = (BoundingBox) wheel_bl.getModelBound();
+        player.addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
+                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
+
+        player.getWheel(2).setFrictionSlip(4);
+        player.getWheel(3).setFrictionSlip(4);
+
+        rootNode.attachChild(carNode);
+        getPhysicsSpace().add(player);
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Lefts")) {
+            if (value) {
+                steeringValue += .5f;
+            } else {
+                steeringValue += -.5f;
+            }
+            player.steer(steeringValue);
+        } else if (binding.equals("Rights")) {
+            if (value) {
+                steeringValue += -.5f;
+            } else {
+                steeringValue += .5f;
+            }
+            player.steer(steeringValue);
+        } //note that our fancy car actually goes backwards..
+        else if (binding.equals("Ups")) {
+            if (value) {
+                accelerationValue -= 800;
+            } else {
+                accelerationValue += 800;
+            }
+            player.accelerate(accelerationValue);
+            player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, "Car")));
+        } else if (binding.equals("Downs")) {
+            if (value) {
+                player.brake(40f);
+            } else {
+                player.brake(0f);
+            }
+        } else if (binding.equals("Reset")) {
+            if (value) {
+                System.out.println("Reset");
+                player.setPhysicsLocation(Vector3f.ZERO);
+                player.setPhysicsRotation(new Matrix3f());
+                player.setLinearVelocity(Vector3f.ZERO);
+                player.setAngularVelocity(Vector3f.ZERO);
+                player.resetSuspension();
+            } else {
+            }
+        }
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        cam.lookAt(carNode.getWorldTranslation(), Vector3f.UNIT_Y);
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestGhostObject.java b/engine/src/test/jme3test/bullet/TestGhostObject.java
new file mode 100644
index 0000000..9539379
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestGhostObject.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.GhostControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+
+/**
+ *
+ * @author tim8dev [at] gmail [dot com]
+ */
+public class TestGhostObject extends SimpleApplication {
+
+    private BulletAppState bulletAppState;
+    private GhostControl ghostControl;
+
+    public static void main(String[] args) {
+        Application app = new TestGhostObject();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+
+        // Mesh to be shared across several boxes.
+        Box boxGeom = new Box(Vector3f.ZERO, 1f, 1f, 1f);
+        // CollisionShape to be shared across several boxes.
+        CollisionShape shape = new BoxCollisionShape(new Vector3f(1, 1, 1));
+
+        Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, shape, 1);
+        physicsBox.setName("box0");
+        physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f));
+        rootNode.attachChild(physicsBox);
+        getPhysicsSpace().add(physicsBox);
+
+        Node physicsBox1 = PhysicsTestHelper.createPhysicsTestNode(assetManager, shape, 1);
+        physicsBox1.setName("box1");
+        physicsBox1.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0, 40, 0));
+        rootNode.attachChild(physicsBox1);
+        getPhysicsSpace().add(physicsBox1);
+
+        Node physicsBox2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1);
+        physicsBox2.setName("box0");
+        physicsBox2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.5f, 80, -.8f));
+        rootNode.attachChild(physicsBox2);
+        getPhysicsSpace().add(physicsBox2);
+
+        // the floor, does not move (mass=0)
+        Node node = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(100, 1, 100)), 0);
+        node.setName("floor");
+        node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        rootNode.attachChild(node);
+        getPhysicsSpace().add(node);
+
+        initGhostObject();
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void initGhostObject() {
+        Vector3f halfExtents = new Vector3f(3, 4.2f, 1);
+        ghostControl = new GhostControl(new BoxCollisionShape(halfExtents));
+        Node node=new Node("Ghost Object");
+        node.addControl(ghostControl);
+        rootNode.attachChild(node);
+        getPhysicsSpace().add(ghostControl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        fpsText.setText("Overlapping objects: " + ghostControl.getOverlappingObjects().toString());
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestHoveringTank.java b/engine/src/test/jme3test/bullet/TestHoveringTank.java
new file mode 100644
index 0000000..7e5fd4a
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestHoveringTank.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.util.CollisionShapeFactory;
+import com.jme3.font.BitmapText;
+import com.jme3.input.ChaseCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.shadow.PssmShadowRenderer.CompareMode;
+import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestHoveringTank extends SimpleApplication implements AnalogListener,
+        ActionListener {
+
+    private BulletAppState bulletAppState;
+    private PhysicsHoverControl hoverControl;
+    private Spatial spaceCraft;
+    TerrainQuad terrain;
+    Material matRock;
+    boolean wireframe = false;
+    protected BitmapText hintText;
+    PointLight pl;
+    Geometry lightMdl;
+    Geometry collisionMarker;
+
+    public static void main(String[] args) {
+        TestHoveringTank app = new TestHoveringTank();
+        app.start();
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "Lefts");
+        inputManager.addListener(this, "Rights");
+        inputManager.addListener(this, "Ups");
+        inputManager.addListener(this, "Downs");
+        inputManager.addListener(this, "Space");
+        inputManager.addListener(this, "Reset");
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+        stateManager.attach(bulletAppState);
+//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        bulletAppState.getPhysicsSpace().setAccuracy(1f/30f);
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        PssmShadowRenderer pssmr = new PssmShadowRenderer(assetManager, 2048, 3);
+        pssmr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
+        pssmr.setLambda(0.55f);
+        pssmr.setShadowIntensity(0.6f);
+        pssmr.setCompareMode(CompareMode.Hardware);
+        pssmr.setFilterMode(FilterMode.Bilinear);
+        viewPort.addProcessor(pssmr);
+
+        setupKeys();
+        createTerrain();
+        buildPlayer();
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(new ColorRGBA(1.0f, 0.94f, 0.8f, 1f).multLocal(1.3f));
+        dl.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal());
+        rootNode.addLight(dl);
+
+        Vector3f lightDir2 = new Vector3f(0.70518064f, 0.5902297f, -0.39287305f);
+        DirectionalLight dl2 = new DirectionalLight();
+        dl2.setColor(new ColorRGBA(0.7f, 0.85f, 1.0f, 1f));
+        dl2.setDirection(lightDir2);
+        rootNode.addLight(dl2);
+    }
+
+    private void buildPlayer() {
+        spaceCraft = assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml");
+        CollisionShape colShape = CollisionShapeFactory.createDynamicMeshShape(spaceCraft);
+        spaceCraft.setShadowMode(ShadowMode.CastAndReceive);
+        spaceCraft.setLocalTranslation(new Vector3f(-140, 14, -23));
+        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);
+
+        ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
+        spaceCraft.addControl(chaseCam);
+
+        flyCam.setEnabled(false);
+    }
+
+    public void makeMissile() {
+        Vector3f pos = spaceCraft.getWorldTranslation().clone();
+        Quaternion rot = spaceCraft.getWorldRotation();
+        Vector3f dir = rot.getRotationColumn(2);
+
+        Spatial missile = assetManager.loadModel("Models/SpaceCraft/Rocket.mesh.xml");
+        missile.scale(0.5f);
+        missile.rotate(0, FastMath.PI, 0);
+        missile.updateGeometricState();
+
+        BoundingBox box = (BoundingBox) missile.getWorldBound();
+        final Vector3f extent = box.getExtent(null);
+
+        BoxCollisionShape boxShape = new BoxCollisionShape(extent);
+
+        missile.setName("Missile");
+        missile.rotate(rot);
+        missile.setLocalTranslation(pos.addLocal(0, extent.y * 4.5f, 0));
+        missile.setLocalRotation(hoverControl.getPhysicsRotation());
+        missile.setShadowMode(ShadowMode.Cast);
+        RigidBodyControl control = new BombControl(assetManager, boxShape, 20);
+        control.setLinearVelocity(dir.mult(100));
+        control.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_03);
+        missile.addControl(control);
+
+
+        rootNode.attachChild(missile);
+        getPhysicsSpace().add(missile);
+    }
+
+    public void onAnalog(String binding, float value, float tpf) {
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Lefts")) {
+            hoverControl.steer(value ? 50f : 0);
+        } else if (binding.equals("Rights")) {
+            hoverControl.steer(value ? -50f : 0);
+        } else if (binding.equals("Ups")) {
+            hoverControl.accelerate(value ? 100f : 0);
+        } else if (binding.equals("Downs")) {
+            hoverControl.accelerate(value ? -100f : 0);
+        } else if (binding.equals("Reset")) {
+            if (value) {
+                System.out.println("Reset");
+                hoverControl.setPhysicsLocation(new Vector3f(-140, 14, -23));
+                hoverControl.setPhysicsRotation(new Matrix3f());
+                hoverControl.clearForces();
+            } else {
+            }
+        } else if (binding.equals("Space") && value) {
+            makeMissile();
+        }
+    }
+
+    public void updateCamera() {
+        rootNode.updateGeometricState();
+
+        Vector3f pos = spaceCraft.getWorldTranslation().clone();
+        Quaternion rot = spaceCraft.getWorldRotation();
+        Vector3f dir = rot.getRotationColumn(2);
+
+        // make it XZ only
+        Vector3f camPos = new Vector3f(dir);
+        camPos.setY(0);
+        camPos.normalizeLocal();
+
+        // negate and multiply by distance from object
+        camPos.negateLocal();
+        camPos.multLocal(15);
+
+        // add Y distance
+        camPos.setY(2);
+        camPos.addLocal(pos);
+        cam.setLocation(camPos);
+
+        Vector3f lookAt = new Vector3f(dir);
+        lookAt.multLocal(7); // look at dist
+        lookAt.addLocal(pos);
+        cam.lookAt(lookAt, Vector3f.UNIT_Y);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+    }
+
+    private void createTerrain() {
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matRock.setBoolean("useTriPlanarMapping", false);
+        matRock.setBoolean("WardIso", true);
+        matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap", grass);
+        matRock.setFloat("DiffuseMap_0_scale", 64);
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_1", dirt);
+        matRock.setFloat("DiffuseMap_1_scale", 16);
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_2", rock);
+        matRock.setFloat("DiffuseMap_2_scale", 128);
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matRock.setTexture("NormalMap", normalMap0);
+        matRock.setTexture("NormalMap_1", normalMap2);
+        matRock.setTexture("NormalMap_2", normalMap2);
+
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+            heightmap.load();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        List<Camera> cameras = new ArrayList<Camera>();
+        cameras.add(getCamera());
+        TerrainLodControl control = new TerrainLodControl(terrain, cameras);
+        terrain.addControl(control);
+        terrain.setMaterial(matRock);
+        terrain.setLocalScale(new Vector3f(2, 2, 2));
+        terrain.setLocked(false); // unlock it so we can edit the height
+
+        terrain.setShadowMode(ShadowMode.CastAndReceive);
+        terrain.addControl(new RigidBodyControl(0));
+        rootNode.attachChild(terrain);
+        getPhysicsSpace().addAll(terrain);
+
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java b/engine/src/test/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java
new file mode 100644
index 0000000..8947d8a
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java
@@ -0,0 +1,80 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.collision.shapes.PlaneCollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestKinematicAddToPhysicsSpaceIssue app = new TestKinematicAddToPhysicsSpaceIssue();
+        app.start();
+    }
+    BulletAppState bulletAppState;
+
+    @Override
+    public void simpleInitApp() {
+
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        // Add a physics sphere to the world
+        Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0));
+        rootNode.attachChild(physicsSphere);
+
+        //Setting the rigidBody to kinematic before adding it to the physic space
+        physicsSphere.getControl(RigidBodyControl.class).setKinematic(true);
+        //adding it to the physic space
+        getPhysicsSpace().add(physicsSphere);         
+        //Making it not kinematic again, it should fall under gravity, it doesn't
+        physicsSphere.getControl(RigidBodyControl.class).setKinematic(false);
+
+        // Add a physics sphere to the world using the collision shape from sphere one
+        Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(5, 6, 0));
+        rootNode.attachChild(physicsSphere2);
+        
+        //Adding the rigid body to physic space
+        getPhysicsSpace().add(physicsSphere2);
+        //making it kinematic
+        physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false);
+        //Making it not kinematic again, it works properly, the rigidbody is affected by grvity.
+        physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false);
+
+      
+
+        // an obstacle mesh, does not move (mass=0)
+        Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0);
+        node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f));
+        rootNode.attachChild(node2);
+        getPhysicsSpace().add(node2);
+
+        // the floor mesh, does not move (mass=0)
+        Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0);
+        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        rootNode.attachChild(node3);
+        getPhysicsSpace().add(node3);
+
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestLocalPhysics.java b/engine/src/test/jme3test/bullet/TestLocalPhysics.java
new file mode 100644
index 0000000..cba8f86
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestLocalPhysics.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.*;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ * This is a basic Test of jbullet-jme functions
+ *
+ * @author normenhansen
+ */
+public class TestLocalPhysics extends SimpleApplication {
+
+    private BulletAppState bulletAppState;
+
+    public static void main(String[] args) {
+        TestLocalPhysics app = new TestLocalPhysics();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+
+        // Add a physics sphere to the world
+        Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0));
+        physicsSphere.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(physicsSphere);
+        getPhysicsSpace().add(physicsSphere);
+
+        // Add a physics sphere to the world using the collision shape from sphere one
+        Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1);
+        physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0));
+        physicsSphere2.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(physicsSphere2);
+        getPhysicsSpace().add(physicsSphere2);
+
+        // Add a physics box to the world
+        Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1);
+        physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f);
+        physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f));
+        physicsBox.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(physicsBox);
+        getPhysicsSpace().add(physicsBox);
+
+        // Add a physics cylinder to the world
+        Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1);
+        physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0));
+        physicsCylinder.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(physicsCylinder);
+        getPhysicsSpace().add(physicsCylinder);
+
+        // an obstacle mesh, does not move (mass=0)
+        Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0);
+        node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f));
+        node2.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(node2);
+        getPhysicsSpace().add(node2);
+
+        // the floor mesh, does not move (mass=0)
+        Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0);
+        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        node3.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
+        rootNode.attachChild(node3);
+        getPhysicsSpace().add(node3);
+
+        // Join the physics objects with a Point2Point joint
+//        PhysicsPoint2PointJoint joint=new PhysicsPoint2PointJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0));
+//        PhysicsHingeJoint joint=new PhysicsHingeJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z);
+//        getPhysicsSpace().add(joint);
+
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        rootNode.rotate(tpf, 0, 0);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestPhysicsCar.java b/engine/src/test/jme3test/bullet/TestPhysicsCar.java
new file mode 100644
index 0000000..11847ba
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestPhysicsCar.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
+import com.jme3.bullet.control.VehicleControl;
+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.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Cylinder;
+
+public class TestPhysicsCar extends SimpleApplication implements ActionListener {
+
+    private BulletAppState bulletAppState;
+    private VehicleControl vehicle;
+    private final float accelerationForce = 1000.0f;
+    private final float brakeForce = 100.0f;
+    private float steeringValue = 0;
+    private float accelerationValue = 0;
+    private Vector3f jumpForce = new Vector3f(0, 3000, 0);
+
+    public static void main(String[] args) {
+        TestPhysicsCar app = new TestPhysicsCar();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
+        setupKeys();
+        buildPlayer();
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "Lefts");
+        inputManager.addListener(this, "Rights");
+        inputManager.addListener(this, "Ups");
+        inputManager.addListener(this, "Downs");
+        inputManager.addListener(this, "Space");
+        inputManager.addListener(this, "Reset");
+    }
+
+    private void buildPlayer() {
+        Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Red);
+
+        //create a compound shape and attach the BoxCollisionShape for the car body at 0,1,0
+        //this shifts the effective center of mass of the BoxCollisionShape to 0,-1,0
+        CompoundCollisionShape compoundShape = new CompoundCollisionShape();
+        BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.2f, 0.5f, 2.4f));
+        compoundShape.addChildShape(box, new Vector3f(0, 1, 0));
+
+        //create vehicle node
+        Node vehicleNode=new Node("vehicleNode");
+        vehicle = new VehicleControl(compoundShape, 400);
+        vehicleNode.addControl(vehicle);
+
+        //setting suspension values for wheels, this can be a bit tricky
+        //see also https://docs.google.com/Doc?docid=0AXVUZ5xw6XpKZGNuZG56a3FfMzU0Z2NyZnF4Zmo&hl=en
+        float stiffness = 60.0f;//200=f1 car
+        float compValue = .3f; //(should be lower than damp)
+        float dampValue = .4f;
+        vehicle.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
+        vehicle.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
+        vehicle.setSuspensionStiffness(stiffness);
+        vehicle.setMaxSuspensionForce(10000.0f);
+
+        //Create four wheels and add them at their locations
+        Vector3f wheelDirection = new Vector3f(0, -1, 0); // was 0, -1, 0
+        Vector3f wheelAxle = new Vector3f(-1, 0, 0); // was -1, 0, 0
+        float radius = 0.5f;
+        float restLength = 0.3f;
+        float yOff = 0.5f;
+        float xOff = 1f;
+        float zOff = 2f;
+
+        Cylinder wheelMesh = new Cylinder(16, 16, radius, radius * 0.6f, true);
+
+        Node node1 = new Node("wheel 1 node");
+        Geometry wheels1 = new Geometry("wheel 1", wheelMesh);
+        node1.attachChild(wheels1);
+        wheels1.rotate(0, FastMath.HALF_PI, 0);
+        wheels1.setMaterial(mat);
+        vehicle.addWheel(node1, new Vector3f(-xOff, yOff, zOff),
+                wheelDirection, wheelAxle, restLength, radius, true);
+
+        Node node2 = new Node("wheel 2 node");
+        Geometry wheels2 = new Geometry("wheel 2", wheelMesh);
+        node2.attachChild(wheels2);
+        wheels2.rotate(0, FastMath.HALF_PI, 0);
+        wheels2.setMaterial(mat);
+        vehicle.addWheel(node2, new Vector3f(xOff, yOff, zOff),
+                wheelDirection, wheelAxle, restLength, radius, true);
+
+        Node node3 = new Node("wheel 3 node");
+        Geometry wheels3 = new Geometry("wheel 3", wheelMesh);
+        node3.attachChild(wheels3);
+        wheels3.rotate(0, FastMath.HALF_PI, 0);
+        wheels3.setMaterial(mat);
+        vehicle.addWheel(node3, new Vector3f(-xOff, yOff, -zOff),
+                wheelDirection, wheelAxle, restLength, radius, false);
+
+        Node node4 = new Node("wheel 4 node");
+        Geometry wheels4 = new Geometry("wheel 4", wheelMesh);
+        node4.attachChild(wheels4);
+        wheels4.rotate(0, FastMath.HALF_PI, 0);
+        wheels4.setMaterial(mat);
+        vehicle.addWheel(node4, new Vector3f(xOff, yOff, -zOff),
+                wheelDirection, wheelAxle, restLength, radius, false);
+
+        vehicleNode.attachChild(node1);
+        vehicleNode.attachChild(node2);
+        vehicleNode.attachChild(node3);
+        vehicleNode.attachChild(node4);
+        rootNode.attachChild(vehicleNode);
+
+        getPhysicsSpace().add(vehicle);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y);
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Lefts")) {
+            if (value) {
+                steeringValue += .5f;
+            } else {
+                steeringValue += -.5f;
+            }
+            vehicle.steer(steeringValue);
+        } else if (binding.equals("Rights")) {
+            if (value) {
+                steeringValue += -.5f;
+            } else {
+                steeringValue += .5f;
+            }
+            vehicle.steer(steeringValue);
+        } else if (binding.equals("Ups")) {
+            if (value) {
+                accelerationValue += accelerationForce;
+            } else {
+                accelerationValue -= accelerationForce;
+            }
+            vehicle.accelerate(accelerationValue);
+        } else if (binding.equals("Downs")) {
+            if (value) {
+                vehicle.brake(brakeForce);
+            } else {
+                vehicle.brake(0f);
+            }
+        } else if (binding.equals("Space")) {
+            if (value) {
+                vehicle.applyImpulse(jumpForce, Vector3f.ZERO);
+            }
+        } else if (binding.equals("Reset")) {
+            if (value) {
+                System.out.println("Reset");
+                vehicle.setPhysicsLocation(Vector3f.ZERO);
+                vehicle.setPhysicsRotation(new Matrix3f());
+                vehicle.setLinearVelocity(Vector3f.ZERO);
+                vehicle.setAngularVelocity(Vector3f.ZERO);
+                vehicle.resetSuspension();
+            } else {
+            }
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestPhysicsCharacter.java b/engine/src/test/jme3test/bullet/TestPhysicsCharacter.java
new file mode 100644
index 0000000..c58d112
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestPhysicsCharacter.java
@@ -0,0 +1,207 @@
+/*

+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved. <p/>

+ * 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. <p/> * 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. <p/> * 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. <p/> 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.bullet;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.bullet.BulletAppState;

+import com.jme3.bullet.PhysicsSpace;

+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

+import com.jme3.bullet.control.CharacterControl;

+import com.jme3.input.KeyInput;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.KeyTrigger;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.math.Vector3f;

+import com.jme3.renderer.RenderManager;

+import com.jme3.scene.CameraNode;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.control.CameraControl.ControlDirection;

+

+/**

+ * A walking physical character followed by a 3rd person camera. (No animation.)

+ * @author normenhansen, zathras

+ */

+public class TestPhysicsCharacter extends SimpleApplication implements ActionListener {

+

+  private BulletAppState bulletAppState;

+  private CharacterControl physicsCharacter;

+  private Node characterNode;

+  private CameraNode camNode;

+  boolean rotate = false;

+  private Vector3f walkDirection = new Vector3f(0,0,0);

+  private Vector3f viewDirection = new Vector3f(0,0,0);

+  boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, 

+          leftRotate = false, rightRotate = false;

+

+  public static void main(String[] args) {

+    TestPhysicsCharacter app = new TestPhysicsCharacter();

+    app.start();

+  }

+

+    private void setupKeys() {

+        inputManager.addMapping("Strafe Left", 

+                new KeyTrigger(KeyInput.KEY_Q), 

+                new KeyTrigger(KeyInput.KEY_Z));

+        inputManager.addMapping("Strafe Right", 

+                new KeyTrigger(KeyInput.KEY_E),

+                new KeyTrigger(KeyInput.KEY_X));

+        inputManager.addMapping("Rotate Left", 

+                new KeyTrigger(KeyInput.KEY_A), 

+                new KeyTrigger(KeyInput.KEY_LEFT));

+        inputManager.addMapping("Rotate Right", 

+                new KeyTrigger(KeyInput.KEY_D), 

+                new KeyTrigger(KeyInput.KEY_RIGHT));

+        inputManager.addMapping("Walk Forward", 

+                new KeyTrigger(KeyInput.KEY_W), 

+                new KeyTrigger(KeyInput.KEY_UP));

+        inputManager.addMapping("Walk Backward", 

+                new KeyTrigger(KeyInput.KEY_S),

+                new KeyTrigger(KeyInput.KEY_DOWN));

+        inputManager.addMapping("Jump", 

+                new KeyTrigger(KeyInput.KEY_SPACE), 

+                new KeyTrigger(KeyInput.KEY_RETURN));

+        inputManager.addMapping("Shoot", 

+                new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+        inputManager.addListener(this, "Strafe Left", "Strafe Right");

+        inputManager.addListener(this, "Rotate Left", "Rotate Right");

+        inputManager.addListener(this, "Walk Forward", "Walk Backward");

+        inputManager.addListener(this, "Jump", "Shoot");

+    }

+  @Override

+  public void simpleInitApp() {

+    // activate physics

+    bulletAppState = new BulletAppState();

+    stateManager.attach(bulletAppState);

+

+    // init a physical test scene

+    PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());

+    setupKeys();

+

+    // Add a physics character to the world

+    physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), .1f);

+    physicsCharacter.setPhysicsLocation(new Vector3f(0, 1, 0));

+    characterNode = new Node("character node");

+    Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");

+    model.scale(0.25f);

+    characterNode.addControl(physicsCharacter);

+    getPhysicsSpace().add(physicsCharacter);

+    rootNode.attachChild(characterNode);

+    characterNode.attachChild(model);

+

+    // set forward camera node that follows the character

+    camNode = new CameraNode("CamNode", cam);

+    camNode.setControlDir(ControlDirection.SpatialToCamera);

+    camNode.setLocalTranslation(new Vector3f(0, 1, -5));

+    camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y);

+    characterNode.attachChild(camNode);

+

+    //disable the default 1st-person flyCam (don't forget this!!)

+    flyCam.setEnabled(false);

+

+  }

+

+   @Override

+    public void simpleUpdate(float tpf) {

+        Vector3f camDir = cam.getDirection().mult(0.2f);

+        Vector3f camLeft = cam.getLeft().mult(0.2f);

+        camDir.y = 0;

+        camLeft.y = 0;

+        viewDirection.set(camDir);

+        walkDirection.set(0, 0, 0);

+        if (leftStrafe) {

+            walkDirection.addLocal(camLeft);

+        } else

+        if (rightStrafe) {

+            walkDirection.addLocal(camLeft.negate());

+        }

+        if (leftRotate) {

+            viewDirection.addLocal(camLeft.mult(0.02f));

+        } else

+        if (rightRotate) {

+            viewDirection.addLocal(camLeft.mult(0.02f).negate());

+        }

+        if (forward) {

+            walkDirection.addLocal(camDir);

+        } else

+        if (backward) {

+            walkDirection.addLocal(camDir.negate());

+        }

+        physicsCharacter.setWalkDirection(walkDirection);

+        physicsCharacter.setViewDirection(viewDirection);

+    }

+

+    public void onAction(String binding, boolean value, float tpf) {

+        if (binding.equals("Strafe Left")) {

+            if (value) {

+                leftStrafe = true;

+            } else {

+                leftStrafe = false;

+            }

+        } else if (binding.equals("Strafe Right")) {

+            if (value) {

+                rightStrafe = true;

+            } else {

+                rightStrafe = false;

+            }

+        } else if (binding.equals("Rotate Left")) {

+            if (value) {

+                leftRotate = true;

+            } else {

+                leftRotate = false;

+            }

+        } else if (binding.equals("Rotate Right")) {

+            if (value) {

+                rightRotate = true;

+            } else {

+                rightRotate = false;

+            }

+        } else if (binding.equals("Walk Forward")) {

+            if (value) {

+                forward = true;

+            } else {

+                forward = false;

+            }

+        } else if (binding.equals("Walk Backward")) {

+            if (value) {

+                backward = true;

+            } else {

+                backward = false;

+            }

+        } else if (binding.equals("Jump")) {

+            physicsCharacter.jump();

+        }

+    }

+

+  private PhysicsSpace getPhysicsSpace() {

+    return bulletAppState.getPhysicsSpace();

+  }

+

+  @Override

+  public void simpleRender(RenderManager rm) {

+    //TODO: add render code

+  }

+}

diff --git a/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java b/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java
new file mode 100644
index 0000000..95b4b30
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestPhysicsHingeJoint.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.HingeJoint;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogListener {
+    private BulletAppState bulletAppState;
+    private HingeJoint joint;
+
+    public static void main(String[] args) {
+        TestPhysicsHingeJoint app = new TestPhysicsHingeJoint();
+        app.start();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Swing", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this, "Left", "Right", "Swing");
+    }
+
+    public void onAnalog(String binding, float value, float tpf) {
+        if(binding.equals("Left")){
+            joint.enableMotor(true, 1, .1f);
+        }
+        else if(binding.equals("Right")){
+            joint.enableMotor(true, -1, .1f);
+        }
+        else if(binding.equals("Swing")){
+            joint.enableMotor(false, 0, 0);
+        }
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        setupKeys();
+        setupJoint();
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    public void setupJoint() {
+        Node holderNode=PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0);
+        holderNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f,0,0f));
+        rootNode.attachChild(holderNode);
+        getPhysicsSpace().add(holderNode);
+
+        Node hammerNode=PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .3f, .3f, .3f)),1);
+        hammerNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f,-1,0f));
+        rootNode.attachChild(hammerNode);
+        getPhysicsSpace().add(hammerNode);
+
+        joint=new HingeJoint(holderNode.getControl(RigidBodyControl.class), hammerNode.getControl(RigidBodyControl.class), Vector3f.ZERO, new Vector3f(0f,-1,0f), Vector3f.UNIT_Z, Vector3f.UNIT_Z);
+        getPhysicsSpace().add(joint);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        
+    }
+
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/bullet/TestPhysicsReadWrite.java b/engine/src/test/jme3test/bullet/TestPhysicsReadWrite.java
new file mode 100644
index 0000000..b31d79a
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestPhysicsReadWrite.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.*;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.HingeJoint;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This is a basic Test of jbullet-jme functions
+ *
+ * @author normenhansen
+ */
+public class TestPhysicsReadWrite extends SimpleApplication{
+    private BulletAppState bulletAppState;
+    private Node physicsRootNode;
+    public static void main(String[] args){
+        TestPhysicsReadWrite app = new TestPhysicsReadWrite();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        physicsRootNode=new Node("PhysicsRootNode");
+        rootNode.attachChild(physicsRootNode);
+
+        // Add a physics sphere to the world
+        Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0));
+        rootNode.attachChild(physicsSphere);
+        getPhysicsSpace().add(physicsSphere);
+
+        // Add a physics sphere to the world using the collision shape from sphere one
+        Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1);
+        physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0));
+        rootNode.attachChild(physicsSphere2);
+        getPhysicsSpace().add(physicsSphere2);
+
+        // Add a physics box to the world
+        Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1);
+        physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f);
+        physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f));
+        rootNode.attachChild(physicsBox);
+        getPhysicsSpace().add(physicsBox);
+
+        // Add a physics cylinder to the world
+        Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1);
+        physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0));
+        rootNode.attachChild(physicsCylinder);
+        getPhysicsSpace().add(physicsCylinder);
+
+        // an obstacle mesh, does not move (mass=0)
+        Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0);
+        node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f));
+        rootNode.attachChild(node2);
+        getPhysicsSpace().add(node2);
+
+        // the floor mesh, does not move (mass=0)
+        Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0);
+        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        rootNode.attachChild(node3);
+        getPhysicsSpace().add(node3);
+
+        // Join the physics objects with a Point2Point joint
+        HingeJoint joint=new HingeJoint(physicsSphere.getControl(RigidBodyControl.class), physicsBox.getControl(RigidBodyControl.class), new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z);
+        getPhysicsSpace().add(joint);
+
+        //save and load the physicsRootNode
+        try {
+            //remove all physics objects from physics space
+            getPhysicsSpace().removeAll(physicsRootNode);
+            physicsRootNode.removeFromParent();
+            //export to byte array
+            ByteArrayOutputStream bout=new ByteArrayOutputStream();
+            BinaryExporter.getInstance().save(physicsRootNode, bout);
+            //import from byte array
+            ByteArrayInputStream bin=new ByteArrayInputStream(bout.toByteArray());
+            BinaryImporter imp=BinaryImporter.getInstance();
+            imp.setAssetManager(assetManager);
+            Node newPhysicsRootNode=(Node)imp.load(bin);
+            //add all physics objects to physics space
+            getPhysicsSpace().addAll(newPhysicsRootNode);
+            rootNode.attachChild(newPhysicsRootNode);
+        } catch (IOException ex) {
+            Logger.getLogger(TestPhysicsReadWrite.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        //TODO: add update code
+    }
+
+    @Override
+    public void simpleRender(RenderManager rm) {
+        //TODO: add render code
+    }
+
+}
diff --git a/engine/src/test/jme3test/bullet/TestQ3.java b/engine/src/test/jme3test/bullet/TestQ3.java
new file mode 100644
index 0000000..1d18c4b
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestQ3.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.MaterialList;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.ogre.OgreMeshKey;
+import java.io.File;
+
+public class TestQ3 extends SimpleApplication implements ActionListener {
+
+    private BulletAppState bulletAppState;
+    private Node gameLevel;
+    private PhysicsCharacter player;
+    private Vector3f walkDirection = new Vector3f();
+    private static boolean useHttp = false;
+    private boolean left=false,right=false,up=false,down=false;
+
+    public static void main(String[] args) {
+        File file = new File("quake3level.zip");
+        if (!file.exists()) {
+            useHttp = true;
+        }
+        TestQ3 app = new TestQ3();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        flyCam.setMoveSpeed(100);
+        setupKeys();
+
+        this.cam.setFrustumFar(2000);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White.clone().multLocal(2));
+        dl.setDirection(new Vector3f(-1, -1, -1).normalize());
+        rootNode.addLight(dl);
+
+        AmbientLight am = new AmbientLight();
+        am.setColor(ColorRGBA.White.mult(2));
+        rootNode.addLight(am);
+
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/quake3level.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("quake3level.zip", ZipLocator.class.getName());
+        }
+
+        // create the geometry and attach it
+        MaterialList matList = (MaterialList) assetManager.loadAsset("Scene.material");
+        OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
+        gameLevel = (Node) assetManager.loadAsset(key);
+        gameLevel.setLocalScale(0.1f);
+
+        // add a physics control, it will generate a MeshCollisionShape based on the gameLevel
+        gameLevel.addControl(new RigidBodyControl(0));
+
+        player = new PhysicsCharacter(new SphereCollisionShape(5), .01f);
+        player.setJumpSpeed(20);
+        player.setFallSpeed(30);
+        player.setGravity(30);
+
+        player.setPhysicsLocation(new Vector3f(60, 10, -60));
+
+        rootNode.attachChild(gameLevel);
+
+        getPhysicsSpace().addAll(gameLevel);
+        getPhysicsSpace().add(player);
+    }
+
+    private PhysicsSpace getPhysicsSpace(){
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
+        Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
+        walkDirection.set(0,0,0);
+        if(left)
+            walkDirection.addLocal(camLeft);
+        if(right)
+            walkDirection.addLocal(camLeft.negate());
+        if(up)
+            walkDirection.addLocal(camDir);
+        if(down)
+            walkDirection.addLocal(camDir.negate());
+        player.setWalkDirection(walkDirection);
+        cam.setLocation(player.getPhysicsLocation());
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this,"Lefts");
+        inputManager.addListener(this,"Rights");
+        inputManager.addListener(this,"Ups");
+        inputManager.addListener(this,"Downs");
+        inputManager.addListener(this,"Space");
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+
+        if (binding.equals("Lefts")) {
+            if(value)
+                left=true;
+            else
+                left=false;
+        } else if (binding.equals("Rights")) {
+            if(value)
+                right=true;
+            else
+                right=false;
+        } else if (binding.equals("Ups")) {
+            if(value)
+                up=true;
+            else
+                up=false;
+        } else if (binding.equals("Downs")) {
+            if(value)
+                down=true;
+            else
+                down=false;
+        } else if (binding.equals("Space")) {
+            player.jump();
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestRagDoll.java b/engine/src/test/jme3test/bullet/TestRagDoll.java
new file mode 100644
index 0000000..9287f10
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestRagDoll.java
@@ -0,0 +1,124 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.joints.ConeJoint;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class TestRagDoll extends SimpleApplication implements ActionListener {
+
+    private BulletAppState bulletAppState = new BulletAppState();
+    private Node ragDoll = new Node();
+    private Node shoulders;
+    private Vector3f upforce = new Vector3f(0, 200, 0);
+    private boolean applyForce = false;
+
+    public static void main(String[] args) {
+        TestRagDoll app = new TestRagDoll();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        inputManager.addMapping("Pull ragdoll up", new MouseButtonTrigger(0));
+        inputManager.addListener(this, "Pull ragdoll up");
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
+        createRagDoll();
+    }
+
+    private void createRagDoll() {
+        shoulders = createLimb(0.2f, 1.0f, new Vector3f(0.00f, 1.5f, 0), true);
+        Node uArmL = createLimb(0.2f, 0.5f, new Vector3f(-0.75f, 0.8f, 0), false);
+        Node uArmR = createLimb(0.2f, 0.5f, new Vector3f(0.75f, 0.8f, 0), false);
+        Node lArmL = createLimb(0.2f, 0.5f, new Vector3f(-0.75f, -0.2f, 0), false);
+        Node lArmR = createLimb(0.2f, 0.5f, new Vector3f(0.75f, -0.2f, 0), false);
+        Node body = createLimb(0.2f, 1.0f, new Vector3f(0.00f, 0.5f, 0), false);
+        Node hips = createLimb(0.2f, 0.5f, new Vector3f(0.00f, -0.5f, 0), true);
+        Node uLegL = createLimb(0.2f, 0.5f, new Vector3f(-0.25f, -1.2f, 0), false);
+        Node uLegR = createLimb(0.2f, 0.5f, new Vector3f(0.25f, -1.2f, 0), false);
+        Node lLegL = createLimb(0.2f, 0.5f, new Vector3f(-0.25f, -2.2f, 0), false);
+        Node lLegR = createLimb(0.2f, 0.5f, new Vector3f(0.25f, -2.2f, 0), false);
+
+        join(body, shoulders, new Vector3f(0f, 1.4f, 0));
+        join(body, hips, new Vector3f(0f, -0.5f, 0));
+
+        join(uArmL, shoulders, new Vector3f(-0.75f, 1.4f, 0));
+        join(uArmR, shoulders, new Vector3f(0.75f, 1.4f, 0));
+        join(uArmL, lArmL, new Vector3f(-0.75f, .4f, 0));
+        join(uArmR, lArmR, new Vector3f(0.75f, .4f, 0));
+
+        join(uLegL, hips, new Vector3f(-.25f, -0.5f, 0));
+        join(uLegR, hips, new Vector3f(.25f, -0.5f, 0));
+        join(uLegL, lLegL, new Vector3f(-.25f, -1.7f, 0));
+        join(uLegR, lLegR, new Vector3f(.25f, -1.7f, 0));
+
+        ragDoll.attachChild(shoulders);
+        ragDoll.attachChild(body);
+        ragDoll.attachChild(hips);
+        ragDoll.attachChild(uArmL);
+        ragDoll.attachChild(uArmR);
+        ragDoll.attachChild(lArmL);
+        ragDoll.attachChild(lArmR);
+        ragDoll.attachChild(uLegL);
+        ragDoll.attachChild(uLegR);
+        ragDoll.attachChild(lLegL);
+        ragDoll.attachChild(lLegR);
+
+        rootNode.attachChild(ragDoll);
+        bulletAppState.getPhysicsSpace().addAll(ragDoll);
+    }
+
+    private Node createLimb(float width, float height, Vector3f location, boolean rotate) {
+        int axis = rotate ? PhysicsSpace.AXIS_X : PhysicsSpace.AXIS_Y;
+        CapsuleCollisionShape shape = new CapsuleCollisionShape(width, height, axis);
+        Node node = new Node("Limb");
+        RigidBodyControl rigidBodyControl = new RigidBodyControl(shape, 1);
+        node.setLocalTranslation(location);
+        node.addControl(rigidBodyControl);
+        return node;
+    }
+
+    private PhysicsJoint join(Node A, Node B, Vector3f connectionPoint) {
+        Vector3f pivotA = A.worldToLocal(connectionPoint, new Vector3f());
+        Vector3f pivotB = B.worldToLocal(connectionPoint, new Vector3f());
+        ConeJoint joint = new ConeJoint(A.getControl(RigidBodyControl.class), B.getControl(RigidBodyControl.class), pivotA, pivotB);
+        joint.setLimit(1f, 1f, 0);
+        return joint;
+    }
+
+    public void onAction(String string, boolean bln, float tpf) {
+        if ("Pull ragdoll up".equals(string)) {
+            if (bln) {
+                shoulders.getControl(RigidBodyControl.class).activate();
+                applyForce = true;
+            } else {
+                applyForce = false;
+            }
+        }
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        if (applyForce) {
+            shoulders.getControl(RigidBodyControl.class).applyForce(upforce, Vector3f.ZERO);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestRagdollCharacter.java b/engine/src/test/jme3test/bullet/TestRagdollCharacter.java
new file mode 100644
index 0000000..bdf5133
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestRagdollCharacter.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.AnimEventListener;
+import com.jme3.animation.LoopMode;
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.control.KinematicRagdollControl;
+import com.jme3.bullet.control.RigidBodyControl;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.Texture;
+
+/**
+ * @author normenhansen
+ */
+public class TestRagdollCharacter extends SimpleApplication implements AnimEventListener, ActionListener {
+
+    BulletAppState bulletAppState;
+    Node model;
+    KinematicRagdollControl ragdoll;
+    boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
+            leftRotate = false, rightRotate = false;
+    AnimControl animControl;
+    AnimChannel animChannel;
+
+    public static void main(String[] args) {
+        TestRagdollCharacter app = new TestRagdollCharacter();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        setupKeys();
+
+        bulletAppState = new BulletAppState();
+        bulletAppState.setEnabled(true);
+        stateManager.attach(bulletAppState);
+
+
+//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
+        initWall(2,1,1);
+        setupLight();
+
+        cam.setLocation(new Vector3f(-8,0,-4));
+        cam.lookAt(new Vector3f(4,0,-7), Vector3f.UNIT_Y);
+
+        model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
+        model.lookAt(new Vector3f(0,0,-1), Vector3f.UNIT_Y);
+        model.setLocalTranslation(4, 0, -7f);
+
+        ragdoll = new KinematicRagdollControl(0.5f);
+        model.addControl(ragdoll);
+
+        getPhysicsSpace().add(ragdoll);
+        speed = 1.3f;
+
+        rootNode.attachChild(model);
+
+
+        AnimControl control = model.getControl(AnimControl.class);
+        animChannel = control.createChannel();
+        animChannel.setAnim("IdleTop");
+        control.addListener(this);
+
+    }
+
+    private void setupLight() {
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("Rotate Left",
+                new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rotate Right",
+                new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Walk Forward",
+                new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("Walk Backward",
+                new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("Slice",
+                new KeyTrigger(KeyInput.KEY_SPACE),
+                new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "Strafe Left", "Strafe Right");
+        inputManager.addListener(this, "Rotate Left", "Rotate Right");
+        inputManager.addListener(this, "Walk Forward", "Walk Backward");
+        inputManager.addListener(this, "Slice");
+    }
+
+    public void initWall(float bLength, float bWidth, float bHeight) {
+        Box brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);
+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
+        Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        mat2.setTexture("ColorMap", tex);
+        
+        float startpt = bLength / 4;
+        float height = -5;
+        for (int j = 0; j < 15; j++) {
+            for (int i = 0; i < 4; i++) {
+                Vector3f ori = new Vector3f(i * bLength * 2 + startpt, bHeight + height, -10);
+                Geometry reBoxg = new Geometry("brick", brick);
+                reBoxg.setMaterial(mat2);
+                reBoxg.setLocalTranslation(ori);
+                //for geometry with sphere mesh the physics system automatically uses a sphere collision shape
+                reBoxg.addControl(new RigidBodyControl(1.5f));
+                reBoxg.setShadowMode(ShadowMode.CastAndReceive);
+                reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f);
+                this.rootNode.attachChild(reBoxg);
+                this.getPhysicsSpace().add(reBoxg);
+            }
+            startpt = -startpt;
+            height += 2 * bHeight;
+        }
+    }
+
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+
+        if (channel.getAnimationName().equals("SliceHorizontal")) {
+            channel.setLoopMode(LoopMode.DontLoop);
+            channel.setAnim("IdleTop", 5);
+            channel.setLoopMode(LoopMode.Loop);
+        }
+
+    }
+
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    }
+    
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Rotate Left")) {
+            if (value) {
+                leftRotate = true;
+            } else {
+                leftRotate = false;
+            }
+        } else if (binding.equals("Rotate Right")) {
+            if (value) {
+                rightRotate = true;
+            } else {
+                rightRotate = false;
+            }
+        } else if (binding.equals("Walk Forward")) {
+            if (value) {
+                forward = true;
+            } else {
+                forward = false;
+            }
+        } else if (binding.equals("Walk Backward")) {
+            if (value) {
+                backward = true;
+            } else {
+                backward = false;
+            }
+        } else if (binding.equals("Slice")) {
+            if (value) {
+                animChannel.setAnim("SliceHorizontal");
+                animChannel.setSpeed(0.3f);
+            }
+        }
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        if(forward){
+            model.move(model.getLocalRotation().multLocal(new Vector3f(0,0,1)).multLocal(tpf));
+        }else if(backward){
+            model.move(model.getLocalRotation().multLocal(new Vector3f(0,0,1)).multLocal(-tpf));
+        }else if(leftRotate){
+            model.rotate(0, tpf, 0);
+        }else if(rightRotate){
+            model.rotate(0, -tpf, 0);
+        }
+        fpsText.setText(cam.getLocation() + "/" + cam.getRotation());
+    }
+
+}
diff --git a/engine/src/test/jme3test/bullet/TestSimplePhysics.java b/engine/src/test/jme3test/bullet/TestSimplePhysics.java
new file mode 100644
index 0000000..21706af
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestSimplePhysics.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.*;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ * This is a basic Test of jbullet-jme functions
+ *
+ * @author normenhansen
+ */
+public class TestSimplePhysics extends SimpleApplication {
+
+    private BulletAppState bulletAppState;
+
+    public static void main(String[] args) {
+        TestSimplePhysics app = new TestSimplePhysics();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+        bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+
+        // Add a physics sphere to the world
+        Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1);
+        physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0));
+        rootNode.attachChild(physicsSphere);
+        getPhysicsSpace().add(physicsSphere);
+
+        // Add a physics sphere to the world using the collision shape from sphere one
+        Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1);
+        physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0));
+        rootNode.attachChild(physicsSphere2);
+        getPhysicsSpace().add(physicsSphere2);
+
+        // Add a physics box to the world
+        Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1);
+        physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f);
+        physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f));
+        rootNode.attachChild(physicsBox);
+        getPhysicsSpace().add(physicsBox);
+
+        // Add a physics cylinder to the world
+        Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1);
+        physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0));
+        rootNode.attachChild(physicsCylinder);
+        getPhysicsSpace().add(physicsCylinder);
+
+        // an obstacle mesh, does not move (mass=0)
+        Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0);
+        node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f));
+        rootNode.attachChild(node2);
+        getPhysicsSpace().add(node2);
+
+        // the floor mesh, does not move (mass=0)
+        Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0);
+        node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f));
+        rootNode.attachChild(node3);
+        getPhysicsSpace().add(node3);
+
+        // Join the physics objects with a Point2Point joint
+//        PhysicsPoint2PointJoint joint=new PhysicsPoint2PointJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0));
+//        PhysicsHingeJoint joint=new PhysicsHingeJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z);
+//        getPhysicsSpace().add(joint);
+
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+}
diff --git a/engine/src/test/jme3test/bullet/TestWalkingChar.java b/engine/src/test/jme3test/bullet/TestWalkingChar.java
new file mode 100644
index 0000000..e9746fb
--- /dev/null
+++ b/engine/src/test/jme3test/bullet/TestWalkingChar.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2009-2010 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.bullet;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.AnimEventListener;
+import com.jme3.animation.LoopMode;
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionEvent;
+import com.jme3.bullet.collision.PhysicsCollisionListener;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.util.CollisionShapeFactory;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.shapes.EmitterSphereShape;
+import com.jme3.input.ChaseCamera;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Sphere.TextureMode;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A walking animated character followed by a 3rd person camera on a terrain with LOD.
+ * @author normenhansen
+ */
+public class TestWalkingChar extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener {
+
+    private BulletAppState bulletAppState;
+    //character
+    CharacterControl character;
+    Node model;
+    //temp vectors
+    Vector3f walkDirection = new Vector3f();
+    //terrain
+    TerrainQuad terrain;
+    RigidBodyControl terrainPhysicsNode;
+    //Materials
+    Material matRock;
+    Material matBullet;
+    //animation
+    AnimChannel animationChannel;
+    AnimChannel shootingChannel;
+    AnimControl animationControl;
+    float airTime = 0;
+    //camera
+    boolean left = false, right = false, up = false, down = false;
+    ChaseCamera chaseCam;
+    //bullet
+    Sphere bullet;
+    SphereCollisionShape bulletCollisionShape;
+    //explosion
+    ParticleEmitter effect;
+    //brick wall
+    Box brick;
+    float bLength = 0.8f;
+    float bWidth = 0.4f;
+    float bHeight = 0.4f;
+    FilterPostProcessor fpp;
+
+    public static void main(String[] args) {
+        TestWalkingChar app = new TestWalkingChar();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+        stateManager.attach(bulletAppState);
+        setupKeys();
+        prepareBullet();
+        prepareEffect();
+        createLight();
+        createSky();
+        createTerrain();
+        createWall();
+        createCharacter();
+        setupChaseCamera();
+        setupAnimationController();
+        setupFilter();
+    }
+
+    private void setupFilter() {
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
+        fpp.addFilter(bloom);
+        viewPort.addProcessor(fpp);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+
+    private void setupKeys() {
+        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(this, "wireframe");
+        inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
+        inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
+        inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W));
+        inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S));
+        inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this, "CharLeft");
+        inputManager.addListener(this, "CharRight");
+        inputManager.addListener(this, "CharUp");
+        inputManager.addListener(this, "CharDown");
+        inputManager.addListener(this, "CharSpace");
+        inputManager.addListener(this, "CharShoot");
+    }
+
+    private void createWall() {
+        float xOff = -144;
+        float zOff = -40;
+        float startpt = bLength / 4 - xOff;
+        float height = 6.1f;
+        brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);
+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
+        for (int j = 0; j < 15; j++) {
+            for (int i = 0; i < 4; i++) {
+                Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff);
+                addBrick(vt);
+            }
+            startpt = -startpt;
+            height += 1.01f * bHeight;
+        }
+    }
+
+    private void addBrick(Vector3f ori) {
+        Geometry reBoxg = new Geometry("brick", brick);
+        reBoxg.setMaterial(matBullet);
+        reBoxg.setLocalTranslation(ori);
+        reBoxg.addControl(new RigidBodyControl(1.5f));
+        reBoxg.setShadowMode(ShadowMode.CastAndReceive);
+        this.rootNode.attachChild(reBoxg);
+        this.getPhysicsSpace().add(reBoxg);
+    }
+
+    private void prepareBullet() {
+        bullet = new Sphere(32, 32, 0.4f, true, false);
+        bullet.setTextureMode(TextureMode.Projected);
+        bulletCollisionShape = new SphereCollisionShape(0.4f);
+        matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        matBullet.setColor("Color", ColorRGBA.Green);
+        matBullet.setColor("m_GlowColor", ColorRGBA.Green);
+        getPhysicsSpace().addCollisionListener(this);
+    }
+
+    private void prepareEffect() {
+        int COUNT_FACTOR = 1;
+        float COUNT_FACTOR_F = 1f;
+        effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR);
+        effect.setSelectRandomImage(true);
+        effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));
+        effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
+        effect.setStartSize(1.3f);
+        effect.setEndSize(2f);
+        effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
+        effect.setParticlesPerSec(0);
+        effect.setGravity(0, -5, 0);
+        effect.setLowLife(.4f);
+        effect.setHighLife(.5f);
+        effect.setInitialVelocity(new Vector3f(0, 7, 0));
+        effect.setVelocityVariation(1f);
+        effect.setImagesX(2);
+        effect.setImagesY(2);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
+        effect.setMaterial(mat);
+//        effect.setLocalScale(100);
+        rootNode.attachChild(effect);
+    }
+
+    private void createLight() {
+        Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(direction);
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    private void createSky() {
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+    }
+
+    private void createTerrain() {
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matRock.setBoolean("useTriPlanarMapping", false);
+        matRock.setBoolean("WardIso", true);
+        matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap", grass);
+        matRock.setFloat("DiffuseMap_0_scale", 64);
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_1", dirt);
+        matRock.setFloat("DiffuseMap_1_scale", 16);
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_2", rock);
+        matRock.setFloat("DiffuseMap_2_scale", 128);
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matRock.setTexture("NormalMap", normalMap0);
+        matRock.setTexture("NormalMap_1", normalMap2);
+        matRock.setTexture("NormalMap_2", normalMap2);
+
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+            heightmap.load();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        List<Camera> cameras = new ArrayList<Camera>();
+        cameras.add(getCamera());
+        TerrainLodControl control = new TerrainLodControl(terrain, cameras);
+        terrain.addControl(control);
+        terrain.setMaterial(matRock);
+        terrain.setLocalScale(new Vector3f(2, 2, 2));
+
+        terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);
+        terrain.addControl(terrainPhysicsNode);
+        rootNode.attachChild(terrain);
+        getPhysicsSpace().add(terrainPhysicsNode);
+    }
+
+    private void createCharacter() {
+        CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f);
+        character = new CharacterControl(capsule, 0.01f);
+        model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        //model.setLocalScale(0.5f);
+        model.addControl(character);
+        character.setPhysicsLocation(new Vector3f(-140, 15, -10));
+        rootNode.attachChild(model);
+        getPhysicsSpace().add(character);
+    }
+
+    private void setupChaseCamera() {
+        flyCam.setEnabled(false);
+        chaseCam = new ChaseCamera(cam, model, inputManager);
+    }
+
+    private void setupAnimationController() {
+        animationControl = model.getControl(AnimControl.class);
+        animationControl.addListener(this);
+        animationChannel = animationControl.createChannel();
+        shootingChannel = animationControl.createChannel();
+        shootingChannel.addBone(animationControl.getSkeleton().getBone("uparm.right"));
+        shootingChannel.addBone(animationControl.getSkeleton().getBone("arm.right"));
+        shootingChannel.addBone(animationControl.getSkeleton().getBone("hand.right"));
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        Vector3f camDir = cam.getDirection().clone().multLocal(0.1f);
+        Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f);
+        camDir.y = 0;
+        camLeft.y = 0;
+        walkDirection.set(0, 0, 0);
+        if (left) {
+            walkDirection.addLocal(camLeft);
+        }
+        if (right) {
+            walkDirection.addLocal(camLeft.negate());
+        }
+        if (up) {
+            walkDirection.addLocal(camDir);
+        }
+        if (down) {
+            walkDirection.addLocal(camDir.negate());
+        }
+        if (!character.onGround()) {
+            airTime = airTime + tpf;
+        } else {
+            airTime = 0;
+        }
+        if (walkDirection.length() == 0) {
+            if (!"stand".equals(animationChannel.getAnimationName())) {
+                animationChannel.setAnim("stand", 1f);
+            }
+        } else {
+            character.setViewDirection(walkDirection);
+            if (airTime > .3f) {
+                if (!"stand".equals(animationChannel.getAnimationName())) {
+                    animationChannel.setAnim("stand");
+                }
+            } else if (!"Walk".equals(animationChannel.getAnimationName())) {
+                animationChannel.setAnim("Walk", 0.7f);
+            }
+        }
+        character.setWalkDirection(walkDirection);
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("CharLeft")) {
+            if (value) {
+                left = true;
+            } else {
+                left = false;
+            }
+        } else if (binding.equals("CharRight")) {
+            if (value) {
+                right = true;
+            } else {
+                right = false;
+            }
+        } else if (binding.equals("CharUp")) {
+            if (value) {
+                up = true;
+            } else {
+                up = false;
+            }
+        } else if (binding.equals("CharDown")) {
+            if (value) {
+                down = true;
+            } else {
+                down = false;
+            }
+        } else if (binding.equals("CharSpace")) {
+            character.jump();
+        } else if (binding.equals("CharShoot") && !value) {
+            bulletControl();
+        }
+    }
+
+    private void bulletControl() {
+        shootingChannel.setAnim("Dodge", 0.1f);
+        shootingChannel.setLoopMode(LoopMode.DontLoop);
+        Geometry bulletg = new Geometry("bullet", bullet);
+        bulletg.setMaterial(matBullet);
+        bulletg.setShadowMode(ShadowMode.CastAndReceive);
+        bulletg.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5)));
+        RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1);
+        bulletControl.setCcdMotionThreshold(0.1f);
+        bulletControl.setLinearVelocity(cam.getDirection().mult(80));
+        bulletg.addControl(bulletControl);
+        rootNode.attachChild(bulletg);
+        getPhysicsSpace().add(bulletControl);
+    }
+
+    public void collision(PhysicsCollisionEvent event) {
+        if (event.getObjectA() instanceof BombControl) {
+            final Spatial node = event.getNodeA();
+            effect.killAllParticles();
+            effect.setLocalTranslation(node.getLocalTranslation());
+            effect.emitAllParticles();
+        } else if (event.getObjectB() instanceof BombControl) {
+            final Spatial node = event.getNodeB();
+            effect.killAllParticles();
+            effect.setLocalTranslation(node.getLocalTranslation());
+            effect.emitAllParticles();
+        }
+    }
+
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+        if (channel == shootingChannel) {
+            channel.setAnim("stand");
+        }
+    }
+
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    }
+}
diff --git a/engine/src/test/jme3test/collision/RayTrace.java b/engine/src/test/jme3test/collision/RayTrace.java
new file mode 100644
index 0000000..7e02ad1
--- /dev/null
+++ b/engine/src/test/jme3test/collision/RayTrace.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.collision.CollisionResults;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Spatial;
+import java.awt.FlowLayout;
+import java.awt.image.BufferedImage;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+public class RayTrace {
+
+    private BufferedImage image;
+    private Camera cam;
+    private Spatial scene;
+    private CollisionResults results = new CollisionResults();
+    private JFrame frame;
+    private JLabel label;
+
+    public RayTrace(Spatial scene, Camera cam, int width, int height){
+        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        this.scene = scene;
+        this.cam = cam;
+    }
+
+    public void show(){
+        frame = new JFrame("HDR View");
+        label = new JLabel(new ImageIcon(image));
+        frame.getContentPane().add(label);
+        frame.setLayout(new FlowLayout());
+        frame.pack();
+        frame.setVisible(true);
+    }
+
+    public void update(){
+        int w = image.getWidth();
+        int h = image.getHeight();
+
+        float wr = (float) cam.getWidth()  / image.getWidth();
+        float hr = (float) cam.getHeight() / image.getHeight();
+
+        scene.updateGeometricState();
+
+        for (int y = 0; y < h; y++){
+            for (int x = 0; x < w; x++){
+                Vector2f v = new Vector2f(x * wr,y * hr);
+                Vector3f pos = cam.getWorldCoordinates(v, 0.0f);
+                Vector3f dir = cam.getWorldCoordinates(v, 0.3f);
+                dir.subtractLocal(pos).normalizeLocal();
+
+                Ray r = new Ray(pos, dir);
+
+                results.clear();
+                scene.collideWith(r, results);
+                if (results.size() > 0){
+                    image.setRGB(x, h - y - 1, 0xFFFFFFFF);
+                }else{
+                    image.setRGB(x, h - y - 1, 0xFF000000);
+                }
+            }
+        }
+
+        label.repaint();
+    }
+
+}
diff --git a/engine/src/test/jme3test/collision/TestMousePick.java b/engine/src/test/jme3test/collision/TestMousePick.java
new file mode 100644
index 0000000..835b2c6
--- /dev/null
+++ b/engine/src/test/jme3test/collision/TestMousePick.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.shape.Box;
+
+public class TestMousePick extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestMousePick app = new TestMousePick();
+        app.start();
+    }
+    
+    Node shootables;
+    Geometry mark;
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setEnabled(false);
+        initMark();       // a red sphere to mark the hit
+
+        /** create four colored boxes and a floor to shoot at: */
+        shootables = new Node("Shootables");
+        rootNode.attachChild(shootables);
+        shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f));
+        shootables.attachChild(makeCube("a tin can", 1f, -2f, 0f));
+        shootables.attachChild(makeCube("the Sheriff", 0f, 1f, -2f));
+        shootables.attachChild(makeCube("the Deputy", 1f, 0f, -4f));
+        shootables.attachChild(makeFloor());
+        shootables.attachChild(makeCharacter());
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Vector3f origin    = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f);
+        Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f);
+        direction.subtractLocal(origin).normalizeLocal();
+
+        Ray ray = new Ray(origin, direction);
+        CollisionResults results = new CollisionResults();
+        shootables.collideWith(ray, results);
+//        System.out.println("----- Collisions? " + results.size() + "-----");
+//        for (int i = 0; i < results.size(); i++) {
+//            // For each hit, we know distance, impact point, name of geometry.
+//            float dist = results.getCollision(i).getDistance();
+//            Vector3f pt = results.getCollision(i).getWorldContactPoint();
+//            String hit = results.getCollision(i).getGeometry().getName();
+//            System.out.println("* Collision #" + i);
+//            System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");
+//        }
+        if (results.size() > 0) {
+            CollisionResult closest = results.getClosestCollision();
+            mark.setLocalTranslation(closest.getContactPoint());
+
+            Quaternion q = new Quaternion();
+            q.lookAt(closest.getContactNormal(), Vector3f.UNIT_Y);
+            mark.setLocalRotation(q);
+
+            rootNode.attachChild(mark);
+        } else {
+            rootNode.detachChild(mark);
+        }
+    }
+ 
+    /** A cube object for target practice */
+    protected Geometry makeCube(String name, float x, float y, float z) {
+        Box box = new Box(new Vector3f(x, y, z), 1, 1, 1);
+        Geometry cube = new Geometry(name, box);
+        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setColor("Color", ColorRGBA.randomColor());
+        cube.setMaterial(mat1);
+        return cube;
+    }
+
+    /** A floor to show that the "shot" can go through several objects. */
+    protected Geometry makeFloor() {
+        Box box = new Box(new Vector3f(0, -4, -5), 15, .2f, 15);
+        Geometry floor = new Geometry("the Floor", box);
+        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setColor("Color", ColorRGBA.Gray);
+        floor.setMaterial(mat1);
+        return floor;
+    }
+
+    /** 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.setColor("Color", ColorRGBA.Red);
+        mark.setMaterial(mark_mat);
+    }
+
+    protected Spatial makeCharacter() {
+        // load a character from jme3test-test-data
+        Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        golem.scale(0.5f);
+        golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);
+
+        // We must add a light to make the model visible
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
+        golem.addLight(sun);
+        return golem;
+    }
+}
diff --git a/engine/src/test/jme3test/collision/TestRayCasting.java b/engine/src/test/jme3test/collision/TestRayCasting.java
new file mode 100644
index 0000000..b2ef0de
--- /dev/null
+++ b/engine/src/test/jme3test/collision/TestRayCasting.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer.Type;
+
+public class TestRayCasting extends SimpleApplication {
+
+    private RayTrace tracer;
+    private Spatial teapot;
+
+    public static void main(String[] args){
+        TestRayCasting app = new TestRayCasting();
+        app.setPauseOnLostFocus(false);
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+//        flyCam.setEnabled(false);
+
+        // load material
+        Material mat = (Material) assetManager.loadMaterial("Interface/Logo/Logo.j3m");
+
+        Mesh q = new Mesh();
+        q.setBuffer(Type.Position, 3, new float[]
+        {
+            1, 0, 0,
+            0, 1.5f, 0,
+            -1, 0, 0
+        }
+        );
+        q.setBuffer(Type.Index, 3, new int[]{ 0, 1, 2 });
+        q.setBound(new BoundingSphere());
+        q.updateBound();
+//        Geometry teapot = new Geometry("MyGeom", q);
+
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.mesh.xml");
+//        teapot.scale(2f, 2f, 2f);
+//        teapot.move(2f, 2f, -.5f);
+        teapot.rotate(FastMath.HALF_PI, FastMath.HALF_PI, FastMath.HALF_PI);
+        teapot.setMaterial(mat);
+        rootNode.attachChild(teapot);
+
+//        cam.setLocation(cam.getLocation().add(0,1,0));
+//        cam.lookAt(teapot.getWorldBound().getCenter(), Vector3f.UNIT_Y);
+
+        tracer = new RayTrace(rootNode, cam, 160, 128);
+        tracer.show();
+        tracer.update();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        teapot.rotate(0,tpf,0);
+        tracer.update();
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/collision/TestTriangleCollision.java b/engine/src/test/jme3test/collision/TestTriangleCollision.java
new file mode 100644
index 0000000..29ed81f
--- /dev/null
+++ b/engine/src/test/jme3test/collision/TestTriangleCollision.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 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.collision;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.CollisionResults;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+
+public class TestTriangleCollision extends SimpleApplication {
+
+    Geometry geom1;
+
+    Spatial golem;
+
+    public static void main(String[] args) {
+        TestTriangleCollision app = new TestTriangleCollision();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // Create two boxes
+        Mesh mesh1 = new Box(0.5f, 0.5f, 0.5f);
+        geom1 = new Geometry("Box", mesh1);
+        geom1.move(2, 2, -.5f);
+        Material m1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        m1.setColor("Color", ColorRGBA.Blue);
+        geom1.setMaterial(m1);
+        rootNode.attachChild(geom1);
+
+        // load a character from jme3test-test-data
+        golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        golem.scale(0.5f);
+        golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);
+
+        // We must add a light to make the model visible
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
+        golem.addLight(sun);
+        rootNode.attachChild(golem);
+
+        // Create input
+        inputManager.addMapping("MoveRight", new KeyTrigger(KeyInput.KEY_L));
+        inputManager.addMapping("MoveLeft", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("MoveUp", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("MoveDown", new KeyTrigger(KeyInput.KEY_K));
+
+        inputManager.addListener(analogListener, new String[]{
+                    "MoveRight", "MoveLeft", "MoveUp", "MoveDown"
+                });
+    }
+    private AnalogListener analogListener = new AnalogListener() {
+
+        public void onAnalog(String name, float value, float tpf) {
+            if (name.equals("MoveRight")) {
+                geom1.move(2 * tpf, 0, 0);
+            }
+
+            if (name.equals("MoveLeft")) {
+                geom1.move(-2 * tpf, 0, 0);
+            }
+
+            if (name.equals("MoveUp")) {
+                geom1.move(0, 2 * tpf, 0);
+            }
+
+            if (name.equals("MoveDown")) {
+                geom1.move(0, -2 * tpf, 0);
+            }
+        }
+    };
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        CollisionResults results = new CollisionResults();
+        BoundingVolume bv = geom1.getWorldBound();
+        golem.collideWith(bv, results);
+
+        if (results.size() > 0) {
+            geom1.getMaterial().setColor("Color", ColorRGBA.Red);
+        }else{
+            geom1.getMaterial().setColor("Color", ColorRGBA.Blue);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/conversion/TestMipMapGen.java b/engine/src/test/jme3test/conversion/TestMipMapGen.java
new file mode 100644
index 0000000..13f67f1
--- /dev/null
+++ b/engine/src/test/jme3test/conversion/TestMipMapGen.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 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.conversion;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.material.Material;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import jme3tools.converters.MipMapGenerator;
+
+public class TestMipMapGen extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestMipMapGen app = new TestMipMapGen();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        BitmapText txt = guiFont.createLabel("Left: HW Mips");
+        txt.setLocalTranslation(0, settings.getHeight() - txt.getLineHeight() * 4, 0);
+        guiNode.attachChild(txt);
+
+        txt = guiFont.createLabel("Right: AWT Mips");
+        txt.setLocalTranslation(0, settings.getHeight() - txt.getLineHeight() * 3, 0);
+        guiNode.attachChild(txt);
+
+        // create a simple plane/quad
+        Quad quadMesh = new Quad(1, 1);
+        quadMesh.updateGeometry(1, 1, false);
+        quadMesh.updateBound();
+
+        Geometry quad1 = new Geometry("Textured Quad", quadMesh);
+        Geometry quad2 = new Geometry("Textured Quad 2", quadMesh);
+
+        Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.png");
+        tex.setMinFilter(Texture.MinFilter.Trilinear);
+
+        Texture texCustomMip = tex.clone();
+        Image imageCustomMip = texCustomMip.getImage().clone();
+        MipMapGenerator.generateMipMaps(imageCustomMip);
+        texCustomMip.setImage(imageCustomMip);
+
+        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setTexture("ColorMap", tex);
+
+        Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setTexture("ColorMap", texCustomMip);
+
+        quad1.setMaterial(mat1);
+//        quad1.setLocalTranslation(1, 0, 0);
+
+        quad2.setMaterial(mat2);
+        quad2.setLocalTranslation(1, 0, 0);
+
+        rootNode.attachChild(quad1);
+        rootNode.attachChild(quad2);
+    }
+
+}
diff --git a/engine/src/test/jme3test/conversion/TestTriangleStrip.java b/engine/src/test/jme3test/conversion/TestTriangleStrip.java
new file mode 100644
index 0000000..2191e11
--- /dev/null
+++ b/engine/src/test/jme3test/conversion/TestTriangleStrip.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2010 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.conversion;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import jme3tools.converters.model.ModelConverter;
+
+public class TestTriangleStrip extends SimpleApplication {
+
+
+    public static void main(String[] args){
+        TestTriangleStrip app = new TestTriangleStrip();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+        Mesh teaMesh = teaGeom.getMesh();
+        ModelConverter.generateStrips(teaMesh, true, false, 24, 0);
+
+        // show normals as material
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+
+        for (int y = -10; y < 10; y++){
+            for (int x = -10; x < 10; x++){
+                Geometry teaClone = new Geometry("teapot", teaMesh);
+                teaClone.setMaterial(mat);
+
+                teaClone.setLocalTranslation(x * .5f, 0, y * .5f);
+                teaClone.setLocalScale(.5f);
+
+                rootNode.attachChild(teaClone);
+            }
+        }
+
+        cam.setLocation(new Vector3f(8.378951f, 5.4324f, 8.795956f));
+        cam.setRotation(new Quaternion(-0.083419204f, 0.90370524f, -0.20599906f, -0.36595422f));
+    }
+
+}
diff --git a/engine/src/test/jme3test/effect/TestEverything.java b/engine/src/test/jme3test/effect/TestEverything.java
new file mode 100644
index 0000000..a3ada4d
--- /dev/null
+++ b/engine/src/test/jme3test/effect/TestEverything.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.HDRRenderer;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.shape.Box;
+import com.jme3.shadow.BasicShadowRenderer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestEverything extends SimpleApplication {
+
+    private BasicShadowRenderer bsr;
+    private HDRRenderer hdrRender;
+    private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal();
+
+    public static void main(String[] args){
+        TestEverything app = new TestEverything();
+        app.start();
+    }
+
+    public void setupHdr(){
+        if (renderer.getCaps().contains(Caps.GLSL100)){
+            hdrRender = new HDRRenderer(assetManager, renderer);
+            hdrRender.setMaxIterations(40);
+            hdrRender.setSamples(settings.getSamples());
+
+            hdrRender.setWhiteLevel(3);
+            hdrRender.setExposure(0.72f);
+            hdrRender.setThrottle(1);
+
+    //        setPauseOnLostFocus(false);
+    //        new HDRConfig(hdrRender).setVisible(true);
+
+            viewPort.addProcessor(hdrRender);
+        }
+    }
+
+    public void setupBasicShadow(){
+        if (renderer.getCaps().contains(Caps.GLSL100)){
+            bsr = new BasicShadowRenderer(assetManager, 1024);
+            bsr.setDirection(lightDir);
+            viewPort.addProcessor(bsr);
+        }
+    }
+
+    public void setupSkyBox(){
+        Texture envMap;
+        if (renderer.getCaps().contains(Caps.FloatTexture)){
+            envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.hdr");
+        }else{
+            envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.jpg");
+        }
+        rootNode.attachChild(SkyFactory.createSky(assetManager, envMap, new Vector3f(-1,-1,-1), true));
+    }
+
+    public void setupLighting(){
+        boolean hdr = false;
+        if (hdrRender != null){
+            hdr = hdrRender.isEnabled();
+        }
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(lightDir);
+        if (hdr){
+            dl.setColor(new ColorRGBA(3, 3, 3, 1));
+        }else{
+            dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1));
+        }
+        rootNode.addLight(dl);
+
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, 0, -1).normalizeLocal());
+        if (hdr){
+            dl.setColor(new ColorRGBA(1, 1, 1, 1));
+        }else{
+            dl.setColor(new ColorRGBA(.4f, .4f, .4f, 1));
+        }
+        rootNode.addLight(dl);
+    }
+
+    public void setupFloor(){
+        Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m");
+        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
+        
+        Box floor = new Box(Vector3f.ZERO, 50, 1f, 50);
+        TangentBinormalGenerator.generate(floor);
+        floor.scaleTextureCoordinates(new Vector2f(5, 5));
+        Geometry floorGeom = new Geometry("Floor", floor);
+        floorGeom.setMaterial(mat);
+        floorGeom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(floorGeom);
+    }
+
+//    public void setupTerrain(){
+//        Material mat = manager.loadMaterial("Textures/Terrain/Rock/Rock.j3m");
+//        mat.getTextureParam("DiffuseMap").getValue().setWrap(WrapMode.Repeat);
+//        mat.getTextureParam("NormalMap").getValue().setWrap(WrapMode.Repeat);
+//        try{
+//            Geomap map = GeomapLoader.fromImage(TestEverything.class.getResource("/textures/heightmap.png"));
+//            Mesh m = map.createMesh(new Vector3f(0.35f, 0.0005f, 0.35f), new Vector2f(10, 10), true);
+//            Logger.getLogger(TangentBinormalGenerator.class.getName()).setLevel(Level.SEVERE);
+//            TangentBinormalGenerator.generate(m);
+//            Geometry t = new Geometry("Terrain", m);
+//            t.setLocalTranslation(85, -15, 0);
+//            t.setMaterial(mat);
+//            t.updateModelBound();
+//            t.setShadowMode(ShadowMode.Receive);
+//            rootNode.attachChild(t);
+//        }catch (IOException ex){
+//            ex.printStackTrace();
+//        }
+//
+//    }
+
+    public void setupRobotGuy(){
+        Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Oto/Oto.j3m");
+        model.getChild(0).setMaterial(mat);
+//        model.setAnimation("Walk");
+        model.setLocalTranslation(30, 10.5f, 30);
+        model.setLocalScale(2);
+        model.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(model);
+    }
+
+    public void setupSignpost(){
+        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
+        signpost.setMaterial(mat);
+        signpost.rotate(0, FastMath.HALF_PI, 0);
+        signpost.setLocalTranslation(12, 3.5f, 30);
+        signpost.setLocalScale(4);
+        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(signpost);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(-32.295086f, 54.80136f, 79.59805f));
+        cam.setRotation(new Quaternion(0.074364014f, 0.92519957f, -0.24794696f, 0.27748522f));
+        cam.update();
+
+        cam.setFrustumFar(300);
+        flyCam.setMoveSpeed(30);
+
+        rootNode.setCullHint(CullHint.Never);
+
+        setupBasicShadow();
+        setupHdr();
+
+        setupLighting();
+        setupSkyBox();
+
+//        setupTerrain();
+        setupFloor();
+//        setupRobotGuy();
+        setupSignpost();
+
+        
+    }
+
+}
diff --git a/engine/src/test/jme3test/effect/TestExplosionEffect.java b/engine/src/test/jme3test/effect/TestExplosionEffect.java
new file mode 100644
index 0000000..7ceae96
--- /dev/null
+++ b/engine/src/test/jme3test/effect/TestExplosionEffect.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.shapes.EmitterSphereShape;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+public class TestExplosionEffect extends SimpleApplication {
+
+    private float time = 0;
+    private int state = 0;
+    private Node explosionEffect = new Node("explosionFX");
+    private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris,
+                            shockwave;
+
+
+    private static final int COUNT_FACTOR = 1;
+    private static final float COUNT_FACTOR_F = 1f;
+
+    private static final boolean POINT_SPRITE = true;
+    private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle;
+
+    public static void main(String[] args){
+        TestExplosionEffect app = new TestExplosionEffect();
+        app.start();
+    }
+
+    private void createFlame(){
+        flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR);
+        flame.setSelectRandomImage(true);
+        flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));
+        flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
+        flame.setStartSize(1.3f);
+        flame.setEndSize(2f);
+        flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
+        flame.setParticlesPerSec(0);
+        flame.setGravity(0, -5, 0);
+        flame.setLowLife(.4f);
+        flame.setHighLife(.5f);
+        flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0));
+        flame.getParticleInfluencer().setVelocityVariation(1f);
+        flame.setImagesX(2);
+        flame.setImagesY(2);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
+        mat.setBoolean("PointSprite", POINT_SPRITE);
+        flame.setMaterial(mat);
+        explosionEffect.attachChild(flame);
+    }
+
+    private void createFlash(){
+        flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR);
+        flash.setSelectRandomImage(true);
+        flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1f / COUNT_FACTOR_F)));
+        flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
+        flash.setStartSize(.1f);
+        flash.setEndSize(3.0f);
+        flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
+        flash.setParticlesPerSec(0);
+        flash.setGravity(0, 0, 0);
+        flash.setLowLife(.2f);
+        flash.setHighLife(.2f);
+        flash.setInitialVelocity(new Vector3f(0, 5f, 0));
+        flash.setVelocityVariation(1);
+        flash.setImagesX(2);
+        flash.setImagesY(2);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flash.png"));
+        mat.setBoolean("PointSprite", POINT_SPRITE);
+        flash.setMaterial(mat);
+        explosionEffect.attachChild(flash);
+    }
+
+    private void createRoundSpark(){
+        roundspark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR);
+        roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F)));
+        roundspark.setEndColor(new ColorRGBA(0, 0, 0, (float) (0.5f / COUNT_FACTOR_F)));
+        roundspark.setStartSize(1.2f);
+        roundspark.setEndSize(1.8f);
+        roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 2f));
+        roundspark.setParticlesPerSec(0);
+        roundspark.setGravity(0, -.5f, 0);
+        roundspark.setLowLife(1.8f);
+        roundspark.setHighLife(2f);
+        roundspark.setInitialVelocity(new Vector3f(0, 3, 0));
+        roundspark.setVelocityVariation(.5f);
+        roundspark.setImagesX(1);
+        roundspark.setImagesY(1);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/roundspark.png"));
+        mat.setBoolean("PointSprite", POINT_SPRITE);
+        roundspark.setMaterial(mat);
+        explosionEffect.attachChild(roundspark);
+    }
+
+    private void createSpark(){
+        spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR);
+        spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F)));
+        spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
+        spark.setStartSize(.5f);
+        spark.setEndSize(.5f);
+        spark.setFacingVelocity(true);
+        spark.setParticlesPerSec(0);
+        spark.setGravity(0, 5, 0);
+        spark.setLowLife(1.1f);
+        spark.setHighLife(1.5f);
+        spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0));
+        spark.getParticleInfluencer().setVelocityVariation(1);
+        spark.setImagesX(1);
+        spark.setImagesY(1);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/spark.png"));
+        spark.setMaterial(mat);
+        explosionEffect.attachChild(spark);
+    }
+
+    private void createSmokeTrail(){
+        smoketrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR);
+        smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F)));
+        smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
+        smoketrail.setStartSize(.2f);
+        smoketrail.setEndSize(1f);
+
+//        smoketrail.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
+        smoketrail.setFacingVelocity(true);
+        smoketrail.setParticlesPerSec(0);
+        smoketrail.setGravity(0, 1, 0);
+        smoketrail.setLowLife(.4f);
+        smoketrail.setHighLife(.5f);
+        smoketrail.setInitialVelocity(new Vector3f(0, 12, 0));
+        smoketrail.setVelocityVariation(1);
+        smoketrail.setImagesX(1);
+        smoketrail.setImagesY(3);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/smoketrail.png"));
+        smoketrail.setMaterial(mat);
+        explosionEffect.attachChild(smoketrail);
+    }
+
+    private void createDebris(){
+        debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR);
+        debris.setSelectRandomImage(true);
+        debris.setRandomAngle(true);
+        debris.setRotateSpeed(FastMath.TWO_PI * 4);
+        debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, (float) (1.0f / COUNT_FACTOR_F)));
+        debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f));
+        debris.setStartSize(.2f);
+        debris.setEndSize(.2f);
+
+//        debris.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
+        debris.setParticlesPerSec(0);
+        debris.setGravity(0, 12f, 0);
+        debris.setLowLife(1.4f);
+        debris.setHighLife(1.5f);
+        debris.setInitialVelocity(new Vector3f(0, 15, 0));
+        debris.setVelocityVariation(.60f);
+        debris.setImagesX(3);
+        debris.setImagesY(3);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/Debris.png"));
+        debris.setMaterial(mat);
+        explosionEffect.attachChild(debris);
+    }
+
+    private void createShockwave(){
+        shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
+//        shockwave.setRandomAngle(true);
+        shockwave.setFaceNormal(Vector3f.UNIT_Y);
+        shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, (float) (.8f / COUNT_FACTOR_F)));
+        shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f));
+
+        shockwave.setStartSize(0f);
+        shockwave.setEndSize(7f);
+
+        shockwave.setParticlesPerSec(0);
+        shockwave.setGravity(0, 0, 0);
+        shockwave.setLowLife(0.5f);
+        shockwave.setHighLife(0.5f);
+        shockwave.setInitialVelocity(new Vector3f(0, 0, 0));
+        shockwave.setVelocityVariation(0f);
+        shockwave.setImagesX(1);
+        shockwave.setImagesY(1);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/shockwave.png"));
+        shockwave.setMaterial(mat);
+        explosionEffect.attachChild(shockwave);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        createFlame();
+        createFlash();
+        createSpark();
+        createRoundSpark();
+        createSmokeTrail();
+        createDebris();
+        createShockwave();
+        explosionEffect.setLocalScale(0.5f);
+        renderManager.preloadScene(explosionEffect);
+
+        cam.setLocation(new Vector3f(0, 3.5135868f, 10));
+        cam.setRotation(new Quaternion(1.5714673E-4f, 0.98696727f, -0.16091813f, 9.6381607E-4f));
+
+        rootNode.attachChild(explosionEffect);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){  
+        time += tpf / speed;
+        if (time > 1f && state == 0){
+            flash.emitAllParticles();
+            spark.emitAllParticles();
+            smoketrail.emitAllParticles();
+            debris.emitAllParticles();
+            shockwave.emitAllParticles();
+            state++;
+        }
+        if (time > 1f + .05f / speed && state == 1){
+            flame.emitAllParticles();
+            roundspark.emitAllParticles();
+            state++;
+        }
+        
+        // rewind the effect
+        if (time > 5 / speed && state == 2){
+            state = 0;
+            time = 0;
+
+            flash.killAllParticles();
+            spark.killAllParticles();
+            smoketrail.killAllParticles();
+            debris.killAllParticles();
+            flame.killAllParticles();
+            roundspark.killAllParticles();
+            shockwave.killAllParticles();
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/effect/TestMovingParticle.java b/engine/src/test/jme3test/effect/TestMovingParticle.java
new file mode 100644
index 0000000..af4b675
--- /dev/null
+++ b/engine/src/test/jme3test/effect/TestMovingParticle.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+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.FastMath;
+import com.jme3.math.Vector3f;
+
+/**
+ * Particle that moves in a circle.
+ *
+ * @author Kirill Vainer
+ */
+public class TestMovingParticle extends SimpleApplication {
+    
+    private ParticleEmitter emit;
+    private float angle = 0;
+    
+    public static void main(String[] args) {
+        TestMovingParticle app = new TestMovingParticle();
+        app.start();
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        emit = new ParticleEmitter("Emitter", Type.Triangle, 300);
+        emit.setGravity(0, 0, 0);
+        emit.setVelocityVariation(1);
+        emit.setLowLife(1);
+        emit.setHighLife(1);
+        emit.setInitialVelocity(new Vector3f(0, .5f, 0));
+        emit.setImagesX(15);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
+        emit.setMaterial(mat);
+        
+        rootNode.attachChild(emit);
+        
+        inputManager.addListener(new ActionListener() {
+            
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if ("setNum".equals(name) && isPressed) {
+                    emit.setNumParticles(1000);
+                }
+            }
+        }, "setNum");
+        
+        inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE));
+    }
+    
+    @Override
+    public void simpleUpdate(float tpf) {
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        float x = FastMath.cos(angle) * 2;
+        float y = FastMath.sin(angle) * 2;
+        emit.setLocalTranslation(x, 0, y);
+    }
+}
diff --git a/engine/src/test/jme3test/effect/TestParticleExportingCloning.java b/engine/src/test/jme3test/effect/TestParticleExportingCloning.java
new file mode 100644
index 0000000..df6390a
--- /dev/null
+++ b/engine/src/test/jme3test/effect/TestParticleExportingCloning.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.shapes.EmitterSphereShape;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class TestParticleExportingCloning extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestParticleExportingCloning app = new TestParticleExportingCloning();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Triangle, 200);
+        emit.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
+        emit.setGravity(0, 0, 0);
+        emit.setLowLife(5);
+        emit.setHighLife(10);
+        emit.setInitialVelocity(new Vector3f(0, 0, 0));
+        emit.setImagesX(15);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
+        emit.setMaterial(mat);
+
+        ParticleEmitter emit2 = emit.clone();
+        emit2.move(3, 0, 0);
+        
+        rootNode.attachChild(emit);
+        rootNode.attachChild(emit2);
+        
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            BinaryExporter.getInstance().save(emit, out);
+            
+            BinaryImporter imp = new BinaryImporter();
+            imp.setAssetManager(assetManager);
+            ParticleEmitter emit3 = (ParticleEmitter) imp.load(out.toByteArray());
+            
+            emit3.move(-3, 0, 0);
+            rootNode.attachChild(emit3);
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+
+            //        Camera cam2 = cam.clone();
+    //        cam.setViewPortTop(0.5f);
+    //        cam2.setViewPortBottom(0.5f);
+    //        ViewPort vp = renderManager.createMainView("SecondView", cam2);
+    //        viewPort.setClearEnabled(false);
+    //        vp.attachScene(rootNode);
+    }
+
+}
diff --git a/engine/src/test/jme3test/effect/TestPointSprite.java b/engine/src/test/jme3test/effect/TestPointSprite.java
new file mode 100644
index 0000000..77064d2
--- /dev/null
+++ b/engine/src/test/jme3test/effect/TestPointSprite.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 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.effect;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.shapes.EmitterBoxShape;
+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.Vector3f;
+
+public class TestPointSprite extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestPointSprite app = new TestPointSprite();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        final ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Point, 10000);
+        emit.setShape(new EmitterBoxShape(new Vector3f(-1.8f, -1.8f, -1.8f),
+                                          new Vector3f(1.8f, 1.8f, 1.8f)));
+        emit.setGravity(0, 0, 0);
+        emit.setLowLife(60);
+        emit.setHighLife(60);
+        emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
+        emit.setImagesX(15);
+        emit.setStartSize(0.05f);
+        emit.setEndSize(0.05f);
+        emit.setStartColor(ColorRGBA.White);
+        emit.setEndColor(ColorRGBA.White);
+        emit.setSelectRandomImage(true);
+        emit.emitAllParticles();
+        
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat.setBoolean("PointSprite", true);
+        mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
+        emit.setMaterial(mat);
+
+        rootNode.attachChild(emit);
+        inputManager.addListener(new ActionListener() {
+            
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if ("setNum".equals(name) && isPressed) {
+                    emit.setNumParticles(5000);
+                    emit.emitAllParticles();
+                }
+            }
+        }, "setNum");
+        
+        inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE));
+        
+    }
+
+}
diff --git a/engine/src/test/jme3test/export/TestAssetLinkNode.java b/engine/src/test/jme3test/export/TestAssetLinkNode.java
new file mode 100644
index 0000000..b15f379
--- /dev/null
+++ b/engine/src/test/jme3test/export/TestAssetLinkNode.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.ModelKey;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.AssetLinkNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestAssetLinkNode extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestAssetLinkNode app = new TestAssetLinkNode();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        AssetLinkNode loaderNode=new AssetLinkNode();
+        loaderNode.addLinkedChild(new ModelKey("Models/MonkeyHead/MonkeyHead.mesh.xml"));
+        //load/attach the children (happens automatically on load)
+//        loaderNode.attachLinkedChildren(assetManager);
+//        rootNode.attachChild(loaderNode);
+
+        //save and load the loaderNode
+        try {
+            //export to byte array
+            ByteArrayOutputStream bout=new ByteArrayOutputStream();
+            BinaryExporter.getInstance().save(loaderNode, bout);
+            //import from byte array, automatically loads the monkeyhead from file
+            ByteArrayInputStream bin=new ByteArrayInputStream(bout.toByteArray());
+            BinaryImporter imp=BinaryImporter.getInstance();
+            imp.setAssetManager(assetManager);
+            Node newLoaderNode=(Node)imp.load(bin);
+            //attach to rootNode
+            rootNode.attachChild(newLoaderNode);
+        } catch (IOException ex) {
+            Logger.getLogger(TestAssetLinkNode.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+
+        rootNode.attachChild(loaderNode);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial( (Material) assetManager.loadAsset(new AssetKey("Common/Materials/RedColor.j3m")));
+        rootNode.attachChild(lightMdl);
+
+        // flourescent main light
+        pl = new PointLight();
+        pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f));
+        rootNode.addLight(pl);
+
+        // sunset light
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f));
+        rootNode.addLight(dl);
+
+        // skylight
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f));
+        rootNode.addLight(dl);
+
+        // white ambient light
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/export/TestOgreConvert.java b/engine/src/test/jme3test/export/TestOgreConvert.java
new file mode 100644
index 0000000..0b00a73
--- /dev/null
+++ b/engine/src/test/jme3test/export/TestOgreConvert.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2010 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.export;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.app.SimpleApplication;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class TestOgreConvert extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestOgreConvert app = new TestOgreConvert();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Spatial ogreModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White);
+        dl.setDirection(new Vector3f(0,-1,-1).normalizeLocal());
+        rootNode.addLight(dl);
+
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            BinaryExporter exp = new BinaryExporter();
+            exp.save(ogreModel, baos);
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+            BinaryImporter imp = new BinaryImporter();
+            imp.setAssetManager(assetManager);
+            Node ogreModelReloaded = (Node) imp.load(bais, null, null);
+            
+            AnimControl control = ogreModelReloaded.getControl(AnimControl.class);
+            AnimChannel chan = control.createChannel();
+            chan.setAnim("Walk");
+
+            rootNode.attachChild(ogreModelReloaded);
+        } catch (IOException ex){
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/games/CubeField.java b/engine/src/test/jme3test/games/CubeField.java
new file mode 100644
index 0000000..ec215b0
--- /dev/null
+++ b/engine/src/test/jme3test/games/CubeField.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2009-2010 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.games;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+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.CullHint;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Dome;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author Kyle "bonechilla" Williams
+ */
+public class CubeField extends SimpleApplication implements AnalogListener {
+
+    public static void main(String[] args) {
+        CubeField app = new CubeField();
+        app.start();
+    }
+
+    private BitmapFont defaultFont;
+
+    private boolean START;
+    private int difficulty, Score, colorInt, highCap, lowCap,diffHelp;
+    private Node player;
+    private Geometry fcube;
+    private ArrayList<Geometry> cubeField;
+    private ArrayList<ColorRGBA> obstacleColors;
+    private float speed, coreTime,coreTime2;
+    private float camAngle = 0;
+    private BitmapText fpsScoreText, pressStart;
+
+    private boolean solidBox = true;
+    private Material playerMaterial;
+    private Material floorMaterial;
+
+    private float fpsRate = 1000f / 1f;
+
+    /**
+     * Initializes game 
+     */
+    @Override
+    public void simpleInitApp() {
+        Logger.getLogger("com.jme3").setLevel(Level.WARNING);
+
+        flyCam.setEnabled(false);
+        setDisplayStatView(false);
+
+        Keys();
+
+        defaultFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        pressStart = new BitmapText(defaultFont, false);
+        fpsScoreText = new BitmapText(defaultFont, false);
+
+        loadText(fpsScoreText, "Current Score: 0", defaultFont, 0, 2, 0);
+        loadText(pressStart, "PRESS ENTER", defaultFont, 0, 5, 0);
+        
+        player = createPlayer();
+        rootNode.attachChild(player);
+        cubeField = new ArrayList<Geometry>();
+        obstacleColors = new ArrayList<ColorRGBA>();
+
+        gameReset();
+    }
+    /**
+     * Used to reset cubeField 
+     */
+    private void gameReset(){
+        Score = 0;
+        lowCap = 10;
+        colorInt = 0;
+        highCap = 40;
+        difficulty = highCap;
+
+        for (Geometry cube : cubeField){
+            cube.removeFromParent();
+        }
+        cubeField.clear();
+
+        if (fcube != null){
+            fcube.removeFromParent();
+        }
+        fcube = createFirstCube();
+
+        obstacleColors.clear();
+        obstacleColors.add(ColorRGBA.Orange);
+        obstacleColors.add(ColorRGBA.Red);
+        obstacleColors.add(ColorRGBA.Yellow);
+        renderer.setBackgroundColor(ColorRGBA.White);
+        speed = lowCap / 400f;
+        coreTime = 20.0f;
+        coreTime2 = 10.0f;
+        diffHelp=lowCap;
+        player.setLocalTranslation(0,0,0);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        camTakeOver(tpf);
+        if (START){
+            gameLogic(tpf);
+        }
+        colorLogic();
+    }
+    /**
+     * Forcefully takes over Camera adding functionality and placing it behind the character
+     * @param tpf Tickes Per Frame
+     */
+    private void camTakeOver(float tpf) {
+        cam.setLocation(player.getLocalTranslation().add(-8, 2, 0));
+        cam.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);
+        
+        Quaternion rot = new Quaternion();
+        rot.fromAngleNormalAxis(camAngle, Vector3f.UNIT_Z);
+        cam.setRotation(cam.getRotation().mult(rot));
+        camAngle *= FastMath.pow(.99f, fpsRate * tpf);
+    }
+
+    @Override
+    public void requestClose(boolean esc) {
+        if (!esc){
+            System.out.println("The game was quit.");
+        }else{
+            System.out.println("Player has Collided. Final Score is " + Score);
+        }
+        context.destroy(false);
+    }
+    /**
+     * Randomly Places a cube on the map between 30 and 90 paces away from player
+     */
+    private void randomizeCube() {
+        Geometry cube = fcube.clone();
+        int playerX = (int) player.getLocalTranslation().getX();
+        int playerZ = (int) player.getLocalTranslation().getZ();
+//        float x = FastMath.nextRandomInt(playerX + difficulty + 10, playerX + difficulty + 150);
+        float x = FastMath.nextRandomInt(playerX + difficulty + 30, playerX + difficulty + 90);
+        float z = FastMath.nextRandomInt(playerZ - difficulty - 50, playerZ + difficulty + 50);
+        cube.getLocalTranslation().set(x, 0, z);
+
+//        playerX+difficulty+30,playerX+difficulty+90
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        if (!solidBox){
+            mat.getAdditionalRenderState().setWireframe(true);
+        }
+        mat.setColor("Color", obstacleColors.get(FastMath.nextRandomInt(0, obstacleColors.size() - 1)));
+        cube.setMaterial(mat);
+
+        rootNode.attachChild(cube);
+        cubeField.add(cube);
+    }
+
+    private Geometry createFirstCube() {
+        Vector3f loc = player.getLocalTranslation();
+        loc.addLocal(4, 0, 0);
+        Box b = new Box(loc, 1, 1, 1);
+
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        geom.setMaterial(mat);
+
+        return geom;
+    }
+
+    private Node createPlayer() {
+        Dome b = new Dome(Vector3f.ZERO, 10, 100, 1);
+        Geometry playerMesh = new Geometry("Box", b);
+
+        playerMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        playerMaterial.setColor("Color", ColorRGBA.Red);
+        playerMesh.setMaterial(playerMaterial);
+        playerMesh.setName("player");
+
+        Box floor = new Box(Vector3f.ZERO.add(playerMesh.getLocalTranslation().getX(),
+                playerMesh.getLocalTranslation().getY() - 1, 0), 100, 0, 100);
+        Geometry floorMesh = new Geometry("Box", floor);
+
+        floorMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        floorMaterial.setColor("Color", ColorRGBA.LightGray);
+        floorMesh.setMaterial(floorMaterial);
+        floorMesh.setName("floor");
+
+        Node playerNode = new Node();
+        playerNode.attachChild(playerMesh);
+        playerNode.attachChild(floorMesh);
+
+        return playerNode;
+    }
+
+    /**
+     * If Game is Lost display Score and Reset the Game
+     */
+    private void gameLost(){
+        START = false;
+        loadText(pressStart, "You lost! Press enter to try again.", defaultFont, 0, 5, 0);
+        gameReset();
+    }
+    
+    /**
+     * Core Game Logic
+     */
+    private void gameLogic(float tpf){
+    	//Subtract difficulty level in accordance to speed every 10 seconds
+    	if(timer.getTimeInSeconds()>=coreTime2){
+			coreTime2=timer.getTimeInSeconds()+10;
+			if(difficulty<=lowCap){
+				difficulty=lowCap;
+			}
+			else if(difficulty>lowCap){
+				difficulty-=5;
+				diffHelp+=1;
+			}
+		}
+    	
+        if(speed<.1f){
+            speed+=.000001f*tpf*fpsRate;
+        }
+
+        player.move(speed * tpf * fpsRate, 0, 0);
+        if (cubeField.size() > difficulty){
+            cubeField.remove(0);
+        }else if (cubeField.size() != difficulty){
+            randomizeCube();
+        }
+
+        if (cubeField.isEmpty()){
+            requestClose(false);
+        }else{
+            for (int i = 0; i < cubeField.size(); i++){
+            	
+            	//better way to check collision
+                Geometry playerModel = (Geometry) player.getChild(0);
+                Geometry cubeModel = cubeField.get(i);
+                cubeModel.updateGeometricState();
+
+                BoundingVolume pVol = playerModel.getWorldBound();
+                BoundingVolume vVol = cubeModel.getWorldBound();
+
+                if (pVol.intersects(vVol)){
+                    gameLost();
+                    return;
+                }
+                //Remove cube if 10 world units behind player
+                if (cubeField.get(i).getLocalTranslation().getX() + 10 < player.getLocalTranslation().getX()){
+                    cubeField.get(i).removeFromParent();
+                    cubeField.remove(cubeField.get(i));
+                }
+
+            }
+        }
+
+        Score += fpsRate * tpf;
+        fpsScoreText.setText("Current Score: "+Score);
+    }
+    /**
+     * Sets up the keyboard bindings
+     */
+    private void Keys() {
+        inputManager.addMapping("START", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("Left",  new KeyTrigger(KeyInput.KEY_LEFT));
+        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addListener(this, "START", "Left", "Right");
+    }
+
+    public void onAnalog(String binding, float value, float tpf) {
+        if (binding.equals("START") && !START){
+            START = true;
+            guiNode.detachChild(pressStart);
+            System.out.println("START");
+        }else if (START == true && binding.equals("Left")){
+            player.move(0, 0, -(speed / 2f) * value * fpsRate);
+            camAngle -= value*tpf;
+        }else if (START == true && binding.equals("Right")){
+            player.move(0, 0, (speed / 2f) * value * fpsRate);
+            camAngle += value*tpf;
+        }
+    }
+
+    /**
+     * Determines the colors of the player, floor, obstacle and background
+     */
+    private void colorLogic() {
+    	if (timer.getTimeInSeconds() >= coreTime){
+            
+        	colorInt++;
+            coreTime = timer.getTimeInSeconds() + 20;
+        
+
+	        switch (colorInt){
+	            case 1:
+	                obstacleColors.clear();
+	                solidBox = false;
+	                obstacleColors.add(ColorRGBA.Green);
+	                renderer.setBackgroundColor(ColorRGBA.Black);
+	                playerMaterial.setColor("Color", ColorRGBA.White);
+			floorMaterial.setColor("Color", ColorRGBA.Black);
+	                break;
+	            case 2:
+	                obstacleColors.set(0, ColorRGBA.Black);
+	                solidBox = true;
+	                renderer.setBackgroundColor(ColorRGBA.White);
+	                playerMaterial.setColor("Color", ColorRGBA.Gray);
+                        floorMaterial.setColor("Color", ColorRGBA.LightGray);
+	                break;
+	            case 3:
+	                obstacleColors.set(0, ColorRGBA.Pink);
+	                break;
+	            case 4:
+	                obstacleColors.set(0, ColorRGBA.Cyan);
+	                obstacleColors.add(ColorRGBA.Magenta);
+	                renderer.setBackgroundColor(ColorRGBA.Gray);
+                        floorMaterial.setColor("Color", ColorRGBA.Gray);
+	                playerMaterial.setColor("Color", ColorRGBA.White);
+	                break;
+	            case 5:
+	                obstacleColors.remove(0);
+	                renderer.setBackgroundColor(ColorRGBA.Pink);
+	                solidBox = false;
+	                playerMaterial.setColor("Color", ColorRGBA.White);
+	                break;
+	            case 6:
+	                obstacleColors.set(0, ColorRGBA.White);
+	                solidBox = true;
+	                renderer.setBackgroundColor(ColorRGBA.Black);
+	                playerMaterial.setColor("Color", ColorRGBA.Gray);
+                        floorMaterial.setColor("Color", ColorRGBA.LightGray);
+	                break;
+	            case 7:
+	                obstacleColors.set(0, ColorRGBA.Green);
+	                renderer.setBackgroundColor(ColorRGBA.Gray);
+	                playerMaterial.setColor("Color", ColorRGBA.Black);
+                        floorMaterial.setColor("Color", ColorRGBA.Orange);
+	                break;
+	            case 8:
+	                obstacleColors.set(0, ColorRGBA.Red);
+                        floorMaterial.setColor("Color", ColorRGBA.Pink);
+	                break;
+	            case 9:
+	                obstacleColors.set(0, ColorRGBA.Orange);
+	                obstacleColors.add(ColorRGBA.Red);
+	                obstacleColors.add(ColorRGBA.Yellow);
+	                renderer.setBackgroundColor(ColorRGBA.White);
+	                playerMaterial.setColor("Color", ColorRGBA.Red);
+	                floorMaterial.setColor("Color", ColorRGBA.Gray);
+	                colorInt=0;
+	                break;
+	            default:
+	                break;
+	        }
+        }
+    }
+    /**
+     * Sets up a BitmapText to be displayed
+     * @param txt the Bitmap Text
+     * @param text the 
+     * @param font the font of the text
+     * @param x    
+     * @param y
+     * @param z
+     */
+    private void loadText(BitmapText txt, String text, BitmapFont font, float x, float y, float z) {
+        txt.setSize(font.getCharSet().getRenderedSize());
+        txt.setLocalTranslation(txt.getLineWidth() * x, txt.getLineHeight() * y, z);
+        txt.setText(text);
+        guiNode.attachChild(txt);
+    }
+} 
\ No newline at end of file
diff --git a/engine/src/test/jme3test/gui/TestBitmapFont.java b/engine/src/test/jme3test/gui/TestBitmapFont.java
new file mode 100644
index 0000000..088046e
--- /dev/null
+++ b/engine/src/test/jme3test/gui/TestBitmapFont.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2009-2010 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.gui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.font.LineWrapMode;
+import com.jme3.font.Rectangle;
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.event.*;
+
+public class TestBitmapFont extends SimpleApplication {
+
+    private String txtB =
+    "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567 890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?";
+    
+    private BitmapText txt;
+    private BitmapText txt2;
+    private BitmapText txt3;
+
+    public static void main(String[] args){
+        TestBitmapFont app = new TestBitmapFont();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        inputManager.addMapping("WordWrap", new KeyTrigger(KeyInput.KEY_TAB));
+        inputManager.addListener(keyListener, "WordWrap");
+        inputManager.addRawInputListener(textListener);
+        
+        BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        txt = new BitmapText(fnt, false);
+        txt.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight()));
+        txt.setSize(fnt.getPreferredSize() * 2f);
+        txt.setText(txtB);
+        txt.setLocalTranslation(0, txt.getHeight(), 0);
+        guiNode.attachChild(txt);
+
+        txt2 = new BitmapText(fnt, false);
+        txt2.setSize(fnt.getPreferredSize() * 1.2f);
+        txt2.setText("Text without restriction. \nText without restriction. Text without restriction. Text without restriction");
+        txt2.setLocalTranslation(0, txt2.getHeight(), 0);
+        guiNode.attachChild(txt2);
+        
+        txt3 = new BitmapText(fnt, false);
+        txt3.setBox(new Rectangle(0, 0, settings.getWidth(), 0));
+        txt3.setText("Press Tab to toggle word-wrap. type text and enter to input text");
+        txt3.setLocalTranslation(0, settings.getHeight()/2, 0);
+        guiNode.attachChild(txt3);
+    }
+    
+    private ActionListener keyListener = new ActionListener() {
+        @Override
+        public void onAction(String name, boolean isPressed, float tpf) {
+            if (name.equals("WordWrap") && !isPressed) {
+                txt.setLineWrapMode( txt.getLineWrapMode() == LineWrapMode.Word ?
+                                        LineWrapMode.NoWrap : LineWrapMode.Word );
+            }            
+        }
+    };
+    
+    private RawInputListener textListener = new RawInputListener() {
+        private StringBuilder str = new StringBuilder();
+        
+        @Override
+        public void onMouseMotionEvent(MouseMotionEvent evt) { } 
+        
+        @Override
+        public void onMouseButtonEvent(MouseButtonEvent evt) { } 
+        
+        @Override
+        public void onKeyEvent(KeyInputEvent evt) {
+            if (evt.isReleased())
+                return;
+            if (evt.getKeyChar() == '\n' || evt.getKeyChar() == '\r') {
+                txt3.setText(str.toString());
+                str.setLength(0);
+            } else {
+                str.append(evt.getKeyChar());
+            }
+        }
+        
+        @Override
+        public void onJoyButtonEvent(JoyButtonEvent evt) { }
+        
+        @Override
+        public void onJoyAxisEvent(JoyAxisEvent evt) { }
+        
+        @Override
+        public void endInput() { }
+        
+        @Override
+        public void beginInput() { }
+
+        @Override
+        public void onTouchEvent(TouchEvent evt) { }
+    };
+
+}
diff --git a/engine/src/test/jme3test/gui/TestBitmapText3D.java b/engine/src/test/jme3test/gui/TestBitmapText3D.java
new file mode 100644
index 0000000..e8c44c0
--- /dev/null
+++ b/engine/src/test/jme3test/gui/TestBitmapText3D.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 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.gui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.font.Rectangle;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+
+public class TestBitmapText3D extends SimpleApplication {
+
+    private String txtB =
+    "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?";
+
+    public static void main(String[] args){
+        TestBitmapText3D app = new TestBitmapText3D();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Quad q = new Quad(6, 3);
+        Geometry g = new Geometry("quad", q);
+        g.setLocalTranslation(0, -3, -0.0001f);
+        g.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(g);
+
+        BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText txt = new BitmapText(fnt, false);
+        txt.setBox(new Rectangle(0, 0, 6, 3));
+        txt.setQueueBucket(Bucket.Transparent);
+        txt.setSize( 0.5f );
+        txt.setText(txtB);
+        rootNode.attachChild(txt);
+    }
+
+}
diff --git a/engine/src/test/jme3test/gui/TestOrtho.java b/engine/src/test/jme3test/gui/TestOrtho.java
new file mode 100644
index 0000000..83a1517
--- /dev/null
+++ b/engine/src/test/jme3test/gui/TestOrtho.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2009-2010 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.gui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.ui.Picture;
+
+public class TestOrtho extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestOrtho app = new TestOrtho();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Picture p = new Picture("Picture");
+        p.move(0, 0, -1); // make it appear behind stats view
+        p.setPosition(0, 0);
+        p.setWidth(settings.getWidth());
+        p.setHeight(settings.getHeight());
+        p.setImage(assetManager, "Interface/Logo/Monkey.png", false);
+
+        // attach geometry to orthoNode
+        guiNode.attachChild(p);
+    }
+
+}
diff --git a/engine/src/test/jme3test/gui/TestSoftwareMouse.java b/engine/src/test/jme3test/gui/TestSoftwareMouse.java
new file mode 100644
index 0000000..32c6801
--- /dev/null
+++ b/engine/src/test/jme3test/gui/TestSoftwareMouse.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 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.gui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.*;
+import com.jme3.math.FastMath;
+import com.jme3.system.AppSettings;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+public class TestSoftwareMouse extends SimpleApplication {
+
+    private Picture cursor;
+
+    private RawInputListener inputListener = new RawInputListener() {
+
+        private float x = 0, y = 0;
+
+        public void beginInput() {
+        }
+        public void endInput() {
+        }
+        public void onJoyAxisEvent(JoyAxisEvent evt) {
+        }
+        public void onJoyButtonEvent(JoyButtonEvent evt) {
+        }
+        public void onMouseMotionEvent(MouseMotionEvent evt) {
+            x += evt.getDX();
+            y += evt.getDY();
+
+            // Prevent mouse from leaving screen
+            AppSettings settings = TestSoftwareMouse.this.settings;
+            x = FastMath.clamp(x, 0, settings.getWidth());
+            y = FastMath.clamp(y, 0, settings.getHeight());
+
+            // adjust for hotspot
+            cursor.setPosition(x, y - 64);
+        }
+        public void onMouseButtonEvent(MouseButtonEvent evt) {
+        }
+        public void onKeyEvent(KeyInputEvent evt) {
+        }
+        public void onTouchEvent(TouchEvent evt) {             
+        }
+    };
+
+    public static void main(String[] args){
+        TestSoftwareMouse app = new TestSoftwareMouse();
+
+//        AppSettings settings = new AppSettings(true);
+//        settings.setFrameRate(60);
+//        app.setSettings(settings);
+
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setEnabled(false);
+//        inputManager.setCursorVisible(false);
+
+        Texture tex = assetManager.loadTexture("Interface/Logo/Cursor.png");
+        
+        cursor = new Picture("cursor");
+        cursor.setTexture(assetManager, (Texture2D) tex, true);
+        cursor.setWidth(64);
+        cursor.setHeight(64);
+        guiNode.attachChild(cursor);
+
+        inputManager.addRawInputListener(inputListener);
+
+//        Image img = tex.getImage();
+//        ByteBuffer data = img.getData(0);
+//        IntBuffer image = BufferUtils.createIntBuffer(64 * 64);
+//        for (int y = 0; y < 64; y++){
+//            for (int x = 0; x < 64; x++){
+//                int rgba = data.getInt();
+//                image.put(rgba);
+//            }
+//        }
+//        image.clear();
+//
+//        try {
+//            Cursor cur = new Cursor(64, 64, 2, 62, 1, image, null);
+//            Mouse.setNativeCursor(cur);
+//        } catch (LWJGLException ex) {
+//            Logger.getLogger(TestSoftwareMouse.class.getName()).log(Level.SEVERE, null, ex);
+//        }
+    }
+}
diff --git a/engine/src/test/jme3test/gui/TestZOrder.java b/engine/src/test/jme3test/gui/TestZOrder.java
new file mode 100644
index 0000000..bc4bd8c
--- /dev/null
+++ b/engine/src/test/jme3test/gui/TestZOrder.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 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.gui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.ui.Picture;
+
+public class TestZOrder extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestZOrder app = new TestZOrder();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Picture p = new Picture("Picture1");
+        p.move(0,0,-1);
+        p.setPosition(100, 100);
+        p.setWidth(100);
+        p.setHeight(100);
+        p.setImage(assetManager, "Interface/Logo/Monkey.png", false);
+        guiNode.attachChild(p);
+
+        Picture p2 = new Picture("Picture2");
+        p2.move(0,0,1.001f);
+        p2.setPosition(150, 150);
+        p2.setWidth(100);
+        p2.setHeight(100);
+        p2.setImage(assetManager, "Interface/Logo/Monkey.png", false);
+        guiNode.attachChild(p2);
+    }
+
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloAnimation.java b/engine/src/test/jme3test/helloworld/HelloAnimation.java
new file mode 100644
index 0000000..101796b
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloAnimation.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.AnimEventListener;
+import com.jme3.animation.LoopMode;
+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.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+/** Sample 7 - how to load an OgreXML model and play an animation, 
+ * using channels, a controller, and an AnimEventListener. */
+public class HelloAnimation extends SimpleApplication
+                         implements AnimEventListener {
+
+  Node player;
+  private AnimChannel channel;
+  private AnimControl control;
+
+  public static void main(String[] args) {
+    HelloAnimation app = new HelloAnimation();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    viewPort.setBackgroundColor(ColorRGBA.LightGray);
+    initKeys();
+
+    /** Add a light source so we can see the model */
+    DirectionalLight dl = new DirectionalLight();
+    dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
+    rootNode.addLight(dl);
+
+    /** Load a model that contains animation */
+    player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+    player.setLocalScale(0.5f);
+    rootNode.attachChild(player);
+
+    /** Create a controller and channels. */
+    control = player.getControl(AnimControl.class);
+    control.addListener(this);
+    channel = control.createChannel();
+    channel.setAnim("stand");
+  }
+
+  /** Use this listener to trigger something after an animation is done. */
+  public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+    if (animName.equals("Walk")) {
+      /** After "walk", reset to "stand". */
+      channel.setAnim("stand", 0.50f);
+      channel.setLoopMode(LoopMode.DontLoop);
+      channel.setSpeed(1f);
+    }
+  }
+
+  /** Use this listener to trigger something between two animations. */
+  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    // unused
+  }
+
+  /** Custom Keybindings: Mapping a named action to a key input. */
+  private void initKeys() {
+    inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
+    inputManager.addListener(actionListener, "Walk");
+  }
+
+  /** Definining the named action that can be triggered by key inputs. */
+  private ActionListener actionListener = new ActionListener() {
+    public void onAction(String name, boolean keyPressed, float tpf) {
+      if (name.equals("Walk") && !keyPressed) {
+        if (!channel.getAnimationName().equals("Walk")) {
+          /** Play the "walk" animation! */
+          channel.setAnim("Walk", 0.50f);
+          channel.setLoopMode(LoopMode.Loop);
+        }
+      }
+    }
+  };
+
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloAssets.java b/engine/src/test/jme3test/helloworld/HelloAssets.java
new file mode 100644
index 0000000..7635abb
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloAssets.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+
+/** Sample 3 - how to load an OBJ model, and OgreXML model, 
+ * a material/texture, or text. */
+public class HelloAssets extends SimpleApplication {
+
+    public static void main(String[] args) {
+        HelloAssets app = new HelloAssets();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+        /** Load a teapot model (OBJ file from test-data) */
+        Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        Material mat_default = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+        teapot.setMaterial(mat_default);
+        rootNode.attachChild(teapot);
+
+        /** Create a wall (Box with material and texture from test-data) */
+        Box box = new Box(Vector3f.ZERO, 2.5f,2.5f,1.0f);
+        Spatial wall = new Geometry("Box", box );
+        Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        wall.setMaterial(mat_brick);
+        wall.setLocalTranslation(2.0f,-2.5f,0.0f);
+        rootNode.attachChild(wall);
+
+        /** Display a line of text (default font from test-data) */
+        guiNode.detachAllChildren();
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText helloText = new BitmapText(guiFont, false);
+        helloText.setSize(guiFont.getCharSet().getRenderedSize());
+        helloText.setText("Hello World");
+        helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
+        guiNode.attachChild(helloText);
+
+        /** Load a Ninja model (OgreXML + material + texture from test_data) */
+        Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
+        ninja.scale(0.05f, 0.05f, 0.05f);
+        ninja.rotate(0.0f, -3.0f, 0.0f);
+        ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
+        rootNode.attachChild(ninja);
+        /** You must add a light to make the model visible */
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
+        rootNode.addLight(sun);
+    }
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloAudio.java b/engine/src/test/jme3test/helloworld/HelloAudio.java
new file mode 100644
index 0000000..b614396
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloAudio.java
@@ -0,0 +1,84 @@
+package jme3test.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+/** Sample 11 - playing 3D audio. */
+public class HelloAudio extends SimpleApplication {
+
+  private AudioNode audio_gun;
+  private AudioNode audio_nature;
+  private Geometry player;
+
+  public static void main(String[] args) {
+    HelloAudio app = new HelloAudio();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    flyCam.setMoveSpeed(40);
+    
+    /** just a blue box floating in space */
+    Box box1 = new Box(Vector3f.ZERO, 1, 1, 1);
+    player = new Geometry("Player", box1);
+    Material mat1 = new Material(assetManager, 
+            "Common/MatDefs/Misc/Unshaded.j3md");
+    mat1.setColor("Color", ColorRGBA.Blue);
+    player.setMaterial(mat1);
+    rootNode.attachChild(player);
+
+    /** custom init methods, see below */
+    initKeys();
+    initAudio();
+  }
+
+  /** We create two audio nodes. */
+  private void initAudio() {
+    /* gun shot sound is to be triggered by a mouse click. */
+    audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false);
+    audio_gun.setLooping(false);
+    audio_gun.setVolume(2);
+    rootNode.attachChild(audio_gun);
+
+    /* nature sound - keeps playing in a loop. */
+    audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);
+    audio_nature.setLooping(true);  // activate continuous playing
+    audio_nature.setPositional(true);
+    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
+    audio_nature.setVolume(3);
+    rootNode.attachChild(audio_nature);
+    audio_nature.play(); // play continuously!
+  }
+
+  /** Declaring "Shoot" action, mapping it to a trigger (mouse click). */
+  private void initKeys() {
+    inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
+    inputManager.addListener(actionListener, "Shoot");
+  }
+
+  /** Defining the "Shoot" action: Play a gun sound. */
+  private ActionListener actionListener = new ActionListener() {
+    @Override
+    public void onAction(String name, boolean keyPressed, float tpf) {
+      if (name.equals("Shoot") && !keyPressed) {
+        audio_gun.playInstance(); // play each instance once!
+      }
+    }
+  };
+
+  /** Move the listener with the a camera - for 3D audio. */
+  @Override
+  public void simpleUpdate(float tpf) {
+    listener.setLocation(cam.getLocation());
+    listener.setRotation(cam.getRotation());
+  }
+
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloCollision.java b/engine/src/test/jme3test/helloworld/HelloCollision.java
new file mode 100644
index 0000000..35d91ed
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloCollision.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.util.CollisionShapeFactory;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+
+/**
+ * Example 9 - How to make walls and floors solid.
+ * This collision code uses Physics and a custom Action Listener.
+ * @author normen, with edits by Zathras
+ */
+public class HelloCollision extends SimpleApplication
+        implements ActionListener {
+
+  private Spatial sceneModel;
+  private BulletAppState bulletAppState;
+  private RigidBodyControl landscape;
+  private CharacterControl player;
+  private Vector3f walkDirection = new Vector3f();
+  private boolean left = false, right = false, up = false, down = false;
+
+  public static void main(String[] args) {
+    HelloCollision app = new HelloCollision();
+    app.start();
+  }
+
+  public void simpleInitApp() {
+    /** Set up Physics */
+    bulletAppState = new BulletAppState();
+    stateManager.attach(bulletAppState);
+    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+
+    // We re-use the flyby camera for rotation, while positioning is handled by physics
+    viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+    flyCam.setMoveSpeed(100);
+    setUpKeys();
+    setUpLight();
+
+    // We load the scene from the zip file and adjust its size.
+    assetManager.registerLocator("town.zip", ZipLocator.class.getName());
+    sceneModel = assetManager.loadModel("main.scene");
+    sceneModel.setLocalScale(2f);
+
+    // We set up collision detection for the scene by creating a
+    // compound collision shape and a static RigidBodyControl with mass zero.
+    CollisionShape sceneShape =
+            CollisionShapeFactory.createMeshShape((Node) sceneModel);
+    landscape = new RigidBodyControl(sceneShape, 0);
+    sceneModel.addControl(landscape);
+
+    // We set up collision detection for the player by creating
+    // a capsule collision shape and a CharacterControl.
+    // The CharacterControl offers extra settings for
+    // size, stepheight, jumping, falling, and gravity.
+    // We also put the player in its starting position.
+    CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
+    player = new CharacterControl(capsuleShape, 0.05f);
+    player.setJumpSpeed(20);
+    player.setFallSpeed(30);
+    player.setGravity(30);
+    player.setPhysicsLocation(new Vector3f(0, 10, 0));
+
+    // We attach the scene and the player to the rootnode and the physics space,
+    // to make them appear in the game world.
+    rootNode.attachChild(sceneModel);
+    bulletAppState.getPhysicsSpace().add(landscape);
+    bulletAppState.getPhysicsSpace().add(player);
+  }
+
+  private void setUpLight() {
+    // We add light so we see the scene
+    AmbientLight al = new AmbientLight();
+    al.setColor(ColorRGBA.White.mult(1.3f));
+    rootNode.addLight(al);
+
+    DirectionalLight dl = new DirectionalLight();
+    dl.setColor(ColorRGBA.White);
+    dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
+    rootNode.addLight(dl);
+  }
+
+  /** We over-write some navigational key mappings here, so we can
+   * add physics-controlled walking and jumping: */
+  private void setUpKeys() {
+    inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
+    inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
+    inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
+    inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
+    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
+    inputManager.addListener(this, "Left");
+    inputManager.addListener(this, "Right");
+    inputManager.addListener(this, "Up");
+    inputManager.addListener(this, "Down");
+    inputManager.addListener(this, "Jump");
+  }
+
+  /** These are our custom actions triggered by key presses.
+   * We do not walk yet, we just keep track of the direction the user pressed. */
+  public void onAction(String binding, boolean value, float tpf) {
+    if (binding.equals("Left")) {
+      if (value) { left = true; } else { left = false; }
+    } else if (binding.equals("Right")) {
+      if (value) { right = true; } else { right = false; }
+    } else if (binding.equals("Up")) {
+      if (value) { up = true; } else { up = false; }
+    } else if (binding.equals("Down")) {
+      if (value) { down = true; } else { down = false; }
+    } else if (binding.equals("Jump")) {
+      player.jump();
+    }
+  }
+
+  /**
+   * This is the main event loop--walking happens here.
+   * We check in which direction the player is walking by interpreting
+   * the camera direction forward (camDir) and to the side (camLeft).
+   * The setWalkDirection() command is what lets a physics-controlled player walk.
+   * We also make sure here that the camera moves with player.
+   */
+  @Override
+  public void simpleUpdate(float tpf) {
+    Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
+    Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
+    walkDirection.set(0, 0, 0);
+    if (left)  { walkDirection.addLocal(camLeft); }
+    if (right) { walkDirection.addLocal(camLeft.negate()); }
+    if (up)    { walkDirection.addLocal(camDir); }
+    if (down)  { walkDirection.addLocal(camDir.negate()); }
+    player.setWalkDirection(walkDirection);
+    cam.setLocation(player.getPhysicsLocation());
+  }
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloEffects.java b/engine/src/test/jme3test/helloworld/HelloEffects.java
new file mode 100644
index 0000000..70e2e78
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloEffects.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+
+/** Sample 11 - how to create fire, water, and explosion effects. */
+public class HelloEffects extends SimpleApplication {
+
+  public static void main(String[] args) {
+    HelloEffects app = new HelloEffects();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    ParticleEmitter fire = 
+            new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
+    Material mat_red = new Material(assetManager, 
+            "Common/MatDefs/Misc/Particle.j3md");
+    mat_red.setTexture("Texture", assetManager.loadTexture(
+            "Effects/Explosion/flame.png"));
+    fire.setMaterial(mat_red);
+    fire.setImagesX(2); 
+    fire.setImagesY(2); // 2x2 texture animation
+    fire.setEndColor(  new ColorRGBA(1f, 0f, 0f, 1f));   // red
+    fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
+    fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0));
+    fire.setStartSize(1.5f);
+    fire.setEndSize(0.1f);
+    fire.setGravity(0, 0, 0);
+    fire.setLowLife(1f);
+    fire.setHighLife(3f);
+    fire.getParticleInfluencer().setVelocityVariation(0.3f);
+    rootNode.attachChild(fire);
+
+    ParticleEmitter debris = 
+            new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 10);
+    Material debris_mat = new Material(assetManager, 
+            "Common/MatDefs/Misc/Particle.j3md");
+    debris_mat.setTexture("Texture", assetManager.loadTexture(
+            "Effects/Explosion/Debris.png"));
+    debris.setMaterial(debris_mat);
+    debris.setImagesX(3); 
+    debris.setImagesY(3); // 3x3 texture animation
+    debris.setSelectRandomImage(true);
+    debris.setRotateSpeed(4);
+    debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 4, 0));
+    debris.setStartColor(ColorRGBA.White);
+    debris.setGravity(0, 6, 0);
+    debris.getParticleInfluencer().setVelocityVariation(.60f);
+    rootNode.attachChild(debris);
+    debris.emitAllParticles();
+
+//    ParticleEmitter water = 
+//            new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 20);
+//    Material mat_blue = new Material(assetManager, 
+//            "Common/MatDefs/Misc/Particle.j3md");
+//    mat_blue.setTexture("Texture", assetManager.loadTexture(
+//            "Effects/Explosion/flame.png"));
+//    water.setMaterial(mat_blue);
+//    water.setImagesX(2); 
+//    water.setImagesY(2); // 2x2 texture animation
+//    water.setStartColor( ColorRGBA.Blue); 
+//    water.setEndColor( ColorRGBA.Cyan); 
+//    water.getParticleInfluencer().setInitialVelocity(new Vector3f(0, -4, 0));
+//    water.setStartSize(1f);
+//    water.setEndSize(1.5f);
+//    water.setGravity(0,1,0);
+//    water.setLowLife(1f);
+//    water.setHighLife(1f);
+//    water.getParticleInfluencer().setVelocityVariation(0.1f);
+//    water.setLocalTranslation(0, 6, 0);
+//    rootNode.attachChild(water);
+
+  }
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloInput.java b/engine/src/test/jme3test/helloworld/HelloInput.java
new file mode 100644
index 0000000..c749c96
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloInput.java
@@ -0,0 +1,110 @@
+/*

+ * Copyright (c) 2009-2010 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.helloworld;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.input.KeyInput;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.AnalogListener;

+import com.jme3.input.controls.KeyTrigger;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.shape.Box;

+

+/** Sample 5 - how to map keys and mousebuttons to actions */

+public class HelloInput extends SimpleApplication {

+

+  public static void main(String[] args) {

+    HelloInput app = new HelloInput();

+    app.start();

+  }

+  protected Geometry player;

+  Boolean isRunning=true;

+

+  @Override

+  public void simpleInitApp() {

+    Box b = new Box(Vector3f.ZERO, 1, 1, 1);

+    player = new Geometry("Player", b);

+    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    mat.setColor("Color", ColorRGBA.Blue);

+    player.setMaterial(mat);

+    rootNode.attachChild(player);

+    initKeys(); // load my custom keybinding

+  }

+

+  /** Custom Keybinding: Map named actions to inputs. */

+  private void initKeys() {

+    /** You can map one or several inputs to one named mapping. */

+    inputManager.addMapping("Pause",  new KeyTrigger(keyInput.KEY_P));

+    inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));

+    inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_K));

+    inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE), // spacebar!

+                                      new MouseButtonTrigger(MouseInput.BUTTON_LEFT) );        // left click!

+    /** Add the named mappings to the action listeners. */

+    inputManager.addListener(actionListener, new String[]{"Pause"});

+    inputManager.addListener(analogListener, new String[]{"Left", "Right", "Rotate"});

+  }

+

+  /** Use this listener for KeyDown/KeyUp events */

+  private ActionListener actionListener = new ActionListener() {

+    public void onAction(String name, boolean keyPressed, float tpf) {

+      if (name.equals("Pause") && !keyPressed) {

+        isRunning = !isRunning;

+      }

+    }

+  };

+

+  /** Use this listener for continuous events */

+  private AnalogListener analogListener = new AnalogListener() {

+    public void onAnalog(String name, float value, float tpf) {

+      if (isRunning) {

+        if (name.equals("Rotate")) {

+          player.rotate(0, value, 0);

+        }

+        if (name.equals("Right")) {

+          player.move((new Vector3f(value, 0,0)) );

+        }

+        if (name.equals("Left")) {

+          player.move(new Vector3f(-value, 0,0));

+        }

+      } else {

+        System.out.println("Press P to unpause.");

+      }

+    }

+  };

+

+}

diff --git a/engine/src/test/jme3test/helloworld/HelloJME3.java b/engine/src/test/jme3test/helloworld/HelloJME3.java
new file mode 100644
index 0000000..3be80f2
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloJME3.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+/** Sample 1 - how to get started with the most simple JME 3 application.
+ * Display a blue 3D cube and view from all sides by
+ * moving the mouse and pressing the WASD keys. */
+public class HelloJME3 extends SimpleApplication {
+
+    public static void main(String[] args){
+        HelloJME3 app = new HelloJME3();
+        app.start(); // start the game
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1); // create cube shape at the origin
+        Geometry geom = new Geometry("Box", b);  // create cube geometry from the shape
+        Material mat = new Material(assetManager,
+          "Common/MatDefs/Misc/Unshaded.j3md");  // create a simple material
+        mat.setColor("Color", ColorRGBA.Blue);   // set color of material to blue
+        geom.setMaterial(mat);                   // set the cube's material
+        rootNode.attachChild(geom);              // make the cube appear in the scene
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/helloworld/HelloLoop.java b/engine/src/test/jme3test/helloworld/HelloLoop.java
new file mode 100644
index 0000000..0aa73dd
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloLoop.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+/** Sample 4 - how to trigger repeating actions from the main event loop.
+ * In this example, you use the loop to make the player character 
+ * rotate continuously. */
+public class HelloLoop extends SimpleApplication {
+
+    public static void main(String[] args){
+        HelloLoop app = new HelloLoop();
+        app.start();
+    }
+
+    protected Geometry player;
+
+    @Override
+    public void simpleInitApp() {
+        /** this blue box is our player character */
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        player = new Geometry("blue cube", b);
+        Material mat = new Material(assetManager,
+          "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        player.setMaterial(mat);
+        rootNode.attachChild(player);
+    }
+
+    /* Use the main event loop to trigger repeating actions. */
+    @Override
+    public void simpleUpdate(float tpf) {
+        // make the player rotate:
+        player.rotate(0, 2*tpf, 0); 
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/helloworld/HelloMaterial.java b/engine/src/test/jme3test/helloworld/HelloMaterial.java
new file mode 100644
index 0000000..e930c91
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloMaterial.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Texture;
+import com.jme3.util.TangentBinormalGenerator;
+
+/** Sample 6 - how to give an object's surface a material and texture.
+ * How to make objects transparent, or let colors "leak" through partially
+ * transparent textures. How to make bumpy and shiny surfaces.  */
+public class HelloMaterial extends SimpleApplication {
+
+  public static void main(String[] args) {
+    HelloMaterial app = new HelloMaterial();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+
+    /** A simple textured cube -- in good MIP map quality. */
+    Box boxshape1 = new Box(new Vector3f(-3f,1.1f,0f), 1f,1f,1f);
+    Geometry cube = new Geometry("My Textured Box", boxshape1);
+    Material mat_stl = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    Texture tex_ml = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
+    mat_stl.setTexture("ColorMap", tex_ml);
+    cube.setMaterial(mat_stl);
+    rootNode.attachChild(cube);
+
+    /** A translucent/transparent texture, similar to a window frame. */
+    Box boxshape3 = new Box(new Vector3f(0f,0f,0f), 1f,1f,0.01f);
+    Geometry window_frame = new Geometry("window frame", boxshape3);
+    Material mat_tt = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    mat_tt.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+    mat_tt.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);  // activate transparency
+    window_frame.setQueueBucket(Bucket.Transparent);
+    window_frame.setMaterial(mat_tt);
+    rootNode.attachChild(window_frame);
+
+    /** A cube with its base color "leaking" through a partially transparent texture */
+    Box boxshape4 = new Box(new Vector3f(3f,-1f,0f), 1f,1f,1f);
+    Geometry cube_leak = new Geometry("Leak-through color cube", boxshape4);
+    Material mat_tl = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");
+    mat_tl.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+    mat_tl.setColor("Color", new ColorRGBA(1f,0f,1f, 1f)); // purple
+    cube_leak.setMaterial(mat_tl);
+    rootNode.attachChild(cube_leak);
+    // cube_leak.setMaterial((Material) assetManager.loadAsset( "Materials/LeakThrough.j3m"));
+
+    /** A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */
+    Sphere rock = new Sphere(32,32, 2f);
+    Geometry shiny_rock = new Geometry("Shiny rock", rock);
+    rock.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres
+    TangentBinormalGenerator.generate(rock);           // for lighting effect
+    Material mat_lit = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+    mat_lit.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
+    mat_lit.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
+    mat_lit.setBoolean("UseMaterialColors",true);    
+    mat_lit.setColor("Specular",ColorRGBA.White);
+    mat_lit.setColor("Diffuse",ColorRGBA.White);
+    mat_lit.setFloat("Shininess", 5f); // [0,128]
+    shiny_rock.setMaterial(mat_lit);
+    shiny_rock.setLocalTranslation(0,2,-2); // Move it a bit
+    shiny_rock.rotate(1.6f, 0, 0);          // Rotate it a bit
+    rootNode.attachChild(shiny_rock);
+    /** Must add a light to make the lit object visible! */
+    DirectionalLight sun = new DirectionalLight();
+    sun.setDirection(new Vector3f(1,0,-2).normalizeLocal());
+    sun.setColor(ColorRGBA.White);
+    rootNode.addLight(sun);
+
+  }
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloNode.java b/engine/src/test/jme3test/helloworld/HelloNode.java
new file mode 100644
index 0000000..ccda211
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloNode.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+
+/** Sample 2 - How to use nodes as handles to manipulate objects in the scene.
+ * You can rotate, translate, and scale objects by manipulating their parent nodes.
+ * The Root Node is special: Only what is attached to the Root Node appears in the scene. */
+public class HelloNode extends SimpleApplication {
+
+    public static void main(String[] args){
+        HelloNode app = new HelloNode();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+        /** create a blue box at coordinates (1,-1,1) */
+        Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);
+        Geometry blue = new Geometry("Box", box1);
+        Material mat1 = new Material(assetManager, 
+                "Common/MatDefs/Misc/Unshaded.j3md");
+        mat1.setColor("Color", ColorRGBA.Blue);
+        blue.setMaterial(mat1);
+
+        /** create a red box straight above the blue one at (1,3,1) */
+        Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);
+        Geometry red = new Geometry("Box", box2);
+        Material mat2 = new Material(assetManager, 
+                "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setColor("Color", ColorRGBA.Red);
+        red.setMaterial(mat2);
+
+        /** Create a pivot node at (0,0,0) and attach it to the root node */
+        Node pivot = new Node("pivot");
+        rootNode.attachChild(pivot); // put this node in the scene
+
+        /** Attach the two boxes to the *pivot* node. (And transitively to the root node.) */
+        pivot.attachChild(blue);
+        pivot.attachChild(red);
+        /** Rotate the pivot node: Note that both boxes have rotated! */
+        pivot.rotate(.4f,.4f,0f);
+    }
+}
+
diff --git a/engine/src/test/jme3test/helloworld/HelloPhysics.java b/engine/src/test/jme3test/helloworld/HelloPhysics.java
new file mode 100644
index 0000000..7a30cb8
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloPhysics.java
@@ -0,0 +1,228 @@
+/**

+ * Copyright (c) 2009-2010 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.helloworld;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.asset.TextureKey;

+import com.jme3.bullet.BulletAppState;

+import com.jme3.bullet.control.RigidBodyControl;

+import com.jme3.font.BitmapText;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.material.Material;

+import com.jme3.math.Vector2f;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.shape.Box;

+import com.jme3.scene.shape.Sphere;

+import com.jme3.scene.shape.Sphere.TextureMode;

+import com.jme3.texture.Texture;

+import com.jme3.texture.Texture.WrapMode;

+

+/**

+ * Example 12 - how to give objects physical properties so they bounce and fall.

+ * @author base code by double1984, updated by zathras

+ */

+public class HelloPhysics extends SimpleApplication {

+

+  public static void main(String args[]) {

+    HelloPhysics app = new HelloPhysics();

+    app.start();

+  }

+

+  /** Prepare the Physics Application State (jBullet) */

+  private BulletAppState bulletAppState;

+

+  /** Prepare Materials */

+  Material wall_mat;

+  Material stone_mat;

+  Material floor_mat;

+

+  /** Prepare geometries and physical nodes for bricks and cannon balls. */

+  private RigidBodyControl    brick_phy;

+  private static final Box    box;

+  private RigidBodyControl    ball_phy;

+  private static final Sphere sphere;

+  private RigidBodyControl    floor_phy;

+  private static final Box    floor;

+  

+  /** dimensions used for bricks and wall */

+  private static final float brickLength = 0.48f;

+  private static final float brickWidth  = 0.24f;

+  private static final float brickHeight = 0.12f;

+

+  static {

+    /** Initialize the cannon ball geometry */

+    sphere = new Sphere(32, 32, 0.4f, true, false);

+    sphere.setTextureMode(TextureMode.Projected);

+    /** Initialize the brick geometry */

+    box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);

+    box.scaleTextureCoordinates(new Vector2f(1f, .5f));

+    /** Initialize the floor geometry */

+    floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);

+    floor.scaleTextureCoordinates(new Vector2f(3, 6));

+  }

+

+  @Override

+  public void simpleInitApp() {

+    /** Set up Physics Game */

+    bulletAppState = new BulletAppState();

+    stateManager.attach(bulletAppState);

+    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);

+    /** Configure cam to look at scene */

+    cam.setLocation(new Vector3f(0, 4f, 6f));

+    cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);

+    /** Initialize the scene, materials, inputs, and physics space */

+    initInputs();

+    initMaterials();

+    initWall();

+    initFloor();

+    initCrossHairs();

+  }

+

+  /** Add InputManager action: Left click triggers shooting. */

+  private void initInputs() {

+    inputManager.addMapping("shoot", 

+            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

+    inputManager.addListener(actionListener, "shoot");

+  }

+

+  /**

+   * Every time the shoot action is triggered, a new cannon ball is produced.

+   * The ball is set up to fly from the camera position in the camera direction.

+   */

+  private ActionListener actionListener = new ActionListener() {

+    public void onAction(String name, boolean keyPressed, float tpf) {

+      if (name.equals("shoot") && !keyPressed) {

+        makeCannonBall();

+      }

+    }

+  };

+

+  /** Initialize the materials used in this scene. */

+  public void initMaterials() {

+    wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");

+    key.setGenerateMips(true);

+    Texture tex = assetManager.loadTexture(key);

+    wall_mat.setTexture("ColorMap", tex);

+

+    stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");

+    key2.setGenerateMips(true);

+    Texture tex2 = assetManager.loadTexture(key2);

+    stone_mat.setTexture("ColorMap", tex2);

+

+    floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");

+    key3.setGenerateMips(true);

+    Texture tex3 = assetManager.loadTexture(key3);

+    tex3.setWrap(WrapMode.Repeat);

+    floor_mat.setTexture("ColorMap", tex3);

+  }

+

+  /** Make a solid floor and add it to the scene. */

+  public void initFloor() {

+    Geometry floor_geo = new Geometry("Floor", floor);

+    floor_geo.setMaterial(floor_mat);

+    floor_geo.setLocalTranslation(0, -0.1f, 0);

+    this.rootNode.attachChild(floor_geo);

+    /* Make the floor physical with mass 0.0f! */

+    floor_phy = new RigidBodyControl(0.0f);

+    floor_geo.addControl(floor_phy);

+    bulletAppState.getPhysicsSpace().add(floor_phy);

+  }

+

+  /** This loop builds a wall out of individual bricks. */

+  public void initWall() {

+    float startpt = brickLength / 4;

+    float height = 0;

+    for (int j = 0; j < 15; j++) {

+      for (int i = 0; i < 6; i++) {

+        Vector3f vt =

+         new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);

+        makeBrick(vt);

+      }

+      startpt = -startpt;

+      height += 2 * brickHeight;

+    }

+  }

+

+  /** This method creates one individual physical brick. */

+  public void makeBrick(Vector3f loc) {

+    /** Create a brick geometry and attach to scene graph. */

+    Geometry brick_geo = new Geometry("brick", box);

+    brick_geo.setMaterial(wall_mat);

+    rootNode.attachChild(brick_geo);

+    /** Position the brick geometry  */

+    brick_geo.setLocalTranslation(loc);

+    /** Make brick physical with a mass > 0.0f. */

+    brick_phy = new RigidBodyControl(2f);

+    /** Add physical brick to physics space. */

+    brick_geo.addControl(brick_phy);

+    bulletAppState.getPhysicsSpace().add(brick_phy);

+  }

+

+  /** This method creates one individual physical cannon ball.

+   * By defaul, the ball is accelerated and flies

+   * from the camera position in the camera direction.*/

+   public void makeCannonBall() {

+    /** Create a cannon ball geometry and attach to scene graph. */

+    Geometry ball_geo = new Geometry("cannon ball", sphere);

+    ball_geo.setMaterial(stone_mat);

+    rootNode.attachChild(ball_geo);

+    /** Position the cannon ball  */

+    ball_geo.setLocalTranslation(cam.getLocation());

+    /** Make the ball physcial with a mass > 0.0f */

+    ball_phy = new RigidBodyControl(1f);

+    /** Add physical ball to physics space. */

+    ball_geo.addControl(ball_phy);

+    bulletAppState.getPhysicsSpace().add(ball_phy);

+    /** Accelerate the physcial ball to shoot it. */

+    ball_phy.setLinearVelocity(cam.getDirection().mult(25));

+  }

+

+  /** A plus sign used as crosshairs to help the player with aiming.*/

+  protected void initCrossHairs() {

+    guiNode.detachAllChildren();

+    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

+    BitmapText ch = new BitmapText(guiFont, false);

+    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

+    ch.setText("+");        // fake crosshairs :)

+    ch.setLocalTranslation( // center

+      settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,

+      settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

+    guiNode.attachChild(ch);

+  }

+}

diff --git a/engine/src/test/jme3test/helloworld/HelloPicking.java b/engine/src/test/jme3test/helloworld/HelloPicking.java
new file mode 100644
index 0000000..5881cc0
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloPicking.java
@@ -0,0 +1,180 @@
+/*

+ * Copyright (c) 2009-2010 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.helloworld;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.collision.CollisionResult;

+import com.jme3.collision.CollisionResults;

+import com.jme3.font.BitmapText;

+import com.jme3.input.KeyInput;

+import com.jme3.input.MouseInput;

+import com.jme3.input.controls.ActionListener;

+import com.jme3.input.controls.KeyTrigger;

+import com.jme3.input.controls.MouseButtonTrigger;

+import com.jme3.light.DirectionalLight;

+import com.jme3.material.Material;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Ray;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.shape.Box;

+import com.jme3.scene.shape.Sphere;

+

+/** Sample 8 - how to let the user pick (select) objects in the scene 

+ * using the mouse or key presses. Can be used for shooting, opening doors, etc. */

+public class HelloPicking extends SimpleApplication {

+

+  public static void main(String[] args) {

+    HelloPicking app = new HelloPicking();

+    app.start();

+  }

+  Node shootables;

+  Geometry mark;

+

+  @Override

+  public void simpleInitApp() {

+    initCrossHairs(); // a "+" in the middle of the screen to help aiming

+    initKeys();       // load custom key mappings

+    initMark();       // a red sphere to mark the hit

+

+    /** create four colored boxes and a floor to shoot at: */

+    shootables = new Node("Shootables");

+    rootNode.attachChild(shootables);

+    shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f));

+    shootables.attachChild(makeCube("a tin can", 1f, -2f, 0f));

+    shootables.attachChild(makeCube("the Sheriff", 0f, 1f, -2f));

+    shootables.attachChild(makeCube("the Deputy", 1f, 0f, -4f));

+    shootables.attachChild(makeFloor());

+    shootables.attachChild(makeCharacter());

+  }

+

+  /** Declaring the "Shoot" action and mapping to its triggers. */

+  private void initKeys() {

+    inputManager.addMapping("Shoot",

+      new KeyTrigger(KeyInput.KEY_SPACE), // trigger 1: spacebar

+      new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); // trigger 2: left-button click

+    inputManager.addListener(actionListener, "Shoot");

+  }

+  /** Defining the "Shoot" action: Determine what was hit and how to respond. */

+  private ActionListener actionListener = new ActionListener() {

+

+    public void onAction(String name, boolean keyPressed, float tpf) {

+      if (name.equals("Shoot") && !keyPressed) {

+        // 1. Reset results list.

+        CollisionResults results = new CollisionResults();

+        // 2. Aim the ray from cam loc to cam direction.

+        Ray ray = new Ray(cam.getLocation(), cam.getDirection());

+        // 3. Collect intersections between Ray and Shootables in results list.

+        shootables.collideWith(ray, results);

+        // 4. Print the results

+        System.out.println("----- Collisions? " + results.size() + "-----");

+        for (int i = 0; i < results.size(); i++) {

+          // For each hit, we know distance, impact point, name of geometry.

+          float dist = results.getCollision(i).getDistance();

+          Vector3f pt = results.getCollision(i).getContactPoint();

+          String hit = results.getCollision(i).getGeometry().getName();

+          System.out.println("* Collision #" + i);

+          System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");

+        }

+        // 5. Use the results (we mark the hit object)

+        if (results.size() > 0) {

+          // The closest collision point is what was truly hit:

+          CollisionResult closest = results.getClosestCollision();

+          // Let's interact - we mark the hit with a red dot.

+          mark.setLocalTranslation(closest.getContactPoint());

+          rootNode.attachChild(mark);

+        } else {

+          // No hits? Then remove the red mark.

+          rootNode.detachChild(mark);

+        }

+      }

+    }

+  };

+

+  /** A cube object for target practice */

+  protected Geometry makeCube(String name, float x, float y, float z) {

+    Box box = new Box(new Vector3f(x, y, z), 1, 1, 1);

+    Geometry cube = new Geometry(name, box);

+    Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    mat1.setColor("Color", ColorRGBA.randomColor());

+    cube.setMaterial(mat1);

+    return cube;

+  }

+

+  /** A floor to show that the "shot" can go through several objects. */

+  protected Geometry makeFloor() {

+    Box box = new Box(new Vector3f(0, -4, -5), 15, .2f, 15);

+    Geometry floor = new Geometry("the Floor", box);

+    Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    mat1.setColor("Color", ColorRGBA.Gray);

+    floor.setMaterial(mat1);

+    return floor;

+  }

+

+  /** A red ball that marks the last spot that was "hit" by the "shot". */

+  protected void initMark() {

+    Sphere sphere = new Sphere(30, 30, 0.2f);

+    mark = new Geometry("BOOM!", sphere);

+    Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

+    mark_mat.setColor("Color", ColorRGBA.Red);

+    mark.setMaterial(mark_mat);

+  }

+

+  /** A centred plus sign to help the player aim. */

+  protected void initCrossHairs() {

+    guiNode.detachAllChildren();

+    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

+    BitmapText ch = new BitmapText(guiFont, false);

+    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

+    ch.setText("+"); // crosshairs

+    ch.setLocalTranslation( // center

+      settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,

+      settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

+    guiNode.attachChild(ch);

+  }

+

+  protected Spatial makeCharacter() {

+    // load a character from jme3test-test-data

+    Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");

+    golem.scale(0.5f);

+    golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);

+

+    // We must add a light to make the model visible

+    DirectionalLight sun = new DirectionalLight();

+    sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));

+    golem.addLight(sun);

+    return golem;

+  }

+}

diff --git a/engine/src/test/jme3test/helloworld/HelloTerrain.java b/engine/src/test/jme3test/helloworld/HelloTerrain.java
new file mode 100644
index 0000000..6986b6c
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloTerrain.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+
+public class HelloTerrain extends SimpleApplication {
+
+  private TerrainQuad terrain;
+  Material mat_terrain;
+
+  public static void main(String[] args) {
+    HelloTerrain app = new HelloTerrain();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    flyCam.setMoveSpeed(50);
+
+    /** 1. Create terrain material and load four textures into it. */
+    mat_terrain = new Material(assetManager, 
+            "Common/MatDefs/Terrain/Terrain.j3md");
+
+    /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
+    mat_terrain.setTexture("Alpha", assetManager.loadTexture(
+            "Textures/Terrain/splat/alphamap.png"));
+
+    /** 1.2) Add GRASS texture into the red layer (Tex1). */
+    Texture grass = assetManager.loadTexture(
+            "Textures/Terrain/splat/grass.jpg");
+    grass.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex1", grass);
+    mat_terrain.setFloat("Tex1Scale", 64f);
+
+    /** 1.3) Add DIRT texture into the green layer (Tex2) */
+    Texture dirt = assetManager.loadTexture(
+            "Textures/Terrain/splat/dirt.jpg");
+    dirt.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex2", dirt);
+    mat_terrain.setFloat("Tex2Scale", 32f);
+
+    /** 1.4) Add ROAD texture into the blue layer (Tex3) */
+    Texture rock = assetManager.loadTexture(
+            "Textures/Terrain/splat/road.jpg");
+    rock.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex3", rock);
+    mat_terrain.setFloat("Tex3Scale", 128f);
+
+    /** 2. Create the height map */
+    AbstractHeightMap heightmap = null;
+    Texture heightMapImage = assetManager.loadTexture(
+            "Textures/Terrain/splat/mountains512.png");
+    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
+    heightmap.load();
+
+    /** 3. We have prepared material and heightmap. 
+     * Now we create the actual terrain:
+     * 3.1) Create a TerrainQuad and name it "my terrain".
+     * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
+     * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
+     * 3.4) As LOD step scale we supply Vector3f(1,1,1).
+     * 3.5) We supply the prepared heightmap itself.
+     */
+    int patchSize = 65;
+    terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());
+
+    /** 4. We give the terrain its material, position & scale it, and attach it. */
+    terrain.setMaterial(mat_terrain);
+    terrain.setLocalTranslation(0, -100, 0);
+    terrain.setLocalScale(2f, 1f, 2f);
+    rootNode.attachChild(terrain);
+
+    /** 5. The LOD (level of detail) depends on were the camera is: */
+    TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+    control.setLodCalculator( new DistanceLodCalculator(patchSize, 2.7f) ); // patch size, and a multiplier
+    terrain.addControl(control);
+  }
+}
diff --git a/engine/src/test/jme3test/helloworld/HelloTerrainCollision.java b/engine/src/test/jme3test/helloworld/HelloTerrainCollision.java
new file mode 100644
index 0000000..c1a96a8
--- /dev/null
+++ b/engine/src/test/jme3test/helloworld/HelloTerrainCollision.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2009-2010 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.helloworld;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.bullet.util.CollisionShapeFactory;
+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.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This demo shows a terrain with collision detection, 
+ * that you can walk around in with a first-person perspective.
+ * This code combines HelloCollision and HelloTerrain.
+ */
+public class HelloTerrainCollision extends SimpleApplication
+        implements ActionListener {
+
+  private BulletAppState bulletAppState;
+  private RigidBodyControl landscape;
+  private CharacterControl player;
+  private Vector3f walkDirection = new Vector3f();
+  private boolean left = false, right = false, up = false, down = false;
+  private TerrainQuad terrain;
+  private Material mat_terrain;
+
+  public static void main(String[] args) {
+    HelloTerrainCollision app = new HelloTerrainCollision();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    /** Set up Physics */
+    bulletAppState = new BulletAppState();
+    stateManager.attach(bulletAppState);
+    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+    
+    flyCam.setMoveSpeed(100);
+    setUpKeys();
+
+    /** 1. Create terrain material and load four textures into it. */
+    mat_terrain = new Material(assetManager, 
+            "Common/MatDefs/Terrain/Terrain.j3md");
+
+    /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
+    mat_terrain.setTexture("Alpha", assetManager.loadTexture(
+            "Textures/Terrain/splat/alphamap.png"));
+
+    /** 1.2) Add GRASS texture into the red layer (Tex1). */
+    Texture grass = assetManager.loadTexture(
+            "Textures/Terrain/splat/grass.jpg");
+    grass.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex1", grass);
+    mat_terrain.setFloat("Tex1Scale", 64f);
+
+    /** 1.3) Add DIRT texture into the green layer (Tex2) */
+    Texture dirt = assetManager.loadTexture(
+            "Textures/Terrain/splat/dirt.jpg");
+    dirt.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex2", dirt);
+    mat_terrain.setFloat("Tex2Scale", 32f);
+
+    /** 1.4) Add ROAD texture into the blue layer (Tex3) */
+    Texture rock = assetManager.loadTexture(
+            "Textures/Terrain/splat/road.jpg");
+    rock.setWrap(WrapMode.Repeat);
+    mat_terrain.setTexture("Tex3", rock);
+    mat_terrain.setFloat("Tex3Scale", 128f);
+
+    /** 2. Create the height map */
+    AbstractHeightMap heightmap = null;
+    Texture heightMapImage = assetManager.loadTexture(
+            "Textures/Terrain/splat/mountains512.png");
+    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
+    heightmap.load();
+
+    /** 3. We have prepared material and heightmap. 
+     * Now we create the actual terrain:
+     * 3.1) Create a TerrainQuad and name it "my terrain".
+     * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
+     * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
+     * 3.4) As LOD step scale we supply Vector3f(1,1,1).
+     * 3.5) We supply the prepared heightmap itself.
+     */
+    terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
+
+    /** 4. We give the terrain its material, position & scale it, and attach it. */
+    terrain.setMaterial(mat_terrain);
+    terrain.setLocalTranslation(0, -100, 0);
+    terrain.setLocalScale(2f, 1f, 2f);
+    rootNode.attachChild(terrain);
+
+    /** 5. The LOD (level of detail) depends on were the camera is: */
+    List<Camera> cameras = new ArrayList<Camera>();
+    cameras.add(getCamera());
+    TerrainLodControl control = new TerrainLodControl(terrain, cameras);
+    terrain.addControl(control);
+    
+    /** 6. Add physics: */ 
+    // We set up collision detection for the scene by creating a
+    // compound collision shape and a static RigidBodyControl with mass zero.*/
+    CollisionShape terrainShape =
+            CollisionShapeFactory.createMeshShape((Node) terrain);
+    landscape = new RigidBodyControl(terrainShape, 0);
+    terrain.addControl(landscape);
+    
+    // We set up collision detection for the player by creating
+    // a capsule collision shape and a CharacterControl.
+    // The CharacterControl offers extra settings for
+    // size, stepheight, jumping, falling, and gravity.
+    // We also put the player in its starting position.
+    CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
+    player = new CharacterControl(capsuleShape, 0.05f);
+    player.setJumpSpeed(20);
+    player.setFallSpeed(30);
+    player.setGravity(30);
+    player.setPhysicsLocation(new Vector3f(0, 10, 0));
+
+    // We attach the scene and the player to the rootnode and the physics space,
+    // to make them appear in the game world.
+    bulletAppState.getPhysicsSpace().add(terrain);
+    bulletAppState.getPhysicsSpace().add(player);
+
+  }
+  /** We over-write some navigational key mappings here, so we can
+   * add physics-controlled walking and jumping: */
+  private void setUpKeys() {
+    inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
+    inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
+    inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
+    inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
+    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
+    inputManager.addListener(this, "Left");
+    inputManager.addListener(this, "Right");
+    inputManager.addListener(this, "Up");
+    inputManager.addListener(this, "Down");
+    inputManager.addListener(this, "Jump");
+  }
+
+  /** These are our custom actions triggered by key presses.
+   * We do not walk yet, we just keep track of the direction the user pressed. */
+  public void onAction(String binding, boolean value, float tpf) {
+    if (binding.equals("Left")) {
+      if (value) { left = true; } else { left = false; }
+    } else if (binding.equals("Right")) {
+      if (value) { right = true; } else { right = false; }
+    } else if (binding.equals("Up")) {
+      if (value) { up = true; } else { up = false; }
+    } else if (binding.equals("Down")) {
+      if (value) { down = true; } else { down = false; }
+    } else if (binding.equals("Jump")) {
+      player.jump();
+    }
+  }
+
+  /**
+   * This is the main event loop--walking happens here.
+   * We check in which direction the player is walking by interpreting
+   * the camera direction forward (camDir) and to the side (camLeft).
+   * The setWalkDirection() command is what lets a physics-controlled player walk.
+   * We also make sure here that the camera moves with player.
+   */
+  @Override
+  public void simpleUpdate(float tpf) {
+    Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
+    Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
+    walkDirection.set(0, 0, 0);
+    if (left)  { walkDirection.addLocal(camLeft); }
+    if (right) { walkDirection.addLocal(camLeft.negate()); }
+    if (up)    { walkDirection.addLocal(camDir); }
+    if (down)  { walkDirection.addLocal(camDir.negate()); }
+    player.setWalkDirection(walkDirection);
+    cam.setLocation(player.getPhysicsLocation());
+  }
+}
+
diff --git a/engine/src/test/jme3test/input/TestCameraNode.java b/engine/src/test/jme3test/input/TestCameraNode.java
new file mode 100644
index 0000000..111260a
--- /dev/null
+++ b/engine/src/test/jme3test/input/TestCameraNode.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * * 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.
+ * <p/>
+ * 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.input;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.*;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.CameraNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+import com.jme3.scene.shape.Quad;
+import com.jme3.system.AppSettings;
+
+/**
+ * A 3rd-person camera node follows a target (teapot).  Follow the teapot with
+ * WASD keys, rotate by dragging the mouse.
+ */
+public class TestCameraNode extends SimpleApplication implements AnalogListener, ActionListener {
+
+  private Geometry teaGeom;
+  private Node teaNode;
+  CameraNode camNode;
+  boolean rotate = false;
+  Vector3f direction = new Vector3f();
+
+  public static void main(String[] args) {
+    TestCameraNode app = new TestCameraNode();
+    AppSettings s = new AppSettings(true);
+    s.setFrameRate(100);
+    app.setSettings(s);
+    app.start();
+  }
+
+  public void simpleInitApp() {
+    // load a teapot model 
+    teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+    Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+    teaGeom.setMaterial(mat);
+    //create a node to attach the geometry and the camera node
+    teaNode = new Node("teaNode");
+    teaNode.attachChild(teaGeom);
+    rootNode.attachChild(teaNode);
+    // create a floor
+    mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+    Geometry ground = new Geometry("ground", new Quad(50, 50));
+    ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+    ground.setLocalTranslation(-25, -1, 25);
+    ground.setMaterial(mat);
+    rootNode.attachChild(ground);
+
+    //creating the camera Node
+    camNode = new CameraNode("CamNode", cam);
+    //Setting the direction to Spatial to camera, this means the camera will copy the movements of the Node
+    camNode.setControlDir(ControlDirection.SpatialToCamera);
+    //attaching the camNode to the teaNode
+    teaNode.attachChild(camNode);
+    //setting the local translation of the cam node to move it away from the teanNode a bit
+    camNode.setLocalTranslation(new Vector3f(-10, 0, 0));
+    //setting the camNode to look at the teaNode
+    camNode.lookAt(teaNode.getLocalTranslation(), Vector3f.UNIT_Y);
+
+    //disable the default 1st-person flyCam (don't forget this!!)
+    flyCam.setEnabled(false);
+
+    registerInput();
+  }
+
+  public void registerInput() {
+    inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W));
+    inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S));
+    inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D));
+    inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A));
+    inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+    inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, true));
+    inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false));
+    inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft");
+    inputManager.addListener(this, "rotateRight", "rotateLeft", "toggleRotate");
+  }
+
+  public void onAnalog(String name, float value, float tpf) {
+    //computing the normalized direction of the cam to move the teaNode
+    direction.set(cam.getDirection()).normalizeLocal();
+    if (name.equals("moveForward")) {
+      direction.multLocal(5 * tpf);
+      teaNode.move(direction);
+    }
+    if (name.equals("moveBackward")) {
+      direction.multLocal(-5 * tpf);
+      teaNode.move(direction);
+    }
+    if (name.equals("moveRight")) {
+      direction.crossLocal(Vector3f.UNIT_Y).multLocal(5 * tpf);
+      teaNode.move(direction);
+    }
+    if (name.equals("moveLeft")) {
+      direction.crossLocal(Vector3f.UNIT_Y).multLocal(-5 * tpf);
+      teaNode.move(direction);
+    }
+    if (name.equals("rotateRight") && rotate) {
+      teaNode.rotate(0, 5 * tpf, 0);
+    }
+    if (name.equals("rotateLeft") && rotate) {
+      teaNode.rotate(0, -5 * tpf, 0);
+    }
+
+  }
+
+  public void onAction(String name, boolean keyPressed, float tpf) {
+    //toggling rotation on or off
+    if (name.equals("toggleRotate") && keyPressed) {
+      rotate = true;
+      inputManager.setCursorVisible(false);
+    }
+    if (name.equals("toggleRotate") && !keyPressed) {
+      rotate = false;
+      inputManager.setCursorVisible(true);
+    }
+
+  }
+}
diff --git a/engine/src/test/jme3test/input/TestChaseCamera.java b/engine/src/test/jme3test/input/TestChaseCamera.java
new file mode 100644
index 0000000..04b528f
--- /dev/null
+++ b/engine/src/test/jme3test/input/TestChaseCamera.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 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.input;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.ChaseCamera;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+
+/** A 3rd-person chase camera orbits a target (teapot). 
+ * Follow the teapot with WASD keys, rotate by dragging the mouse. */
+public class TestChaseCamera extends SimpleApplication implements AnalogListener, ActionListener {
+
+  private Geometry teaGeom;
+  private ChaseCamera chaseCam;
+
+  public static void main(String[] args) {
+    TestChaseCamera app = new TestChaseCamera();
+    app.start();
+  }
+
+  public void simpleInitApp() {
+    // Load a teapot model
+    teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+    Material mat_tea = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+    teaGeom.setMaterial(mat_tea);
+    rootNode.attachChild(teaGeom);
+
+    // Load a floor model
+    Material mat_ground = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    mat_ground.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+    Geometry ground = new Geometry("ground", new Quad(50, 50));
+    ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+    ground.setLocalTranslation(-25, -1, 25);
+    ground.setMaterial(mat_ground);
+    rootNode.attachChild(ground);
+    
+    // Disable the default first-person cam!
+    flyCam.setEnabled(false);
+
+    // Enable a chase cam
+    chaseCam = new ChaseCamera(cam, teaGeom, inputManager);
+
+    //Uncomment this to invert the camera's vertical rotation Axis 
+    //chaseCam.setInvertVerticalAxis(true);
+
+    //Uncomment this to invert the camera's horizontal rotation Axis
+    //chaseCam.setInvertHorizontalAxis(true);
+
+    //Comment this to disable smooth camera motion
+    chaseCam.setSmoothMotion(true);
+
+    //Uncomment this to disable trailing of the camera 
+    //WARNING, trailing only works with smooth motion enabled. It is true by default.
+    //chaseCam.setTrailingEnabled(false);
+
+    //Uncomment this to look 3 world units above the target
+    //chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(3));
+
+    //Uncomment this to enable rotation when the middle mouse button is pressed (like Blender)
+    //WARNING : setting this trigger disable the rotation on right and left mouse button click
+    //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));
+
+    //Uncomment this to set mutiple triggers to enable rotation of the cam
+    //Here spade bar and middle mouse button
+    //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE),new KeyTrigger(KeyInput.KEY_SPACE));
+
+    //registering inputs for target's movement
+    registerInput();
+
+  }
+
+  public void registerInput() {
+    inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W));
+    inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S));
+    inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D));
+    inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A));
+    inputManager.addMapping("displayPosition", new KeyTrigger(keyInput.KEY_P));
+    inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft");
+    inputManager.addListener(this, "displayPosition");
+  }
+
+  public void onAnalog(String name, float value, float tpf) {
+    if (name.equals("moveForward")) {
+      teaGeom.move(0, 0, -5 * tpf);
+    }
+    if (name.equals("moveBackward")) {
+      teaGeom.move(0, 0, 5 * tpf);
+    }
+    if (name.equals("moveRight")) {
+      teaGeom.move(5 * tpf, 0, 0);
+    }
+    if (name.equals("moveLeft")) {
+      teaGeom.move(-5 * tpf, 0, 0);
+
+    }
+
+  }
+
+  public void onAction(String name, boolean keyPressed, float tpf) {
+    if (name.equals("displayPosition") && keyPressed) {
+      teaGeom.move(10, 10, 10);
+
+    }
+  }
+
+  @Override
+  public void simpleUpdate(float tpf) {
+    super.simpleUpdate(tpf);
+
+    //  teaGeom.move(new Vector3f(0.001f, 0, 0));
+    // pivot.rotate(0, 0.00001f, 0);
+    //   rootNode.updateGeometricState();
+  }
+//    public void update() {
+//        super.update();
+//// render the viewports
+//        float tpf = timer.getTimePerFrame();
+//        state.getRootNode().rotate(0, 0.000001f, 0);
+//        stateManager.update(tpf);
+//        stateManager.render(renderManager);
+//        renderManager.render(tpf);
+//    }
+}
diff --git a/engine/src/test/jme3test/input/TestControls.java b/engine/src/test/jme3test/input/TestControls.java
new file mode 100644
index 0000000..6967b6b
--- /dev/null
+++ b/engine/src/test/jme3test/input/TestControls.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 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.input;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseAxisTrigger;
+
+public class TestControls extends SimpleApplication {
+    
+    private ActionListener actionListener = new ActionListener(){
+        public void onAction(String name, boolean pressed, float tpf){
+            System.out.println(name + " = " + pressed);
+        }
+    };
+    public AnalogListener analogListener = new AnalogListener() {
+        public void onAnalog(String name, float value, float tpf) {
+            System.out.println(name + " = " + value);
+        }
+    };
+
+    public static void main(String[] args){
+        TestControls app = new TestControls();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // Test multiple inputs per mapping
+        inputManager.addMapping("My Action",
+                new KeyTrigger(KeyInput.KEY_SPACE),
+                new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
+
+        // Test multiple listeners per mapping
+        inputManager.addListener(actionListener, "My Action");
+        inputManager.addListener(analogListener, "My Action");
+    }
+
+}
diff --git a/engine/src/test/jme3test/input/TestJoystick.java b/engine/src/test/jme3test/input/TestJoystick.java
new file mode 100644
index 0000000..2ea924e
--- /dev/null
+++ b/engine/src/test/jme3test/input/TestJoystick.java
@@ -0,0 +1,51 @@
+package jme3test.input;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.JoyInput;
+import com.jme3.input.Joystick;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.JoyAxisTrigger;
+import com.jme3.system.AppSettings;
+
+public class TestJoystick extends SimpleApplication implements AnalogListener {
+
+    public static void main(String[] args){
+        TestJoystick app = new TestJoystick();
+        AppSettings settings = new AppSettings(true);
+        settings.setUseJoysticks(true);
+        app.setSettings(settings);
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Joystick[] joysticks = inputManager.getJoysticks();
+        if (joysticks == null)
+            throw new IllegalStateException("Cannot find any joysticks!");
+            
+        for (Joystick joy : joysticks){
+            System.out.println(joy.toString());
+        }
+
+        inputManager.addMapping("DPAD Left", new JoyAxisTrigger(0, JoyInput.AXIS_POV_X, true));
+        inputManager.addMapping("DPAD Right", new JoyAxisTrigger(0, JoyInput.AXIS_POV_X, false));
+        inputManager.addMapping("DPAD Down", new JoyAxisTrigger(0, JoyInput.AXIS_POV_Y, true));
+        inputManager.addMapping("DPAD Up", new JoyAxisTrigger(0, JoyInput.AXIS_POV_Y, false));
+        inputManager.addListener(this, "DPAD Left", "DPAD Right", "DPAD Down", "DPAD Up");
+
+        inputManager.addMapping("Joy Left", new JoyAxisTrigger(0, 0, true));
+        inputManager.addMapping("Joy Right", new JoyAxisTrigger(0, 0, false));
+        inputManager.addMapping("Joy Down", new JoyAxisTrigger(0, 1, true));
+        inputManager.addMapping("Joy Up", new JoyAxisTrigger(0, 1, false));
+        inputManager.addListener(this, "Joy Left", "Joy Right", "Joy Down", "Joy Up");
+    }
+
+    public void onAnalog(String name, float isPressed, float tpf) {
+        System.out.println(name + " = " + isPressed);
+    }
+
+    public void onAction(String name, boolean isPressed, float tpf) {
+        System.out.println(name + " = " + isPressed);
+    }
+
+}
diff --git a/engine/src/test/jme3test/input/combomoves/ComboMove.java b/engine/src/test/jme3test/input/combomoves/ComboMove.java
new file mode 100644
index 0000000..f0763f7
--- /dev/null
+++ b/engine/src/test/jme3test/input/combomoves/ComboMove.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2010 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.input.combomoves;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ComboMove {
+
+    public static class ComboMoveState {
+        
+        private String[] pressedMappings;
+        private String[] unpressedMappings;
+        private float timeElapsed;
+
+        public ComboMoveState(String[] pressedMappings, String[] unpressedMappings, float timeElapsed) {
+            this.pressedMappings = pressedMappings;
+            this.unpressedMappings = unpressedMappings;
+            this.timeElapsed = timeElapsed;
+        }
+
+        public String[] getUnpressedMappings() {
+            return unpressedMappings;
+        }
+
+        public String[] getPressedMappings() {
+            return pressedMappings;
+        }
+
+        public float getTimeElapsed() {
+            return timeElapsed;
+        }
+        
+    }
+
+    private String moveName;
+    private List<ComboMoveState> states = new ArrayList<ComboMoveState>();
+    private boolean useFinalState = true;
+    private float priority = 1;
+    private float castTime = 0.8f;
+
+    private transient String[] pressed, unpressed;
+    private transient float timeElapsed;
+
+    public ComboMove(String moveName){
+        this.moveName = moveName;
+    }
+
+    public float getPriority() {
+        return priority;
+    }
+
+    public void setPriority(float priority) {
+        this.priority = priority;
+    }
+
+    public float getCastTime() {
+        return castTime;
+    }
+
+    public void setCastTime(float castTime) {
+        this.castTime = castTime;
+    }
+    
+    public boolean useFinalState() {
+        return useFinalState;
+    }
+
+    public void setUseFinalState(boolean useFinalState) {
+        this.useFinalState = useFinalState;
+    }
+    
+    public ComboMove press(String ... pressedMappings){
+        this.pressed = pressedMappings;
+        return this;
+    }
+
+    public ComboMove notPress(String ... unpressedMappings){
+        this.unpressed = unpressedMappings;
+        return this;
+    }
+
+    public ComboMove timeElapsed(float time){
+        this.timeElapsed = time;
+        return this;
+    }
+
+    public void done(){
+        if (pressed == null)
+            pressed = new String[0];
+        
+        if (unpressed == null)
+            unpressed = new String[0];
+
+        states.add(new ComboMoveState(pressed, unpressed, timeElapsed));
+        pressed = null;
+        unpressed = null;
+        timeElapsed = -1;
+    }
+
+    public ComboMoveState getState(int num){
+        return states.get(num);
+    }
+
+    public int getNumStates(){
+        return states.size();
+    }
+
+    public String getMoveName() {
+        return moveName;
+    }
+    
+}
diff --git a/engine/src/test/jme3test/input/combomoves/ComboMoveExecution.java b/engine/src/test/jme3test/input/combomoves/ComboMoveExecution.java
new file mode 100644
index 0000000..3a623bb
--- /dev/null
+++ b/engine/src/test/jme3test/input/combomoves/ComboMoveExecution.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 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.input.combomoves;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import jme3test.input.combomoves.ComboMove.ComboMoveState;
+
+public class ComboMoveExecution {
+
+    private static final float TIME_LIMIT = 0.3f;
+
+    private ComboMove moveDef;
+    private int state;
+    private float moveTime;
+    private boolean finalState = false;
+
+    private String debugString = ""; // for debug only
+
+    public ComboMoveExecution(ComboMove move){
+        moveDef = move;
+    }
+
+    private boolean isStateSatisfied(HashSet<String> pressedMappings, float time,
+                                     ComboMoveState state){
+
+        if (state.getTimeElapsed() != -1f){
+            // check if an appropriate amount of time has passed
+            // if the state requires it
+            if (moveTime + state.getTimeElapsed() >= time){
+                return false;
+            }
+        }
+        for (String mapping : state.getPressedMappings()){
+            if (!pressedMappings.contains(mapping))
+                return false;
+        }
+        for (String mapping : state.getUnpressedMappings()){
+            if (pressedMappings.contains(mapping))
+                return false;
+        }
+        return true;
+    }
+
+    public String getDebugString(){
+        return debugString;
+    }
+
+    public void updateExpiration(float time){
+        if (!finalState && moveTime > 0 && moveTime + TIME_LIMIT < time){
+            state    = 0;
+            moveTime = 0;
+            finalState = false;
+
+            // reset debug string.
+            debugString = "";
+        }
+    }
+
+    /**
+     * Check if move needs to be executed. 
+     * @param pressedMappings Which mappings are currently pressed
+     * @param time Current time since start of app
+     * @return True if move needs to be executed.
+     */
+    public boolean updateState(HashSet<String> pressedMappings, float time){
+        ComboMoveState currentState = moveDef.getState(state);
+        if (isStateSatisfied(pressedMappings, time, currentState)){
+            state ++;
+            moveTime = time;
+
+            if (state >= moveDef.getNumStates()){
+                finalState = false;
+                state = 0;
+                
+                moveTime = time+0.5f; // this is for the reset of the debug string only.
+                debugString += ", -CASTING " + moveDef.getMoveName().toUpperCase() + "-";
+                return true;
+            } 
+            
+            // the following for debug only.
+            if (currentState.getPressedMappings().length > 0){
+                if (!debugString.equals(""))
+                    debugString += ", ";
+
+                debugString += Arrays.toString(currentState.getPressedMappings()).replace(", ", "+");
+            }
+
+            if (moveDef.useFinalState() && state == moveDef.getNumStates() - 1){
+                finalState = true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/engine/src/test/jme3test/input/combomoves/TestComboMoves.java b/engine/src/test/jme3test/input/combomoves/TestComboMoves.java
new file mode 100644
index 0000000..cd922f4
--- /dev/null
+++ b/engine/src/test/jme3test/input/combomoves/TestComboMoves.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2009-2010 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.input.combomoves;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Spatial.CullHint;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class TestComboMoves extends SimpleApplication implements ActionListener {
+
+    private HashSet<String> pressedMappings = new HashSet<String>();
+
+    private ComboMove fireball;
+    private ComboMoveExecution fireballExec;
+    private BitmapText fireballText;
+
+    private ComboMove shuriken;
+    private ComboMoveExecution shurikenExec;
+    private BitmapText shurikenText;
+
+    private ComboMove jab;
+    private ComboMoveExecution jabExec;
+    private BitmapText jabText;
+
+    private ComboMove punch;
+    private ComboMoveExecution punchExec;
+    private BitmapText punchText;
+
+    private ComboMove currentMove = null;
+    private float currentMoveCastTime = 0;
+    private float time = 0;
+
+    public static void main(String[] args){
+        TestComboMoves app = new TestComboMoves();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setDisplayFps(false);
+        setDisplayStatView(false);
+
+        // Create debug text
+        BitmapText helpText = new BitmapText(guiFont);
+        helpText.setLocalTranslation(0, settings.getHeight(), 0);
+        helpText.setText("Moves:\n" +
+                         "Fireball: Down, Down+Right, Right\n"+
+                         "Shuriken: Left, Down, Attack1(Z)\n"+
+                         "Jab: Attack1(Z)\n"+
+                         "Punch: Attack1(Z), Attack1(Z)\n");
+        guiNode.attachChild(helpText);
+
+        fireballText = new BitmapText(guiFont);
+        fireballText.setColor(ColorRGBA.Orange);
+        fireballText.setLocalTranslation(0, fireballText.getLineHeight(), 0);
+        guiNode.attachChild(fireballText);
+
+        shurikenText = new BitmapText(guiFont);
+        shurikenText.setColor(ColorRGBA.Cyan);
+        shurikenText.setLocalTranslation(0, shurikenText.getLineHeight()*2f, 0);
+        guiNode.attachChild(shurikenText);
+
+        jabText = new BitmapText(guiFont);
+        jabText.setColor(ColorRGBA.Red);
+        jabText.setLocalTranslation(0, jabText.getLineHeight()*3f, 0);
+        guiNode.attachChild(jabText);
+
+        punchText = new BitmapText(guiFont);
+        punchText.setColor(ColorRGBA.Green);
+        punchText.setLocalTranslation(0, punchText.getLineHeight()*4f, 0);
+        guiNode.attachChild(punchText);
+
+        inputManager.addMapping("Left",    new KeyTrigger(KeyInput.KEY_LEFT));
+        inputManager.addMapping("Right",   new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addMapping("Up",      new KeyTrigger(KeyInput.KEY_UP));
+        inputManager.addMapping("Down",    new KeyTrigger(KeyInput.KEY_DOWN));
+        inputManager.addMapping("Attack1", new KeyTrigger(KeyInput.KEY_Z));
+        inputManager.addListener(this, "Left", "Right", "Up", "Down", "Attack1");
+
+        fireball = new ComboMove("Fireball");
+        fireball.press("Down").notPress("Right").done();
+        fireball.press("Right", "Down").done();
+        fireball.press("Right").notPress("Down").done();
+        fireball.notPress("Right", "Down").done();
+        fireball.setUseFinalState(false); // no waiting on final state
+
+        shuriken = new ComboMove("Shuriken");
+        shuriken.press("Left").notPress("Down", "Attack1").done();
+        shuriken.press("Down").notPress("Attack1").timeElapsed(0.11f).done();
+        shuriken.press("Attack1").notPress("Left").timeElapsed(0.11f).done();
+        shuriken.notPress("Left", "Down", "Attack1").done();
+
+        jab = new ComboMove("Jab");
+        jab.setPriority(0.5f); // make jab less important than other moves
+        jab.press("Attack1").done();
+
+        punch = new ComboMove("Punch");
+        punch.press("Attack1").done();
+        punch.notPress("Attack1").done();
+        punch.press("Attack1").done();
+
+        fireballExec = new ComboMoveExecution(fireball);
+        shurikenExec = new ComboMoveExecution(shuriken);
+        jabExec = new ComboMoveExecution(jab);
+        punchExec = new ComboMoveExecution(punch);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        time += tpf;
+
+        // check every frame if any executions are expired
+        shurikenExec.updateExpiration(time);
+        shurikenText.setText("Shuriken Exec: " + shurikenExec.getDebugString());
+
+        fireballExec.updateExpiration(time);
+        fireballText.setText("Fireball Exec: " + fireballExec.getDebugString());
+
+        jabExec.updateExpiration(time);
+        jabText.setText("Jab Exec: " + jabExec.getDebugString());
+
+        punchExec.updateExpiration(time);
+        punchText.setText("Punch Exec: " + punchExec.getDebugString());
+
+        if (currentMove != null){
+            currentMoveCastTime -= tpf;
+            if (currentMoveCastTime <= 0){
+                System.out.println("DONE CASTING " + currentMove.getMoveName());
+                currentMoveCastTime = 0;
+                currentMove = null;
+            }
+        }
+    }
+
+    public void onAction(String name, boolean isPressed, float tpf) {
+        if (isPressed){
+            pressedMappings.add(name);
+        }else{
+            pressedMappings.remove(name);
+        }
+
+        // the pressed mappings was changed. update combo executions
+        List<ComboMove> invokedMoves = new ArrayList<ComboMove>();
+        if (shurikenExec.updateState(pressedMappings, time)){
+            invokedMoves.add(shuriken);
+        }
+
+        if (fireballExec.updateState(pressedMappings, time)){
+            invokedMoves.add(fireball);
+        }
+
+        if (jabExec.updateState(pressedMappings, time)){
+            invokedMoves.add(jab);
+        }
+
+        if (punchExec.updateState(pressedMappings, time)){
+            invokedMoves.add(punch);
+        }
+
+        if (invokedMoves.size() > 0){
+            // choose move with highest priority
+            float priority = 0;
+            ComboMove toExec = null;
+            for (ComboMove move : invokedMoves){
+                if (move.getPriority() > priority){
+                    priority = move.getPriority();
+                    toExec = move;
+                }
+            }
+            if (currentMove != null && currentMove.getPriority() > toExec.getPriority()){
+                return;
+            }
+
+            currentMove = toExec;
+            currentMoveCastTime = currentMove.getCastTime();
+            //System.out.println("CASTING " + currentMove.getMoveName());
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestEnvironmentMapping.java b/engine/src/test/jme3test/light/TestEnvironmentMapping.java
new file mode 100644
index 0000000..67b03e0
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestEnvironmentMapping.java
@@ -0,0 +1,61 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.input.ChaseCamera;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Texture;
+import com.jme3.util.SkyFactory;
+
+/**
+ * test
+ * @author nehon
+ */
+public class TestEnvironmentMapping extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestEnvironmentMapping app = new TestEnvironmentMapping();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        final Node buggy = (Node) assetManager.loadModel("Models/Buggy/Buggy.j3o");
+
+        TextureKey key = new TextureKey("Textures/Sky/Bright/BrightSky.dds", true);
+        key.setGenerateMips(true);
+        key.setAsCube(true);
+        final Texture tex = assetManager.loadTexture(key);
+
+        for (Spatial geom : buggy.getChildren()) {
+            if (geom instanceof Geometry) {
+                Material m = ((Geometry) geom).getMaterial();
+                m.setTexture("EnvMap", tex);
+                m.setVector3("FresnelParams", new Vector3f(0.05f, 0.18f, 0.11f));
+            }
+        }
+
+        flyCam.setEnabled(false);
+
+        ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
+        chaseCam.setLookAtOffset(new Vector3f(0,0.5f,-1.0f));
+        buggy.addControl(chaseCam);
+        rootNode.attachChild(buggy);
+        rootNode.attachChild(SkyFactory.createSky(assetManager, tex, false));
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        BloomFilter bf = new BloomFilter(BloomFilter.GlowMode.Objects);
+        bf.setBloomIntensity(2.3f);
+        bf.setExposurePower(0.6f);
+        
+        fpp.addFilter(bf);
+        
+        viewPort.addProcessor(fpp);
+    }
+}
diff --git a/engine/src/test/jme3test/light/TestLightNode.java b/engine/src/test/jme3test/light/TestLightNode.java
new file mode 100644
index 0000000..955f24f
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestLightNode.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.LightNode;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Torus;
+
+public class TestLightNode extends SimpleApplication {
+
+    float angle;
+    Node movingNode;
+
+    public static void main(String[] args){
+        TestLightNode app = new TestLightNode();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Torus torus = new Torus(10, 6, 1, 3);
+//        Torus torus = new Torus(50, 30, 1, 3);
+        Geometry g = new Geometry("Torus Geom", torus);
+        g.rotate(-FastMath.HALF_PI, 0, 0);
+        g.center();
+//        g.move(0, 1, 0);
+        
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 32f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient",  ColorRGBA.Black);
+        mat.setColor("Diffuse",  ColorRGBA.White);
+        mat.setColor("Specular", ColorRGBA.White);
+//        mat.setBoolean("VertexLighting", true);
+//        mat.setBoolean("LowQuality", true);
+        g.setMaterial(mat);
+
+        rootNode.attachChild(g);
+
+        Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        
+        movingNode=new Node("lightParentNode");
+        movingNode.attachChild(lightMdl);  
+        rootNode.attachChild(movingNode);
+
+        PointLight pl = new PointLight();
+        pl.setColor(ColorRGBA.Green);
+        pl.setRadius(4f);
+        rootNode.addLight(pl);
+        
+        LightNode lightNode=new LightNode("pointLight", pl);
+        movingNode.attachChild(lightNode);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.Red);
+        dl.setDirection(new Vector3f(0, 1, 0));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+//        cam.setLocation(new Vector3f(5.0347548f, 6.6481347f, 3.74853f));
+//        cam.setRotation(new Quaternion(-0.19183293f, 0.80776674f, -0.37974006f, -0.40805697f));
+
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+
+        movingNode.setLocalTranslation(new Vector3f(FastMath.cos(angle) * 3f, 2, FastMath.sin(angle) * 3f));
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestLightRadius.java b/engine/src/test/jme3test/light/TestLightRadius.java
new file mode 100644
index 0000000..680639f
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestLightRadius.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.scene.shape.Torus;
+
+public class TestLightRadius extends SimpleApplication {
+
+    float pos, vel=1;
+    PointLight pl;
+    Geometry lightMdl;
+
+    public static void main(String[] args){
+        TestLightRadius app = new TestLightRadius();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Torus torus = new Torus(10, 6, 1, 3);
+//        Torus torus = new Torus(50, 30, 1, 3);
+        Geometry g = new Geometry("Torus Geom", torus);
+        g.rotate(-FastMath.HALF_PI, 0, 0);
+        g.center();
+//        g.move(0, 1, 0);
+        
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 32f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient",  ColorRGBA.Black);
+        mat.setColor("Diffuse",  ColorRGBA.White);
+        mat.setColor("Specular", ColorRGBA.White);
+//        mat.setBoolean("VertexLighting", true);
+//        mat.setBoolean("LowQuality", true);
+        g.setMaterial(mat);
+
+        rootNode.attachChild(g);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.Green);
+        pl.setRadius(4f);
+        rootNode.addLight(pl);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.Red);
+        dl.setDirection(new Vector3f(0, 1, 0));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+//        cam.setLocation(new Vector3f(5.0347548f, 6.6481347f, 3.74853f));
+//        cam.setRotation(new Quaternion(-0.19183293f, 0.80776674f, -0.37974006f, -0.40805697f));
+
+        pos += tpf * vel * 5f;
+        if (pos > 15){
+            vel *= -1;
+        }else if (pos < -15){
+            vel *= -1;
+        }
+        
+        pl.setPosition(new Vector3f(pos, 2, 0));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestManyLights.java b/engine/src/test/jme3test/light/TestManyLights.java
new file mode 100644
index 0000000..edd3af5
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestManyLights.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.scene.Node;
+
+public class TestManyLights extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestManyLights app = new TestManyLights();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(10);
+
+        Node scene = (Node) assetManager.loadModel("Scenes/ManyLights/Main.scene");
+        rootNode.attachChild(scene);
+//        guiNode.setCullHint(CullHint.Always);
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/light/TestPssmShadow.java b/engine/src/test/jme3test/light/TestPssmShadow.java
new file mode 100644
index 0000000..67addf0
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestPssmShadow.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2010 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.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.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.shadow.PssmShadowRenderer.CompareMode;
+import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+import java.util.Random;
+
+public class TestPssmShadow extends SimpleApplication implements ActionListener {
+
+    private Spatial teapot;
+    private boolean renderShadows = true;
+    private boolean hardwareShadows = false;
+    private PssmShadowRenderer pssmRenderer;
+
+    public static void main(String[] args){
+        TestPssmShadow app = new TestPssmShadow();
+        app.start();
+    }
+
+    public void loadScene(){
+        Material mat = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+        Material matSoil = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
+        matSoil.setColor("Color", ColorRGBA.Cyan);
+
+        teapot = new Geometry("sphere", new Sphere(30, 30, 2));
+//        teapot = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
+//        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalTranslation(0,0,10);
+
+        teapot.setMaterial(mat);
+        teapot.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(teapot);
+
+        long seed = 1294719330150L; //System.currentTimeMillis();
+        Random random = new Random(seed);
+        System.out.println(seed);
+
+        for (int i = 0; i < 30; i++) {
+            Spatial t = teapot.clone(false);
+            rootNode.attachChild(t);
+            teapot.setLocalTranslation((float) random.nextFloat() * 3, (float) random.nextFloat() * 3, (i + 2));
+        }
+
+        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+        soil.setMaterial(matSoil);
+        soil.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(soil);
+
+        for (int i = 0; i < 30; i++) {
+            Spatial t = teapot.clone(false);
+            t.setLocalScale(10.0f);
+            rootNode.attachChild(t);
+            teapot.setLocalTranslation((float) random.nextFloat() * 300, (float) random.nextFloat() * 30, 30 * (i + 2));
+        }
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(41.59757f, 34.38738f, 11.528807f));
+        cam.setRotation(new Quaternion(0.2905285f, 0.3816416f, -0.12772122f, 0.86811876f));
+        flyCam.setMoveSpeed(100);
+
+        loadScene();
+           
+        pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 3);
+        pssmRenderer.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        pssmRenderer.setLambda(0.55f);
+        pssmRenderer.setShadowIntensity(0.6f);
+        pssmRenderer.setCompareMode(CompareMode.Software);
+        pssmRenderer.setFilterMode(FilterMode.Bilinear);
+        pssmRenderer.displayDebug();
+        viewPort.addProcessor(pssmRenderer);
+        initInputs();
+    }
+
+      private void initInputs() {
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("ShadowUp", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addMapping("ShadowDown", new KeyTrigger(KeyInput.KEY_G));
+        inputManager.addMapping("ThicknessUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("ThicknessDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("lambdaUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("toggleHW", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addListener(this, "lambdaUp", "lambdaDown", "toggleHW", "toggle", "ShadowUp","ShadowDown","ThicknessUp","ThicknessDown");
+    }
+
+    public void onAction(String name, boolean keyPressed, float tpf) {
+        if (name.equals("toggle") && keyPressed) {
+            if (renderShadows) {
+                renderShadows = false;
+                viewPort.removeProcessor(pssmRenderer);
+            } else {
+                renderShadows = true;
+                viewPort.addProcessor(pssmRenderer);
+            }
+        } else if (name.equals("toggleHW") && keyPressed) {
+            hardwareShadows = !hardwareShadows;
+            pssmRenderer.setCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software);
+            System.out.println("HW Shadows: " + hardwareShadows);
+        }
+
+        if (name.equals("lambdaUp") && keyPressed) {
+            pssmRenderer.setLambda(pssmRenderer.getLambda() + 0.01f);
+            System.out.println("Lambda : " + pssmRenderer.getLambda());
+        } else if (name.equals("lambdaDown") && keyPressed) {
+            pssmRenderer.setLambda(pssmRenderer.getLambda() - 0.01f);
+            System.out.println("Lambda : " + pssmRenderer.getLambda());
+        }
+
+        if (name.equals("ShadowUp") && keyPressed) {
+            pssmRenderer.setShadowIntensity(pssmRenderer.getShadowIntensity() + 0.1f);
+            System.out.println("Shadow intensity : " + pssmRenderer.getShadowIntensity());
+        }
+        if (name.equals("ShadowDown") && keyPressed) {
+            pssmRenderer.setShadowIntensity(pssmRenderer.getShadowIntensity() - 0.1f);
+            System.out.println("Shadow intensity : " + pssmRenderer.getShadowIntensity());
+        }
+        if (name.equals("ThicknessUp") && keyPressed) {
+            pssmRenderer.setEdgesThickness(pssmRenderer.getEdgesThickness() + 1);
+            System.out.println("Shadow thickness : " + pssmRenderer.getEdgesThickness());
+        }
+        if (name.equals("ThicknessDown") && keyPressed) {
+            pssmRenderer.setEdgesThickness(pssmRenderer.getEdgesThickness() - 1);
+            System.out.println("Shadow thickness : " + pssmRenderer.getEdgesThickness());
+        }
+    }
+
+
+}
diff --git a/engine/src/test/jme3test/light/TestShadow.java b/engine/src/test/jme3test/light/TestShadow.java
new file mode 100644
index 0000000..a8d168c
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestShadow.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.shadow.BasicShadowRenderer;
+import com.jme3.shadow.ShadowUtil;
+
+public class TestShadow extends SimpleApplication {
+
+    float angle;
+    Spatial lightMdl;
+    Spatial teapot;
+    Geometry frustumMdl;
+    WireFrustum frustum;
+
+    private BasicShadowRenderer bsr;
+    private Vector3f[] points;
+
+    {
+        points = new Vector3f[8];
+        for (int i = 0; i < points.length; i++) points[i] = new Vector3f();
+    }
+
+    public static void main(String[] args){
+        TestShadow app = new TestShadow();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(0.7804813f, 1.7502685f, -2.1556435f));
+        cam.setRotation(new Quaternion(0.1961598f, -0.7213164f, 0.2266092f, 0.6243975f));
+        cam.setFrustumFar(10);
+
+        Material mat = assetManager.loadMaterial("Common/Materials/WhiteColor.j3m");
+        rootNode.setShadowMode(ShadowMode.Off);
+        Box floor = new Box(Vector3f.ZERO, 3, 0.1f, 3);
+        Geometry floorGeom = new Geometry("Floor", floor);
+        floorGeom.setMaterial(mat);
+        floorGeom.setLocalTranslation(0,-0.2f,0);
+        floorGeom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(floorGeom);
+
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalScale(2f);
+        teapot.setMaterial(mat);
+        teapot.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(teapot);
+//        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+//        lightMdl.setMaterial(mat);
+//        // disable shadowing for light representation
+//        lightMdl.setShadowMode(ShadowMode.Off);
+//        rootNode.attachChild(lightMdl);
+
+        bsr = new BasicShadowRenderer(assetManager, 512);
+        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        viewPort.addProcessor(bsr);
+
+        frustum = new WireFrustum(bsr.getPoints());
+        frustumMdl = new Geometry("f", frustum);
+        frustumMdl.setCullHint(Spatial.CullHint.Never);
+        frustumMdl.setShadowMode(ShadowMode.Off);
+        frustumMdl.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        frustumMdl.getMaterial().getAdditionalRenderState().setWireframe(true);
+        frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red);
+        rootNode.attachChild(frustumMdl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Camera shadowCam = bsr.getShadowCamera();
+        ShadowUtil.updateFrustumPoints2(shadowCam, points);
+
+        frustum.update(points);
+        // rotate teapot around Y axis
+        teapot.rotate(0, tpf * 0.25f, 0);
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestSimpleLighting.java b/engine/src/test/jme3test/light/TestSimpleLighting.java
new file mode 100644
index 0000000..5c94f81
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestSimpleLighting.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestSimpleLighting extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Geometry lightMdl;
+
+    public static void main(String[] args){
+        TestSimpleLighting app = new TestSimpleLighting();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+        TangentBinormalGenerator.generate(teapot.getMesh(), true);
+
+        teapot.setLocalScale(2f);
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+//        mat.selectTechnique("GBuf");
+        mat.setFloat("Shininess", 12);
+        mat.setBoolean("UseMaterialColors", true);
+
+//        mat.setTexture("ColorRamp", assetManager.loadTexture("Textures/ColorRamp/cloudy.png"));
+//
+//        mat.setBoolean("VTangent", true);
+//        mat.setBoolean("Minnaert", true);
+//        mat.setBoolean("WardIso", true);
+//        mat.setBoolean("VertexLighting", true);
+//        mat.setBoolean("LowQuality", true);
+//        mat.setBoolean("HighQuality", true);
+
+        mat.setColor("Ambient",  ColorRGBA.Black);
+        mat.setColor("Diffuse",  ColorRGBA.Gray);
+        mat.setColor("Specular", ColorRGBA.Gray);
+        
+        teapot.setMaterial(mat);
+        rootNode.attachChild(teapot);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        lightMdl.getMesh().setStatic();
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setRadius(4f);
+        rootNode.addLight(pl);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        dl.setColor(ColorRGBA.Green);
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+//        cam.setLocation(new Vector3f(2.0632997f, 1.9493936f, 2.6885238f));
+//        cam.setRotation(new Quaternion(-0.053555284f, 0.9407851f, -0.17754152f, -0.28378546f));
+
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 0.5f, FastMath.sin(angle) * 2f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestSpotLight.java b/engine/src/test/jme3test/light/TestSpotLight.java
new file mode 100644
index 0000000..91e6987
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestSpotLight.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestSpotLight extends SimpleApplication {
+
+    private Vector3f lightTarget = new Vector3f(12, 3.5f, 30);
+
+    public static void main(String[] args){
+        TestSpotLight app = new TestSpotLight();
+        app.start();
+    }
+
+ SpotLight spot;
+    Geometry lightMdl;
+    public void setupLighting(){
+      AmbientLight al=new AmbientLight();
+      al.setColor(ColorRGBA.White.mult(0.8f));
+      rootNode.addLight(al);
+        
+      spot=new SpotLight();
+      
+      spot.setSpotRange(1000);
+      spot.setSpotInnerAngle(5*FastMath.DEG_TO_RAD);
+      spot.setSpotOuterAngle(10*FastMath.DEG_TO_RAD);
+      spot.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f));
+      spot.setDirection(lightTarget.subtract(spot.getPosition()));     
+      spot.setColor(ColorRGBA.White.mult(2));
+      rootNode.addLight(spot);
+      
+      
+//        PointLight pl=new PointLight();
+//      pl.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f));
+//      pl.setRadius(1000);     
+//      pl.setColor(ColorRGBA.White.mult(2));
+//      rootNode.addLight(pl);
+       lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+      lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+      lightMdl.setLocalTranslation(new Vector3f(77.70334f, 34.013165f, 27.1017f));
+      lightMdl.setLocalScale(5);
+      rootNode.attachChild(lightMdl);
+        
+//        DirectionalLight dl = new DirectionalLight();
+//        dl.setDirection(lightTarget.subtract(new Vector3f(77.70334f, 34.013165f, 27.1017f)));
+//        dl.setColor(ColorRGBA.White.mult(2));
+//        rootNode.addLight(dl);
+      
+      
+    }
+
+    public void setupFloor(){
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
+       // mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.setFloat("Shininess",3);
+      //  mat.setBoolean("VertexLighting", true);
+        
+        
+        Box floor = new Box(Vector3f.ZERO, 50, 1f, 50);
+        TangentBinormalGenerator.generate(floor);
+        floor.scaleTextureCoordinates(new Vector2f(5, 5));
+        Geometry floorGeom = new Geometry("Floor", floor);
+        floorGeom.setMaterial(mat);
+        floorGeom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(floorGeom);
+    }
+
+
+
+    public void setupSignpost(){
+        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
+      //   mat.setBoolean("VertexLighting", true);
+        signpost.setMaterial(mat);
+        signpost.rotate(0, FastMath.HALF_PI, 0);
+        signpost.setLocalTranslation(12, 3.5f, 30);
+        signpost.setLocalScale(4);
+        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        TangentBinormalGenerator.generate(signpost);
+        rootNode.attachChild(signpost);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f));
+        cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f));
+        flyCam.setMoveSpeed(30);
+     
+        setupLighting();
+        setupFloor();
+        setupSignpost();
+
+        
+    }
+    
+    float angle;    
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        super.simpleUpdate(tpf);
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+
+        spot.setPosition(new Vector3f(FastMath.cos(angle) * 30f, 34.013165f, FastMath.sin(angle) * 30f));
+        lightMdl.setLocalTranslation(spot.getPosition());
+        spot.setDirection(lightTarget.subtract(spot.getPosition()));     
+    }
+    
+    
+
+}
diff --git a/engine/src/test/jme3test/light/TestSpotLightTerrain.java b/engine/src/test/jme3test/light/TestSpotLightTerrain.java
new file mode 100644
index 0000000..7e6139f
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestSpotLightTerrain.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.font.BitmapText;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import jme3tools.converters.ImageToAwt;
+
+/**
+ * Uses the terrain's lighting texture with normal maps and lights.
+ *
+ * @author bowens
+ */
+public class TestSpotLightTerrain extends SimpleApplication {
+
+    private TerrainQuad terrain;
+    Material matTerrain;
+    Material matWire;
+    boolean wireframe = false;
+    boolean triPlanar = false;
+    boolean wardiso = false;
+    boolean minnaert = false;
+    protected BitmapText hintText;
+    PointLight pl;
+    Geometry lightMdl;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    SpotLight sl;
+
+    public static void main(String[] args) {
+        TestSpotLightTerrain app = new TestSpotLightTerrain();
+        app.start();
+    }
+
+  
+    @Override
+    public void simpleInitApp() {  
+        makeTerrain();
+        flyCam.setMoveSpeed(50);
+
+        sl = new SpotLight();
+        sl.setSpotRange(100);
+        sl.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD);
+        sl.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD);
+        sl.setDirection(new Vector3f(-0.39820394f, -0.73094344f, 0.55421597f));
+        sl.setPosition(new Vector3f(-64.61567f, -87.615425f, -202.41328f));
+        rootNode.addLight(sl);
+
+        AmbientLight ambLight = new AmbientLight();
+        ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f));
+        rootNode.addLight(ambLight);
+
+        cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f));
+        cam.setRotation(new Quaternion(-0.04562731f, 0.89917684f, -0.09668826f, -0.4243236f));
+        sl.setDirection(cam.getDirection());
+        sl.setPosition(cam.getLocation());
+
+    }
+    
+      @Override
+    public void simpleUpdate(float tpf) {
+        super.simpleUpdate(tpf);
+        sl.setDirection(cam.getDirection());
+        sl.setPosition(cam.getLocation());
+
+    }
+
+    private void makeTerrain() {
+        // TERRAIN TEXTURE material
+        matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matTerrain.setBoolean("useTriPlanarMapping", false);
+        matTerrain.setBoolean("WardIso", true);
+
+        // ALPHA map (for splat textures)
+        matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png"));
+        matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png"));
+
+        // HEIGHTMAP image (for the terrain heightmap)
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap", grass);
+        matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_1", dirt);
+        matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_2", rock);
+        matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
+
+        // BRICK texture
+        Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg");
+        brick.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_3", brick);
+        matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
+
+        // RIVER ROCK texture
+        Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
+        riverRock.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_4", riverRock);
+        matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
+
+
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("NormalMap", normalMap0);
+        matTerrain.setTexture("NormalMap_1", normalMap2);
+        matTerrain.setTexture("NormalMap_2", normalMap2);
+        matTerrain.setTexture("NormalMap_4", normalMap2);
+
+        // WIREFRAME material
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+
+        createSky();
+
+        // CREATE HEIGHTMAP
+        AbstractHeightMap heightmap = null;
+        try {
+            //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);
+
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
+            heightmap.load();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
+        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) );
+        terrain.addControl(control);
+        terrain.setMaterial(matTerrain);
+        terrain.setModelBound(new BoundingBox());
+        terrain.updateModelBound();
+        terrain.setLocalTranslation(0, -100, 0);
+        terrain.setLocalScale(1f, 1f, 1f);
+        rootNode.attachChild(terrain);
+    }
+
+    private void createSky() {
+        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
+        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
+        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
+        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
+        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
+        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
+
+        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
+        rootNode.attachChild(sky);
+    }
+
+  
+}
diff --git a/engine/src/test/jme3test/light/TestTangentGen.java b/engine/src/test/jme3test/light/TestTangentGen.java
new file mode 100644
index 0000000..a810d6a
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestTangentGen.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TangentBinormalGenerator;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+
+public class TestTangentGen extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Geometry lightMdl;
+
+    public static void main(String[] args){
+        TestTangentGen app = new TestTangentGen();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(20);
+        Sphere sphereMesh = new Sphere(32, 32, 1);
+        sphereMesh.setTextureMode(Sphere.TextureMode.Projected);
+        sphereMesh.updateGeometry(32, 32, 1, false, false);
+        addMesh("Sphere", sphereMesh, new Vector3f(-1, 0, 0));
+
+        Quad quadMesh = new Quad(1, 1);
+        quadMesh.updateGeometry(1, 1);
+        addMesh("Quad", quadMesh, new Vector3f(1, 0, 0));
+
+        Mesh strip = createTriangleStripMesh();
+        addMesh("strip", strip, new Vector3f(0, -3, 0));
+        
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -1, -1).normalizeLocal());
+        dl.setColor(ColorRGBA.White);
+        rootNode.addLight(dl);
+    }
+
+    private void addMesh(String name, Mesh mesh, Vector3f translation) {
+        TangentBinormalGenerator.generate(mesh);
+
+        Geometry testGeom = new Geometry(name, mesh);
+        Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m");
+        testGeom.setMaterial(mat);
+        testGeom.getLocalTranslation().set(translation);
+        rootNode.attachChild(testGeom);
+
+        Geometry debug = new Geometry(
+                "Debug " + name,
+                TangentBinormalGenerator.genTbnLines(mesh, 0.08f)
+        );
+        Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+        debug.setMaterial(debugMat);
+        debug.setCullHint(Spatial.CullHint.Never);
+        debug.getLocalTranslation().set(translation);
+        rootNode.attachChild(debug);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+    }
+
+    private Mesh createTriangleStripMesh() {
+        Mesh strip = new Mesh();
+        strip.setMode(Mode.TriangleStrip);
+        FloatBuffer vb = BufferUtils.createFloatBuffer(3*3*3); // 3 rows * 3 columns * 3 floats
+        vb.rewind();
+        vb.put(new float[]{0,2,0}); vb.put(new float[]{1,2,0}); vb.put(new float[]{2,2,0});
+        vb.put(new float[]{0,1,0}); vb.put(new float[]{1,1,0}); vb.put(new float[]{2,1,0});
+        vb.put(new float[]{0,0,0}); vb.put(new float[]{1,0,0}); vb.put(new float[]{2,0,0});
+        FloatBuffer nb = BufferUtils.createFloatBuffer(3*3*3);
+        nb.rewind();
+        nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1});
+        nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1});
+        nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1}); nb.put(new float[]{0,0,1});
+        FloatBuffer tb = BufferUtils.createFloatBuffer(3*3*2);
+        tb.rewind();
+        tb.put(new float[]{0,0}); tb.put(new float[]{0.5f,0}); tb.put(new float[]{1,0});
+        tb.put(new float[]{0,0.5f}); tb.put(new float[]{0.5f,0.5f}); tb.put(new float[]{1,0.5f});
+        tb.put(new float[]{0,1}); tb.put(new float[]{0.5f,1}); tb.put(new float[]{1,1});
+        int[] indexes = new int[]{0,3,1,4,2,5, 5,3, 3,6,4,7,5,8};
+        IntBuffer ib = BufferUtils.createIntBuffer(indexes.length);
+        ib.put(indexes);
+        strip.setBuffer(Type.Position, 3, vb);
+		strip.setBuffer(Type.Normal, 3, nb);
+		strip.setBuffer(Type.TexCoord, 2, tb);
+		strip.setBuffer(Type.Index, 3, ib);
+        strip.updateBound();
+        return strip;
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestTangentGenBadModels.java b/engine/src/test/jme3test/light/TestTangentGenBadModels.java
new file mode 100644
index 0000000..a786d76
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestTangentGenBadModels.java
@@ -0,0 +1,136 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+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.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.*;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+/**
+ *
+ * @author Kirusha
+ */
+public class TestTangentGenBadModels extends SimpleApplication {
+    
+    float angle;
+    PointLight pl;
+    Geometry lightMdl;
+
+    public static void main(String[] args){
+        TestTangentGenBadModels app = new TestTangentGenBadModels();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+//        assetManager.registerLocator("http://jme-glsl-shaders.googlecode.com/hg/assets/Models/LightBlow/", UrlLocator.class);
+//        assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/", UrlLocator.class);
+        
+        final Spatial badModel = assetManager.loadModel("Models/TangentBugs/test.blend");
+//        badModel.setLocalScale(1f);
+        
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png"));
+//        Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m");
+        badModel.setMaterial(mat);
+        rootNode.attachChild(badModel);
+        
+        // TODO: For some reason blender loader fails to load this.
+        // need to check it
+//        Spatial model = assetManager.loadModel("test.blend");
+//        rootNode.attachChild(model);
+        
+        final Node debugTangents = new Node("debug tangents");
+        debugTangents.setCullHint(CullHint.Always);
+        rootNode.attachChild(debugTangents);
+
+        final Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+        
+        badModel.depthFirstTraversal(new SceneGraphVisitorAdapter(){
+            @Override
+            public void visit(Geometry g){
+                Mesh m = g.getMesh();
+                Material mat = g.getMaterial();
+                
+//                if (mat.getParam("DiffuseMap") != null){
+//                    mat.setTexture("DiffuseMap", null);
+//                }
+                TangentBinormalGenerator.generate(m);
+                
+                Geometry debug = new Geometry(
+                    "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());
+                debugTangents.attachChild(debug);
+            }
+        });
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.8f, -0.6f, -0.08f).normalizeLocal());
+        dl.setColor(new ColorRGBA(1,1,1,1));
+        rootNode.addLight(dl);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        lightMdl.getMesh().setStatic();
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+//        rootNode.addLight(pl);
+        
+        
+        BitmapText info = new BitmapText(guiFont);
+        info.setText("Press SPACE to switch between lighting and tangent display");
+        info.setQueueBucket(Bucket.Gui);
+        info.move(0, settings.getHeight() - info.getLineHeight(), 0);
+        rootNode.attachChild(info);
+        
+        inputManager.addMapping("space", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(new ActionListener() {
+            
+            private boolean isLit = true;
+            
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) return;
+                Material mat;
+                if (isLit){
+                    mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m");
+                    debugTangents.setCullHint(CullHint.Inherit);
+                }else{
+                    mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+                    mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png"));
+                    debugTangents.setCullHint(CullHint.Always);
+                }
+                isLit = !isLit;
+                badModel.setMaterial(mat);
+            }
+        }, "space");
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 2f, FastMath.sin(angle) * 2f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+    
+    
+}
diff --git a/engine/src/test/jme3test/light/TestTangentGenBadUV.java b/engine/src/test/jme3test/light/TestTangentGenBadUV.java
new file mode 100644
index 0000000..7816c71
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestTangentGenBadUV.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestTangentGenBadUV extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Geometry lightMdl;
+
+    public static void main(String[] args){
+        TestTangentGenBadUV app = new TestTangentGenBadUV();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        if (teapot instanceof Geometry){
+            Geometry g = (Geometry) teapot;
+            TangentBinormalGenerator.generate(g.getMesh());
+        }else{
+            throw new RuntimeException();
+        }
+        teapot.setLocalScale(2f);
+        Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m");
+        teapot.setMaterial(mat);
+        rootNode.attachChild(teapot);
+
+        Geometry debug = new Geometry(
+                "Debug Teapot",
+                TangentBinormalGenerator.genTbnLines(((Geometry) teapot).getMesh(), 0.03f)
+        );
+        Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+        debug.setMaterial(debugMat);
+        debug.setCullHint(Spatial.CullHint.Never);
+        debug.getLocalTranslation().set(teapot.getLocalTranslation());
+        debug.getLocalScale().set(teapot.getLocalScale());
+        rootNode.attachChild(debug);
+
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1,-1,-1).normalizeLocal());
+        dl.setColor(ColorRGBA.White);
+        rootNode.addLight(dl);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        lightMdl.getMesh().setStatic();
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        //pl.setRadius(3f);
+        rootNode.addLight(pl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 0.5f, FastMath.sin(angle) * 2f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/light/TestTransparentShadow.java b/engine/src/test/jme3test/light/TestTransparentShadow.java
new file mode 100644
index 0000000..727dfa8
--- /dev/null
+++ b/engine/src/test/jme3test/light/TestTransparentShadow.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 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.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.LodControl;
+import com.jme3.scene.shape.Quad;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.shadow.PssmShadowRenderer.CompareMode;
+import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+
+public class TestTransparentShadow extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestTransparentShadow app = new TestTransparentShadow();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+
+        cam.setLocation(new Vector3f(2.0606942f, 3.20342f, 6.7860126f));
+        cam.setRotation(new Quaternion(-0.017481906f, 0.98241085f, -0.12393151f, -0.13857932f));
+
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Quad q = new Quad(20, 20);
+        q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5));
+        Geometry geom = new Geometry("floor", q);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        geom.setMaterial(mat);
+        
+        geom.rotate(-FastMath.HALF_PI, 0, 0);
+        geom.center();
+        geom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(geom);
+
+        // create the geometry and attach it
+        Spatial teaGeom = assetManager.loadModel("Models/Tree/Tree.mesh.j3o");
+        teaGeom.setQueueBucket(Bucket.Transparent);
+        teaGeom.setShadowMode(ShadowMode.Cast);
+
+        teaGeom.depthFirstTraversal(new SceneGraphVisitorAdapter(){
+            @Override
+             public void visit(Geometry geom) {
+                 LodControl lodCtrl = new LodControl();
+                 lodCtrl.setTrisPerPixel(0.25f);
+                 geom.addControl(lodCtrl);
+             }
+        });
+        
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(2));
+        rootNode.addLight(al);
+
+        DirectionalLight dl1 = new DirectionalLight();
+        dl1.setDirection(new Vector3f(1, -1, 1).normalizeLocal());
+        dl1.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl1);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl);
+
+        rootNode.attachChild(teaGeom);    
+        
+        /** Uses Texture from jme3-test-data library! */
+        ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
+        Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
+        //mat_red.getAdditionalRenderState().setDepthTest(true);
+        //mat_red.getAdditionalRenderState().setDepthWrite(true);
+        fire.setMaterial(mat_red);
+        fire.setImagesX(2); fire.setImagesY(2); // 2x2 texture animation
+        fire.setEndColor(  new ColorRGBA(1f, 0f, 0f, 1f));   // red
+        fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
+        fire.setInitialVelocity(new Vector3f(0, 2, 0));
+        fire.setStartSize(0.6f);
+        fire.setEndSize(0.1f);
+        fire.setGravity(0, 0, 0);
+        fire.setLowLife(0.5f);
+        fire.setHighLife(1.5f);
+        fire.setVelocityVariation(0.3f);
+        fire.setLocalTranslation(1.0f, 0, 1.0f);
+        fire.setLocalScale(0.3f);
+        fire.setQueueBucket(Bucket.Translucent);
+        rootNode.attachChild(fire);
+
+        
+        PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 1);
+        pssmRenderer.setDirection(new Vector3f(0.01f, -1f, 0.01f).normalizeLocal());
+        pssmRenderer.setLambda(0.55f);
+        pssmRenderer.setShadowIntensity(0.6f);
+        pssmRenderer.setCompareMode(CompareMode.Software);
+        pssmRenderer.setFilterMode(FilterMode.PCF4);
+        pssmRenderer.displayDebug();
+        viewPort.addProcessor(pssmRenderer);
+    }
+}
diff --git a/engine/src/test/jme3test/material/TestBumpModel.java b/engine/src/test/jme3test/material/TestBumpModel.java
new file mode 100644
index 0000000..c10ccbe
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestBumpModel.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2009-2010 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.ogre.OgreMeshKey;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestBumpModel extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestBumpModel app = new TestBumpModel();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Spatial signpost = (Spatial) assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml"));
+        signpost.setMaterial( (Material) assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"));
+        TangentBinormalGenerator.generate(signpost);
+        rootNode.attachChild(signpost);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial( (Material) assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        // flourescent main light
+        pl = new PointLight();
+        pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f));
+        rootNode.addLight(pl);
+
+        // sunset light
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f));
+        rootNode.addLight(dl);
+
+        // skylight
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f));
+        rootNode.addLight(dl);
+
+        // white ambient light
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/material/TestColoredTexture.java b/engine/src/test/jme3test/material/TestColoredTexture.java
new file mode 100644
index 0000000..8beb771
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestColoredTexture.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+
+public class TestColoredTexture extends SimpleApplication {
+
+    private float time = 0;
+    private ColorRGBA nextColor;
+    private ColorRGBA prevColor;
+    private Material mat;
+
+    public static void main(String[] args){
+        TestColoredTexture app = new TestColoredTexture();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Quad quadMesh = new Quad(512,512);
+        Geometry quad = new Geometry("Quad", quadMesh);
+        quad.setQueueBucket(Bucket.Gui);
+
+        mat = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+        quad.setMaterial(mat);
+        guiNode.attachChildAt(quad, 0);
+
+        nextColor = ColorRGBA.randomColor();
+        prevColor = ColorRGBA.Black;
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        time += tpf;
+        if (time > 1f){
+            time -= 1f;
+            prevColor = nextColor;
+            nextColor = ColorRGBA.randomColor();
+        }
+        ColorRGBA currentColor = new ColorRGBA();
+        currentColor.interpolate(prevColor, nextColor, time);
+
+        mat.setColor("Color", currentColor);
+    }
+
+}
diff --git a/engine/src/test/jme3test/material/TestNormalMapping.java b/engine/src/test/jme3test/material/TestNormalMapping.java
new file mode 100644
index 0000000..4ad3abe
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestNormalMapping.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestNormalMapping extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestNormalMapping app = new TestNormalMapping();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Sphere sphMesh = new Sphere(32, 32, 1);
+        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
+        sphMesh.updateGeometry(32, 32, 1, false, false);
+        TangentBinormalGenerator.generate(sphMesh);
+
+        Geometry sphere = new Geometry("Rock Ball", sphMesh);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        sphere.setMaterial(mat);
+        rootNode.attachChild(sphere);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setPosition(new Vector3f(0f, 0f, 4f));
+        rootNode.addLight(pl);
+
+//        DirectionalLight dl = new DirectionalLight();
+//        dl.setDirection(new Vector3f(1,-1,1).normalizeLocal());
+//        dl.setColor(new ColorRGBA(0.22f, 0.15f, 0.1f, 1.0f));
+//        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/material/TestParallax.java b/engine/src/test/jme3test/material/TestParallax.java
new file mode 100644
index 0000000..b52ad3c
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestParallax.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2009-2010 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.FXAAFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestParallax extends SimpleApplication {
+
+    private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal();
+
+    public static void main(String[] args) {
+        TestParallax app = new TestParallax();
+        app.start();
+    }
+
+    public void setupSkyBox() {
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false));
+    }
+    DirectionalLight dl;
+
+    public void setupLighting() {
+
+        dl = new DirectionalLight();
+        dl.setDirection(lightDir);
+        dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1));
+        rootNode.addLight(dl);
+    }
+    Material mat;
+
+    public void setupFloor() {
+        mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m");
+        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.setFloat("Shininess", 0);
+
+       // Node floorGeom = (Node) assetManager.loadAsset("Models/WaterTest/WaterTest.mesh.xml");
+        //Geometry g = ((Geometry) floorGeom.getChild(0));
+        //g.getMesh().scaleTextureCoordinates(new Vector2f(10, 10));
+                
+        Node floorGeom = new Node("floorGeom");
+        Quad q = new Quad(100, 100);
+        q.scaleTextureCoordinates(new Vector2f(10, 10));
+        Geometry g = new Geometry("geom", q);
+        g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+        floorGeom.attachChild(g);
+        
+        
+        TangentBinormalGenerator.generate(floorGeom);
+        floorGeom.setLocalTranslation(-50, 22, 60);
+        //floorGeom.setLocalScale(100);
+
+        floorGeom.setMaterial(mat);        
+        rootNode.attachChild(floorGeom);
+    }
+
+    public void setupSignpost() {
+        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
+        TangentBinormalGenerator.generate(signpost);
+        signpost.setMaterial(mat);
+        signpost.rotate(0, FastMath.HALF_PI, 0);
+        signpost.setLocalTranslation(12, 23.5f, 30);
+        signpost.setLocalScale(4);
+        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(signpost);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f));
+        cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f));
+
+        flyCam.setMoveSpeed(30);
+
+
+        setupLighting();
+        setupSkyBox();
+        setupFloor();
+        setupSignpost();
+
+        inputManager.addListener(new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if ("heightUP".equals(name)) {
+                    parallaxHeigh += 0.0001;
+                    mat.setFloat("ParallaxHeight", parallaxHeigh);
+                }
+                if ("heightDown".equals(name)) {
+                    parallaxHeigh -= 0.0001;
+                    parallaxHeigh = Math.max(parallaxHeigh, 0);
+                    mat.setFloat("ParallaxHeight", parallaxHeigh);
+                }
+
+            }
+        }, "heightUP", "heightDown");
+        inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K));
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed && "toggleSteep".equals(name)) {
+                    steep = !steep;
+                    mat.setBoolean("SteepParallax", steep);
+                }
+            }
+        }, "toggleSteep");
+        inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE));
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        FXAAFilter fxaa = new FXAAFilter();
+        fxaa.setReduceMul(0.08f);
+        fpp.addFilter(fxaa);
+        viewPort.addProcessor(fpp);
+    }
+    float parallaxHeigh = 0.05f;
+    float time = 0;
+    boolean steep = false;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+//        time+=tpf;
+//        lightDir.set(FastMath.sin(time), -1, FastMath.cos(time));
+//        bsr.setDirection(lightDir);
+//        dl.setDirection(lightDir);
+    }
+}
diff --git a/engine/src/test/jme3test/material/TestSimpleBumps.java b/engine/src/test/jme3test/material/TestSimpleBumps.java
new file mode 100644
index 0000000..6155196
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestSimpleBumps.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+// phong cutoff for light to normal angle > 90?
+public class TestSimpleBumps extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestSimpleBumps app = new TestSimpleBumps();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Quad quadMesh = new Quad(1, 1);
+
+        Geometry sphere = new Geometry("Rock Ball", quadMesh);
+        Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m");
+        sphere.setMaterial(mat);
+        TangentBinormalGenerator.generate(sphere);
+        rootNode.attachChild(sphere);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setPosition(new Vector3f(0f, 0f, 4f));
+        rootNode.addLight(pl);
+
+//        DirectionalLight dl = new DirectionalLight();
+//        dl.setDirection(new Vector3f(1, -1, -1).normalizeLocal());
+//        dl.setColor(new ColorRGBA(0.22f, 0.15f, 0.1f, 1.0f));
+//        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/material/TestUnshadedModel.java b/engine/src/test/jme3test/material/TestUnshadedModel.java
new file mode 100644
index 0000000..826ef44
--- /dev/null
+++ b/engine/src/test/jme3test/material/TestUnshadedModel.java
@@ -0,0 +1,44 @@
+package jme3test.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestUnshadedModel extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestUnshadedModel app = new TestUnshadedModel();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Sphere sphMesh = new Sphere(32, 32, 1);
+        sphMesh.setTextureMode(Sphere.TextureMode.Projected);
+        sphMesh.updateGeometry(32, 32, 1, false, false);
+        TangentBinormalGenerator.generate(sphMesh);
+
+        Geometry sphere = new Geometry("Rock Ball", sphMesh);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        mat.setColor("Ambient", ColorRGBA.DarkGray);
+        mat.setColor("Diffuse", ColorRGBA.White);
+        mat.setBoolean("UseMaterialColors", true);
+        sphere.setMaterial(mat);
+        rootNode.attachChild(sphere);
+
+        PointLight pl = new PointLight();
+        pl.setColor(ColorRGBA.White);
+        pl.setPosition(new Vector3f(4f, 0f, 0f));
+        rootNode.addLight(pl);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White);
+        rootNode.addLight(al);
+    }
+}
diff --git a/engine/src/test/jme3test/math/TestHalfFloat.java b/engine/src/test/jme3test/math/TestHalfFloat.java
new file mode 100644
index 0000000..17365d3
--- /dev/null
+++ b/engine/src/test/jme3test/math/TestHalfFloat.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2009-2010 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.math;
+
+import com.jme3.math.FastMath;
+import java.util.Scanner;
+
+public class TestHalfFloat {
+    public static void main(String[] args){
+        Scanner scan = new Scanner(System.in);
+        while (true){
+            System.out.println("Enter float to convert or 'x' to exit: ");
+            String s = scan.nextLine();
+            if (s.equals("x"))
+                break;
+
+            float flt = Float.valueOf(s);
+            short half = FastMath.convertFloatToHalf(flt);
+            float flt2 = FastMath.convertHalfToFloat(half);
+
+            System.out.println("Input float: "+flt);
+            System.out.println("Result float: "+flt2);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/model/TestHoverTank.java b/engine/src/test/jme3test/model/TestHoverTank.java
new file mode 100644
index 0000000..62656c4
--- /dev/null
+++ b/engine/src/test/jme3test/model/TestHoverTank.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 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.model;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.ChaseCamera;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.LodControl;
+import jme3test.post.BloomUI;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestHoverTank extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestHoverTank app = new TestHoverTank();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml");
+
+        flyCam.setEnabled(false);
+        ChaseCamera chaseCam = new ChaseCamera(cam, tank, inputManager);
+        chaseCam.setSmoothMotion(true);
+        chaseCam.setMaxDistance(100000);
+        chaseCam.setMinVerticalRotation(-FastMath.PI / 2);
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Geometry tankGeom = (Geometry) tank.getChild(0);
+        LodControl control = new LodControl();
+        tankGeom.addControl(control);
+        rootNode.attachChild(tank);
+
+        Vector3f lightDir = new Vector3f(-0.8719428f, -0.46824604f, 0.14304268f);
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(new ColorRGBA(1.0f, 0.92f, 0.75f, 1f));
+        dl.setDirection(lightDir);
+
+        Vector3f lightDir2 = new Vector3f(0.70518064f, 0.5902297f, -0.39287305f);
+        DirectionalLight dl2 = new DirectionalLight();
+        dl2.setColor(new ColorRGBA(0.7f, 0.85f, 1.0f, 1f));
+        dl2.setDirection(lightDir2);
+
+        rootNode.addLight(dl);
+        rootNode.addLight(dl2);
+        rootNode.attachChild(tank);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        BloomFilter bf = new BloomFilter(BloomFilter.GlowMode.Objects);
+        bf.setBloomIntensity(2.0f);
+        bf.setExposurePower(1.3f);
+        fpp.addFilter(bf);
+        BloomUI bui = new BloomUI(inputManager, bf);
+        viewPort.addProcessor(fpp);
+    }
+}
diff --git a/engine/src/test/jme3test/model/TestMonkeyHead.java b/engine/src/test/jme3test/model/TestMonkeyHead.java
new file mode 100644
index 0000000..396195f
--- /dev/null
+++ b/engine/src/test/jme3test/model/TestMonkeyHead.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 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.model;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+
+public class TestMonkeyHead extends SimpleApplication {
+
+    float angle;
+    PointLight pl;
+    Spatial lightMdl;
+
+    public static void main(String[] args){
+        TestMonkeyHead app = new TestMonkeyHead();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Spatial bumpy = (Spatial) assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml");
+        rootNode.attachChild(bumpy);
+
+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        rootNode.attachChild(lightMdl);
+
+        // flourescent main light
+        pl = new PointLight();
+        pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f));
+        rootNode.addLight(pl);
+
+        // sunset light
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f));
+        rootNode.addLight(dl);
+
+        // skylight
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f));
+        rootNode.addLight(dl);
+
+        // white ambient light
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f));
+        rootNode.addLight(dl);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        angle += tpf * 0.25f;
+        angle %= FastMath.TWO_PI;
+
+        pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f));
+        lightMdl.setLocalTranslation(pl.getPosition());
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/TestObjLoading.java b/engine/src/test/jme3test/model/TestObjLoading.java
new file mode 100644
index 0000000..a94a708
--- /dev/null
+++ b/engine/src/test/jme3test/model/TestObjLoading.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.model;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.scene.Geometry;
+
+/**
+ * Tests OBJ format loading
+ */
+public class TestObjLoading extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestObjLoading app = new TestObjLoading();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        // create the geometry and attach it
+        Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+        
+        // show normals as material
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+        teaGeom.setMaterial(mat);
+
+        rootNode.attachChild(teaGeom);
+    }
+}
diff --git a/engine/src/test/jme3test/model/TestOgreLoading.java b/engine/src/test/jme3test/model/TestOgreLoading.java
new file mode 100644
index 0000000..8676b1e
--- /dev/null
+++ b/engine/src/test/jme3test/model/TestOgreLoading.java
@@ -0,0 +1,111 @@
+/*

+ * Copyright (c) 2009-2010 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.model;

+

+import com.jme3.app.SimpleApplication;

+import com.jme3.light.DirectionalLight;

+import com.jme3.light.PointLight;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.FastMath;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Spatial;

+import com.jme3.scene.shape.Sphere;

+

+public class TestOgreLoading extends SimpleApplication {

+

+    float angle1;

+    float angle2;

+    PointLight pl;

+    PointLight p2;

+    Spatial lightMdl;

+    Spatial lightMd2;

+

+    public static void main(String[] args) {

+        TestOgreLoading app = new TestOgreLoading();

+        app.start();

+    }

+

+    public void simpleInitApp() {

+//        PointLight pl = new PointLight();

+//        pl.setPosition(new Vector3f(10, 10, -10));

+//        rootNode.addLight(pl);

+        flyCam.setMoveSpeed(10f);

+

+        // sunset light

+        DirectionalLight dl = new DirectionalLight();

+        dl.setDirection(new Vector3f(-0.1f, -0.7f, 1).normalizeLocal());

+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));

+        rootNode.addLight(dl);

+

+

+        lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));

+        lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));

+        rootNode.attachChild(lightMdl);

+

+        lightMd2 = new Geometry("Light", new Sphere(10, 10, 0.1f));

+        lightMd2.setMaterial(assetManager.loadMaterial("Common/Materials/WhiteColor.j3m"));

+        rootNode.attachChild(lightMd2);

+

+

+        pl = new PointLight();

+        pl.setColor(new ColorRGBA(1, 0.9f, 0.9f, 0));

+        pl.setPosition(new Vector3f(0f, 0f, 4f));

+        rootNode.addLight(pl);

+

+        p2 = new PointLight();

+        p2.setColor(new ColorRGBA(0.9f, 1, 0.9f, 0));

+        p2.setPosition(new Vector3f(0f, 0f, 3f));

+        rootNode.addLight(p2);

+

+

+        // create the geometry and attach it

+        Spatial elephant = (Spatial) assetManager.loadModel("Models/Elephant/Elephant.mesh.xml");

+        float scale = 0.05f;

+        elephant.scale(scale, scale, scale);

+        rootNode.attachChild(elephant);

+    }

+

+    @Override

+    public void simpleUpdate(float tpf) {

+        angle1 += tpf * 0.25f;

+        angle1 %= FastMath.TWO_PI;

+

+        angle2 += tpf * 0.50f;

+        angle2 %= FastMath.TWO_PI;

+

+        pl.setPosition(new Vector3f(FastMath.cos(angle1) * 4f, 0.5f, FastMath.sin(angle1) * 4f));

+        p2.setPosition(new Vector3f(FastMath.cos(angle2) * 4f, 0.5f, FastMath.sin(angle2) * 4f));

+        lightMdl.setLocalTranslation(pl.getPosition());

+        lightMd2.setLocalTranslation(p2.getPosition());

+    }

+}

diff --git a/engine/src/test/jme3test/model/anim/TestAnimBlendBug.java b/engine/src/test/jme3test/model/anim/TestAnimBlendBug.java
new file mode 100644
index 0000000..df487a3
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestAnimBlendBug.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009-2010 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.model.anim;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.debug.SkeletonDebugger;
+
+public class TestAnimBlendBug extends SimpleApplication implements ActionListener {
+
+//    private AnimControl control;
+    private AnimChannel channel1, channel2;
+    private String[] animNames;
+
+    private float blendTime = 0.5f;
+    private float lockAfterBlending =  blendTime + 0.25f;
+    private float blendingAnimationLock;
+
+    public static void main(String[] args) {
+        TestAnimBlendBug app = new TestAnimBlendBug();
+        app.start();
+    }
+
+    public void onAction(String name, boolean value, float tpf) {
+        if (name.equals("One") && value){
+            channel1.setAnim(animNames[4], blendTime);
+            channel2.setAnim(animNames[4], 0);
+            channel1.setSpeed(0.25f);
+            channel2.setSpeed(0.25f);
+            blendingAnimationLock = lockAfterBlending;
+        }
+    }
+
+    public void onPreUpdate(float tpf) {
+    }
+
+    public void onPostUpdate(float tpf) {
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        // Is there currently a blending underway?
+        if (blendingAnimationLock > 0f) {
+            blendingAnimationLock -= tpf;
+        }
+    }
+
+    @Override
+    public void simpleInitApp() {
+        inputManager.addMapping("One", new KeyTrigger(KeyInput.KEY_1));
+        inputManager.addListener(this, "One");
+
+        flyCam.setMoveSpeed(100f);
+        cam.setLocation( new Vector3f( 0f, 150f, -325f ) );
+        cam.lookAt( new Vector3f( 0f, 100f, 0f ), Vector3f.UNIT_Y );
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, 1).normalizeLocal());
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+
+        Node model1 = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
+        Node model2 = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
+//        Node model2 = model1.clone();
+
+        model1.setLocalTranslation(-60, 0, 0);
+        model2.setLocalTranslation(60, 0, 0);
+
+        AnimControl control1 = model1.getControl(AnimControl.class);
+        animNames = control1.getAnimationNames().toArray(new String[0]);
+        channel1 = control1.createChannel();
+        
+        AnimControl control2 = model2.getControl(AnimControl.class);
+        channel2 = control2.createChannel();
+
+        SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton1", control1.getSkeleton());
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Green);
+        mat.getAdditionalRenderState().setDepthTest(false);
+        skeletonDebug.setMaterial(mat);
+        model1.attachChild(skeletonDebug);
+
+        skeletonDebug = new SkeletonDebugger("skeleton2", control2.getSkeleton());
+        skeletonDebug.setMaterial(mat);
+        model2.attachChild(skeletonDebug);
+
+        rootNode.attachChild(model1);
+        rootNode.attachChild(model2);
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/anim/TestAnimationFactory.java b/engine/src/test/jme3test/model/anim/TestAnimationFactory.java
new file mode 100644
index 0000000..5576851
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestAnimationFactory.java
@@ -0,0 +1,85 @@
+package jme3test.model.anim;

+

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.AnimationFactory;

+import com.jme3.app.SimpleApplication;

+import com.jme3.light.AmbientLight;

+import com.jme3.light.DirectionalLight;

+import com.jme3.math.FastMath;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.shape.Box;

+import com.jme3.util.TangentBinormalGenerator;

+

+public class TestAnimationFactory extends SimpleApplication {

+

+    public static void main(String[] args) {

+        TestSpatialAnim app = new TestSpatialAnim();

+        app.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+

+        AmbientLight al = new AmbientLight();

+        rootNode.addLight(al);

+

+        DirectionalLight dl = new DirectionalLight();

+        dl.setDirection(Vector3f.UNIT_XYZ.negate());

+        rootNode.addLight(dl);

+

+        // Create model

+        Box box = new Box(1, 1, 1);

+        Geometry geom = new Geometry("box", box);

+        geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));

+        Node model = new Node("model");

+        model.attachChild(geom);

+

+        Box child = new Box(0.5f, 0.5f, 0.5f);

+        Geometry childGeom = new Geometry("box", child);

+        childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));

+        Node childModel = new Node("childmodel");

+        childModel.setLocalTranslation(2, 2, 2);

+        childModel.attachChild(childGeom);

+        model.attachChild(childModel);

+        TangentBinormalGenerator.generate(model);

+

+        //creating quite complex animation witht the AnimationHelper

+        // animation of 6 seconds named "anim" and with 25 frames per second

+        AnimationFactory animationFactory = new AnimationFactory(6, "anim", 25);

+        

+        //creating a translation keyFrame at time = 3 with a translation on the x axis of 5 WU        

+        animationFactory.addTimeTranslation(3, new Vector3f(5, 0, 0));

+        //reseting the translation to the start position at time = 6

+        animationFactory.addTimeTranslation(6, new Vector3f(0, 0, 0));

+

+        //Creating a scale keyFrame at time = 2 with the unit scale.

+        animationFactory.addTimeScale(2, new Vector3f(1, 1, 1));

+        //Creating a scale keyFrame at time = 4 scaling to 1.5

+        animationFactory.addTimeScale(4, new Vector3f(1.5f, 1.5f, 1.5f));

+        //reseting the scale to the start value at time = 5

+        animationFactory.addTimeScale(5, new Vector3f(1, 1, 1));

+

+        

+        //Creating a rotation keyFrame at time = 0.5 of quarter PI around the Z axis

+        animationFactory.addTimeRotation(0.5f,new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z));

+        //rotating back to initial rotation value at time = 1

+        animationFactory.addTimeRotation(1,Quaternion.IDENTITY);

+        //Creating a rotation keyFrame at time = 2. Note that i used the Euler angle version because the angle is higher than PI

+        //this should result in a complete revolution of the spatial around the x axis in 1 second (from 1 to 2)

+        animationFactory.addTimeRotationAngles(2, FastMath.TWO_PI,0, 0);

+

+

+        AnimControl control = new AnimControl();

+        control.addAnim(animationFactory.buildAnimation());

+

+        model.addControl(control);

+

+        rootNode.attachChild(model);

+

+        //run animation

+        control.createChannel().setAnim("anim");

+    }

+}

diff --git a/engine/src/test/jme3test/model/anim/TestBlenderAnim.java b/engine/src/test/jme3test/model/anim/TestBlenderAnim.java
new file mode 100644
index 0000000..999c276
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestBlenderAnim.java
@@ -0,0 +1,93 @@
+/*

+ * Copyright (c) 2009-2010 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.model.anim;

+

+import com.jme3.animation.AnimChannel;

+import com.jme3.animation.AnimControl;

+import com.jme3.app.SimpleApplication;

+import com.jme3.asset.BlenderKey;

+import com.jme3.light.DirectionalLight;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+

+public class TestBlenderAnim extends SimpleApplication {

+

+    private AnimChannel channel;

+    private AnimControl control;

+

+    public static void main(String[] args) {

+    	TestBlenderAnim app = new TestBlenderAnim();

+        app.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+        flyCam.setMoveSpeed(10f);

+        cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));

+        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));

+

+        DirectionalLight dl = new DirectionalLight();

+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());

+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));

+        rootNode.addLight(dl);

+

+        BlenderKey blenderKey = new BlenderKey("Blender/2.4x/BaseMesh_249.blend");

+        

+        Spatial scene = (Spatial) assetManager.loadModel(blenderKey);

+        rootNode.attachChild(scene);

+        

+        Spatial model = this.findNode(rootNode, "BaseMesh_01");

+        model.center();

+        

+        control = model.getControl(AnimControl.class);

+        channel = control.createChannel();

+

+        channel.setAnim("run_01");

+    }

+    

+    /**

+     * This method finds a node of a given name.

+     * @param rootNode the root node to search

+     * @param name the name of the searched node

+     * @return the found node or null

+     */

+    private Spatial findNode(Node rootNode, String name) {

+        if (name.equals(rootNode.getName())) {

+            return rootNode;

+        }

+        return rootNode.getChild(name);

+    }

+}

diff --git a/engine/src/test/jme3test/model/anim/TestBlenderObjectAnim.java b/engine/src/test/jme3test/model/anim/TestBlenderObjectAnim.java
new file mode 100644
index 0000000..ccd8e9e
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestBlenderObjectAnim.java
@@ -0,0 +1,93 @@
+/*

+ * Copyright (c) 2009-2010 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.model.anim;

+

+import com.jme3.animation.AnimChannel;

+import com.jme3.animation.AnimControl;

+import com.jme3.app.SimpleApplication;

+import com.jme3.asset.BlenderKey;

+import com.jme3.light.DirectionalLight;

+import com.jme3.math.ColorRGBA;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Node;

+import com.jme3.scene.Spatial;

+

+public class TestBlenderObjectAnim extends SimpleApplication {

+

+    private AnimChannel channel;

+    private AnimControl control;

+

+    public static void main(String[] args) {

+    	TestBlenderObjectAnim app = new TestBlenderObjectAnim();

+        app.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+        flyCam.setMoveSpeed(10f);

+        cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));

+        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));

+

+        DirectionalLight dl = new DirectionalLight();

+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());

+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));

+        rootNode.addLight(dl);

+

+        BlenderKey blenderKey = new BlenderKey("Blender/2.4x/animtest.blend");

+        

+        Spatial scene = (Spatial) assetManager.loadModel(blenderKey);

+        rootNode.attachChild(scene);

+        

+        Spatial model = this.findNode(rootNode, "TestAnim");

+        model.center();

+        

+        control = model.getControl(AnimControl.class);

+        channel = control.createChannel();

+

+        channel.setAnim("TestAnim");

+    }

+    

+    /**

+     * This method finds a node of a given name.

+     * @param rootNode the root node to search

+     * @param name the name of the searched node

+     * @return the found node or null

+     */

+    private Spatial findNode(Node rootNode, String name) {

+        if (name.equals(rootNode.getName())) {

+            return rootNode;

+        }

+        return rootNode.getChild(name);

+    }

+}

diff --git a/engine/src/test/jme3test/model/anim/TestCustomAnim.java b/engine/src/test/jme3test/model/anim/TestCustomAnim.java
new file mode 100644
index 0000000..f1b8933
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestCustomAnim.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2009-2010 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.model.anim;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.shape.Box;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+public class TestCustomAnim extends SimpleApplication {
+
+    private Bone bone;
+    private Skeleton skeleton;
+    private Quaternion rotation = new Quaternion();
+
+    public static void main(String[] args) {
+        TestCustomAnim app = new TestCustomAnim();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+        AmbientLight al = new AmbientLight();
+        rootNode.addLight(al);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(Vector3f.UNIT_XYZ.negate());
+        rootNode.addLight(dl);
+
+        Box box = new Box(1, 1, 1);
+
+        // Setup bone weight buffer
+        FloatBuffer weights = FloatBuffer.allocate( box.getVertexCount() * 4 );
+        VertexBuffer weightsBuf = new VertexBuffer(Type.BoneWeight);
+        weightsBuf.setupData(Usage.CpuOnly, 4, Format.Float, weights);
+        box.setBuffer(weightsBuf);
+
+        // Setup bone index buffer
+        ByteBuffer indices = ByteBuffer.allocate( box.getVertexCount() * 4 );
+        VertexBuffer indicesBuf = new VertexBuffer(Type.BoneIndex);
+        indicesBuf.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indices);
+        box.setBuffer(indicesBuf);
+
+        // Create bind pose buffers
+        box.generateBindPose(true);
+
+        // Create skeleton
+        bone = new Bone("root");
+        bone.setBindTransforms(Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ);
+        bone.setUserControl(true);
+        skeleton = new Skeleton(new Bone[]{ bone });
+
+        // Assign all verticies to bone 0 with weight 1
+        for (int i = 0; i < box.getVertexCount() * 4; i += 4){
+            // assign vertex to bone index 0
+            indices.array()[i+0] = 0;
+            indices.array()[i+1] = 0;
+            indices.array()[i+2] = 0;
+            indices.array()[i+3] = 0;
+
+            // set weight to 1 only for first entry
+            weights.array()[i+0] = 1;
+            weights.array()[i+1] = 0;
+            weights.array()[i+2] = 0;
+            weights.array()[i+3] = 0;
+        }
+
+        // Maximum number of weights per bone is 1
+        box.setMaxNumWeights(1);
+
+        // Create model
+        Geometry geom = new Geometry("box", box);
+        geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));
+        Node model = new Node("model");
+        model.attachChild(geom);
+
+        // Create skeleton control
+        SkeletonControl skeletonControl = new SkeletonControl(skeleton);
+        model.addControl(skeletonControl);
+
+        rootNode.attachChild(model);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        // Rotate around X axis
+        Quaternion rotate = new Quaternion();
+        rotate.fromAngleAxis(tpf, Vector3f.UNIT_X);
+
+        // Combine rotation with previous
+        rotation.multLocal(rotate);
+
+        // Set new rotation into bone
+        bone.setUserTransforms(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ);
+
+        // After changing skeleton transforms, must update world data
+        skeleton.updateWorldVectors();
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/anim/TestOgreAnim.java b/engine/src/test/jme3test/model/anim/TestOgreAnim.java
new file mode 100644
index 0000000..c5f8213
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestOgreAnim.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009-2010 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.model.anim;
+
+import com.jme3.animation.*;
+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.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.scene.shape.Box;
+
+public class TestOgreAnim extends SimpleApplication 
+        implements AnimEventListener, ActionListener {
+
+    private AnimChannel channel;
+    private AnimControl control;
+
+    public static void main(String[] args) {
+        TestOgreAnim app = new TestOgreAnim();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(10f);
+        cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
+        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+
+        Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        model.center();
+
+        control = model.getControl(AnimControl.class);
+        control.addListener(this);
+        channel = control.createChannel();
+
+        for (String anim : control.getAnimationNames())
+            System.out.println(anim);
+
+        channel.setAnim("stand");
+
+        SkeletonControl skeletonControl = model.getControl(SkeletonControl.class);
+
+        Box b = new Box(.25f,3f,.25f);
+        Geometry item = new Geometry("Item", b);
+        item.move(0, 1.5f, 0);
+        item.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+        Node n = skeletonControl.getAttachmentsNode("hand.right");
+        n.attachChild(item);
+
+        rootNode.attachChild(model);
+
+        inputManager.addListener(this, "Attack");
+        inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE));
+    }
+
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+        if (animName.equals("Dodge")){
+            channel.setAnim("stand", 0.50f);
+            channel.setLoopMode(LoopMode.DontLoop);
+            channel.setSpeed(1f);
+        }
+    }
+
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    }
+
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Attack") && value){
+            if (!channel.getAnimationName().equals("Dodge")){
+                channel.setAnim("Dodge", 0.50f);
+                channel.setLoopMode(LoopMode.Cycle);
+                channel.setSpeed(0.10f);
+            }
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java b/engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java
new file mode 100644
index 0000000..77385bf
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2009-2010 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.model.anim;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Bone;
+import com.jme3.animation.LoopMode;
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.debug.SkeletonDebugger;
+
+public class TestOgreComplexAnim extends SimpleApplication {
+
+    private AnimControl control;
+    private float angle = 0;
+    private float scale = 1;
+    private float rate = 1;
+
+    public static void main(String[] args) {
+        TestOgreComplexAnim app = new TestOgreComplexAnim();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(10f);
+        cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
+        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
+        rootNode.addLight(dl);
+
+        Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+
+        control = model.getControl(AnimControl.class);
+
+        AnimChannel feet = control.createChannel();
+        AnimChannel leftHand = control.createChannel();
+        AnimChannel rightHand = control.createChannel();
+
+        // feet will dodge
+        feet.addFromRootBone("hip.right");
+        feet.addFromRootBone("hip.left");
+        feet.setAnim("Dodge");
+        feet.setSpeed(2);
+        feet.setLoopMode(LoopMode.Cycle);
+
+        // will blend over 15 seconds to stand
+        feet.setAnim("Walk", 15);
+        feet.setSpeed(0.25f);
+        feet.setLoopMode(LoopMode.Cycle);
+
+        // left hand will pull
+        leftHand.addFromRootBone("uparm.right");
+        leftHand.setAnim("pull");
+        leftHand.setSpeed(.5f);
+
+        // will blend over 15 seconds to stand
+        leftHand.setAnim("stand", 15);
+
+        // right hand will push
+        rightHand.addBone("spinehigh");
+        rightHand.addFromRootBone("uparm.left");
+        rightHand.setAnim("push");
+
+        SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton());
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", ColorRGBA.Green);
+        mat.getAdditionalRenderState().setDepthTest(false);
+        skeletonDebug.setMaterial(mat);
+
+        model.attachChild(skeletonDebug);
+        rootNode.attachChild(model);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Bone b = control.getSkeleton().getBone("spinehigh");
+        Bone b2 = control.getSkeleton().getBone("uparm.left");
+        
+        angle += tpf * rate;        
+        if (angle > FastMath.HALF_PI / 2f){
+            angle = FastMath.HALF_PI / 2f;
+            rate = -1;
+        }else if (angle < -FastMath.HALF_PI / 2f){
+            angle = -FastMath.HALF_PI / 2f;
+            rate = 1;
+        }
+
+        Quaternion q = new Quaternion();
+        q.fromAngles(0, angle, 0);
+
+        b.setUserControl(true);
+        b.setUserTransforms(Vector3f.ZERO, q, Vector3f.UNIT_XYZ);
+        
+        b2.setUserControl(true);
+        b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(1+angle,1+ angle, 1+angle));
+  
+  
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/anim/TestSpatialAnim.java b/engine/src/test/jme3test/model/anim/TestSpatialAnim.java
new file mode 100644
index 0000000..94dfd03
--- /dev/null
+++ b/engine/src/test/jme3test/model/anim/TestSpatialAnim.java
@@ -0,0 +1,87 @@
+package jme3test.model.anim;

+

+import com.jme3.animation.AnimControl;

+import com.jme3.animation.Animation;

+import com.jme3.animation.SpatialTrack;

+import com.jme3.app.SimpleApplication;

+import com.jme3.light.AmbientLight;

+import com.jme3.light.DirectionalLight;

+import com.jme3.math.Quaternion;

+import com.jme3.math.Vector3f;

+import com.jme3.scene.Geometry;

+import com.jme3.scene.Node;

+import com.jme3.scene.shape.Box;

+import java.util.HashMap;

+

+public class TestSpatialAnim extends SimpleApplication {

+

+    public static void main(String[] args) {

+    	TestSpatialAnim app = new TestSpatialAnim();

+        app.start();

+    }

+

+    @Override

+    public void simpleInitApp() {

+

+        AmbientLight al = new AmbientLight();

+        rootNode.addLight(al);

+

+        DirectionalLight dl = new DirectionalLight();

+        dl.setDirection(Vector3f.UNIT_XYZ.negate());

+        rootNode.addLight(dl);

+

+        // Create model

+        Box box = new Box(1, 1, 1);

+        Geometry geom = new Geometry("box", box);

+        geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));

+        Node model = new Node("model");

+        model.attachChild(geom);

+

+        Box child = new Box(0.5f, 0.5f, 0.5f);

+        Geometry childGeom = new Geometry("box", child);

+        childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));

+        Node childModel = new Node("childmodel");

+        childModel.setLocalTranslation(2, 2, 2);

+        childModel.attachChild(childGeom);

+        model.attachChild(childModel);

+        

+        //animation parameters

+        float animTime = 5;

+        int fps = 25;

+        float totalXLength = 10;

+        

+        //calculating frames

+        int totalFrames = (int) (fps * animTime);

+        float dT = animTime / totalFrames, t = 0;

+        float dX = totalXLength / totalFrames, x = 0;

+        float[] times = new float[totalFrames];

+        Vector3f[] translations = new Vector3f[totalFrames];

+        Quaternion[] rotations = new Quaternion[totalFrames];

+        Vector3f[] scales = new Vector3f[totalFrames];

+		for (int i = 0; i < totalFrames; ++i) {

+        	times[i] = t;

+        	t += dT;

+        	translations[i] = new Vector3f(x, 0, 0);

+        	x += dX;

+        	rotations[i] = Quaternion.IDENTITY;

+        	scales[i] = Vector3f.UNIT_XYZ;

+        }

+        SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales);

+        

+        //creating the animation

+        Animation spatialAnimation = new Animation("anim", animTime);

+        spatialAnimation.setTracks(new SpatialTrack[] { spatialTrack });

+        

+        //create spatial animation control

+        AnimControl control = new AnimControl();

+        HashMap<String, Animation> animations = new HashMap<String, Animation>();

+        animations.put("anim", spatialAnimation);

+        control.setAnimations(animations);

+        model.addControl(control);

+

+        rootNode.attachChild(model);

+        

+        //run animation

+        control.createChannel().setAnim("anim");

+    }

+}

diff --git a/engine/src/test/jme3test/model/shape/TestBillboard.java b/engine/src/test/jme3test/model/shape/TestBillboard.java
new file mode 100644
index 0000000..dc5be7f
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestBillboard.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.BillboardControl;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Quad;
+
+/**
+ *
+ * @author Kirill Vainer
+ */
+public class TestBillboard extends SimpleApplication {
+
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(10);
+
+        Quad q = new Quad(2, 2);
+        Geometry g = new Geometry("Quad", q);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        g.setMaterial(mat);
+
+        Quad q2 = new Quad(1, 1);
+        Geometry g3 = new Geometry("Quad2", q2);
+        Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setColor("Color", ColorRGBA.Yellow);
+        g3.setMaterial(mat2);
+        g3.setLocalTranslation(.5f, .5f, .01f);
+
+        Box b = new Box(new Vector3f(0, 0, 3), .25f, .5f, .25f);
+        Geometry g2 = new Geometry("Box", b);
+        g2.setMaterial(mat);
+
+        Node bb = new Node("billboard");
+
+        BillboardControl control=new BillboardControl();
+        
+        bb.addControl(control);
+        bb.attachChild(g);
+        bb.attachChild(g3);       
+        
+
+        n=new Node("parent");
+        n.attachChild(g2);
+        n.attachChild(bb);
+        rootNode.attachChild(n);
+
+        n2=new Node("parentParent");
+        n2.setLocalTranslation(Vector3f.UNIT_X.mult(5));
+        n2.attachChild(n);
+
+        rootNode.attachChild(n2);
+
+
+//        rootNode.attachChild(bb);
+//        rootNode.attachChild(g2);
+    }
+ Node n;
+ Node n2;
+    @Override
+    public void simpleUpdate(float tpf) {
+        super.simpleUpdate(tpf);
+        n.rotate(0, tpf, 0);
+        n.move(0.1f*tpf, 0, 0);
+        n2.rotate(0, 0, -tpf);
+    }
+
+
+
+    public static void main(String[] args) {
+        TestBillboard app = new TestBillboard();
+        app.start();
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/model/shape/TestBox.java b/engine/src/test/jme3test/model/shape/TestBox.java
new file mode 100644
index 0000000..606805e
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestBox.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+public class TestBox extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestBox app = new TestBox();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/shape/TestCustomMesh.java b/engine/src/test/jme3test/model/shape/TestCustomMesh.java
new file mode 100644
index 0000000..1ea48fb
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestCustomMesh.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+
+/**
+ * How to create custom meshes by specifying vertices
+ * We render the mesh in three different ways, once with a solid blue color,
+ * once with vertex colors, and once with a wireframe material.
+ * @author KayTrance
+ */
+public class TestCustomMesh extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestCustomMesh app = new TestCustomMesh();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+      
+        Mesh m = new Mesh();
+
+        // Vertex positions in space
+        Vector3f [] vertices = new Vector3f[4];
+        vertices[0] = new Vector3f(0,0,0);
+        vertices[1] = new Vector3f(3,0,0);
+        vertices[2] = new Vector3f(0,3,0);
+        vertices[3] = new Vector3f(3,3,0);
+
+        // Texture coordinates
+        Vector2f [] texCoord = new Vector2f[4];
+        texCoord[0] = new Vector2f(0,0);
+        texCoord[1] = new Vector2f(1,0);
+        texCoord[2] = new Vector2f(0,1);
+        texCoord[3] = new Vector2f(1,1);
+
+        // Indexes. We define the order in which mesh should be constructed
+        int [] indexes = {2,0,1,1,3,2};
+
+        // Setting buffers
+        m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
+        m.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
+        m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
+        m.updateBound();
+
+        // *************************************************************************
+        // First mesh uses one solid color
+        // *************************************************************************
+
+        // Creating a geometry, and apply a single color material to it
+        Geometry geom = new Geometry("OurMesh", m);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        geom.setMaterial(mat);
+
+        // Attaching our geometry to the root node.
+        rootNode.attachChild(geom);
+
+        // *************************************************************************
+        // Second mesh uses vertex colors to color each vertex
+        // *************************************************************************
+        Mesh cMesh = m.clone();
+        Geometry coloredMesh = new Geometry ("ColoredMesh", cMesh);
+        Material matVC = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matVC.setBoolean("VertexColor", true);
+
+        //We have 4 vertices and 4 color values for each of them.
+        //If you have more vertices, you need 'new float[yourVertexCount * 4]' here!
+        float[] colorArray = new float[4*4];
+        int colorIndex = 0;
+
+        //Set custom RGBA value for each Vertex. Values range from 0.0f to 1.0f
+        for(int i = 0; i < 4; i++){
+           // Red value (is increased by .2 on each next vertex here)
+           colorArray[colorIndex++]= 0.1f+(.2f*i);
+           // Green value (is reduced by .2 on each next vertex)
+           colorArray[colorIndex++]= 0.9f-(0.2f*i);
+           // Blue value (remains the same in our case)
+           colorArray[colorIndex++]= 0.5f;
+           // Alpha value (no transparency set here)
+           colorArray[colorIndex++]= 1.0f;
+        }
+        // Set the color buffer
+        cMesh.setBuffer(Type.Color, 4, colorArray);
+        coloredMesh.setMaterial(matVC);
+        // move mesh a bit so that it doesn't intersect with the first one
+        coloredMesh.setLocalTranslation(4, 0, 0);
+        rootNode.attachChild(coloredMesh);
+
+//        /** Alternatively, you can show the mesh vertixes as points
+//          * instead of coloring the faces. */
+//        cMesh.setMode(Mesh.Mode.Points);
+//        cMesh.setPointSize(10f);
+//        cMesh.updateBound();
+//        cMesh.setStatic();
+//        Geometry points = new Geometry("Points", m);
+//        points.setMaterial(mat);
+//        rootNode.attachChild(points);
+
+        // *************************************************************************
+        // Third mesh will use a wireframe shader to show wireframe
+        // *************************************************************************
+        Mesh wfMesh = m.clone();
+        Geometry wfGeom = new Geometry("wireframeGeometry", wfMesh);
+        Material matWireframe = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWireframe.setColor("Color", ColorRGBA.Green);
+        matWireframe.getAdditionalRenderState().setWireframe(true);
+        wfGeom.setMaterial(matWireframe);
+        wfGeom.setLocalTranslation(4, 4, 0);
+        rootNode.attachChild(wfGeom);
+        
+    }
+}
diff --git a/engine/src/test/jme3test/model/shape/TestCylinder.java b/engine/src/test/jme3test/model/shape/TestCylinder.java
new file mode 100644
index 0000000..4765dd7
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestCylinder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Cylinder;
+import com.jme3.texture.Texture;
+
+public class TestCylinder extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestCylinder app = new TestCylinder();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Cylinder t = new Cylinder(20, 50, 1, 2, true);
+        Geometry geom = new Geometry("Cylinder", t);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Interface/Logo/Monkey.jpg", true);
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        tex.setMinFilter(Texture.MinFilter.Trilinear);
+        mat.setTexture("ColorMap", tex);
+
+        geom.setMaterial(mat);
+        
+        rootNode.attachChild(geom);
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/shape/TestDebugShapes.java b/engine/src/test/jme3test/model/shape/TestDebugShapes.java
new file mode 100644
index 0000000..7c34ecf
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestDebugShapes.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.debug.Grid;
+import com.jme3.scene.debug.WireBox;
+import com.jme3.scene.debug.WireSphere;
+
+public class TestDebugShapes extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestDebugShapes app = new TestDebugShapes();
+        app.start();
+    }
+
+    public Geometry putShape(Mesh shape, ColorRGBA color){
+        Geometry g = new Geometry("shape", shape);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.setColor("Color", color);
+        g.setMaterial(mat);
+        rootNode.attachChild(g);
+        return g;
+    }
+
+    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);
+    }
+
+    public void putBox(Vector3f pos, float size, ColorRGBA color){
+        putShape(new WireBox(size, size, size), color).setLocalTranslation(pos);
+    }
+
+    public void putGrid(Vector3f pos, ColorRGBA color){
+        putShape(new Grid(6, 6, 0.2f), color).center().move(pos);
+    }
+
+    public void putSphere(Vector3f pos, ColorRGBA color){
+        putShape(new WireSphere(1), color).setLocalTranslation(pos);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(2,1.5f,2));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+
+        putArrow(Vector3f.ZERO, Vector3f.UNIT_X, ColorRGBA.Red);
+        putArrow(Vector3f.ZERO, Vector3f.UNIT_Y, ColorRGBA.Green);
+        putArrow(Vector3f.ZERO, Vector3f.UNIT_Z, ColorRGBA.Blue);
+
+        putBox(new Vector3f(2, 0, 0), 0.5f, ColorRGBA.Yellow);
+        putGrid(new Vector3f(3.5f, 0, 0), ColorRGBA.White);
+        putSphere(new Vector3f(4.5f, 0, 0), ColorRGBA.Magenta);
+    }
+
+}
diff --git a/engine/src/test/jme3test/model/shape/TestSphere.java b/engine/src/test/jme3test/model/shape/TestSphere.java
new file mode 100644
index 0000000..77e9139
--- /dev/null
+++ b/engine/src/test/jme3test/model/shape/TestSphere.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 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.model.shape;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Sphere;
+
+public class TestSphere extends SimpleApplication  {
+
+    public static void main(String[] args){
+        TestSphere app = new TestSphere();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Sphere sphMesh = new Sphere(14, 14, 1);
+        Material solidColor = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+
+        for (int y = -5; y < 5; y++){
+            for (int x = -5; x < 5; x++){
+                Geometry sphere = new Geometry("sphere", sphMesh);
+                sphere.setMaterial(solidColor);
+                sphere.setLocalTranslation(x * 2, 0, y * 2);
+                rootNode.attachChild(sphere);
+            }
+        }
+        cam.setLocation(new Vector3f(0, 5, 0));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+    }
+
+}
diff --git a/engine/src/test/jme3test/network/MovingAverage.java b/engine/src/test/jme3test/network/MovingAverage.java
new file mode 100644
index 0000000..0fed2d5
--- /dev/null
+++ b/engine/src/test/jme3test/network/MovingAverage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+@Deprecated
+public class MovingAverage {
+
+    private long[] samples;
+    private long sum;
+    private int count, index;
+
+    public MovingAverage(int numSamples){
+        samples = new long[numSamples];
+    }
+
+    public void add(long sample){
+        sum = sum - samples[index] + sample;
+        samples[index++] = sample;
+        if (index > count){
+            count = index;
+        }
+        if (index >= samples.length){
+            index = 0;
+        }
+    }
+
+    public long getAverage(){
+        if (count == 0)
+            return 0;
+        else
+            return (long) ((float) sum / (float) count);
+    }
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/network/TestChatClient.java b/engine/src/test/jme3test/network/TestChatClient.java
new file mode 100644
index 0000000..2300c40
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestChatClient.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.Client;
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import com.jme3.network.Network;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import javax.swing.*;
+import jme3test.network.TestChatServer.ChatMessage;
+
+/**
+ *  A simple test chat server.  When SM implements a set
+ *  of standard chat classes this can become a lot simpler.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class TestChatClient extends JFrame {
+
+    private Client client;
+    private JEditorPane chatLog;
+    private StringBuilder chatMessages = new StringBuilder();
+    private JTextField nameField;
+    private JTextField messageField;
+
+    public TestChatClient(String host) throws IOException {
+        super("jME3 Test Chat Client - to:" + host);
+
+        // Build out the UI       
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        setSize(800, 600);
+
+        chatLog = new JEditorPane();
+        chatLog.setEditable(false);
+        chatLog.setContentType("text/html");
+        chatLog.setText("<html><body>");
+
+        getContentPane().add(new JScrollPane(chatLog), "Center");
+
+        // A crude form       
+        JPanel p = new JPanel();
+        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
+        p.add(new JLabel("Name:"));
+        nameField = new JTextField(System.getProperty("user.name", "yourname"));
+        Dimension d = nameField.getPreferredSize();
+        nameField.setMaximumSize(new Dimension(120, d.height + 6));
+        p.add(nameField);
+        p.add(new JLabel("  Message:"));
+        messageField = new JTextField();
+        p.add(messageField);
+        p.add(new JButton(new SendAction(true)));
+        p.add(new JButton(new SendAction(false)));
+
+        getContentPane().add(p, "South");
+
+        client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION,
+                host, TestChatServer.PORT, TestChatServer.UDP_PORT);
+        client.addMessageListener(new ChatHandler(), ChatMessage.class);
+        client.start();
+    }
+
+    public static String getString(Component owner, String title, String message, String initialValue) {
+        return (String) JOptionPane.showInputDialog(owner, message, title, JOptionPane.PLAIN_MESSAGE,
+                null, null, initialValue);
+    }
+
+    public static void main(String... args) throws Exception {
+        TestChatServer.initializeClasses();
+
+        // Grab a host string from the user
+        String s = getString(null, "Host Info", "Enter chat host:", "localhost");
+        if (s == null) {
+            System.out.println("User cancelled.");
+            return;
+        }
+
+        TestChatClient test = new TestChatClient(s);
+        test.setVisible(true);
+    }
+
+    private class ChatHandler implements MessageListener<Client> {
+
+        public void messageReceived(Client source, Message m) {
+            ChatMessage chat = (ChatMessage) m;
+
+            System.out.println("Received:" + chat);
+
+            // One of the least efficient ways to add text to a
+            // JEditorPane
+            chatMessages.append("<font color='#00a000'>" + (m.isReliable() ? "TCP" : "UDP") + "</font>");
+            chatMessages.append(" -- <font color='#000080'><b>" + chat.getName() + "</b></font> : ");
+            chatMessages.append(chat.getMessage());
+            chatMessages.append("<br />");
+            String s = "<html><body>" + chatMessages + "</body></html>";
+            chatLog.setText(s);
+
+            // Set selection to the end so that the scroll panel will scroll
+            // down.
+            chatLog.select(s.length(), s.length());
+        }
+    }
+
+    private class SendAction extends AbstractAction {
+
+        private boolean reliable;
+
+        public SendAction(boolean reliable) {
+            super(reliable ? "TCP" : "UDP");
+            this.reliable = reliable;
+        }
+
+        public void actionPerformed(ActionEvent evt) {
+            String name = nameField.getText();
+            String message = messageField.getText();
+
+            ChatMessage chat = new ChatMessage(name, message);
+            chat.setReliable(reliable);
+            System.out.println("Sending:" + chat);
+            client.send(chat);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/network/TestChatServer.java b/engine/src/test/jme3test/network/TestChatServer.java
new file mode 100644
index 0000000..7d37c0c
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestChatServer.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+
+/**
+ *  A simple test chat server.  When SM implements a set
+ *  of standard chat classes this can become a lot simpler.
+ *
+ *  @version   $Revision: 8843 $
+ *  @author    Paul Speed
+ */
+public class TestChatServer {
+    // Normally these and the initialized method would
+    // be in shared constants or something.
+
+    public static final String NAME = "Test Chat Server";
+    public static final int VERSION = 1;
+    public static final int PORT = 5110;
+    public static final int UDP_PORT = 5110;
+
+    public static void initializeClasses() {
+        // Doing it here means that the client code only needs to
+        // call our initialize. 
+        Serializer.registerClass(ChatMessage.class);
+    }
+
+    public static void main(String... args) throws Exception {
+        initializeClasses();
+
+        // Use this to test the client/server name version check
+        Server server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
+        server.start();
+
+        ChatHandler handler = new ChatHandler();
+        server.addMessageListener(handler, ChatMessage.class);
+
+        // Keep running basically forever
+        synchronized (NAME) {
+            NAME.wait();
+        }
+    }
+
+    private static class ChatHandler implements MessageListener<HostedConnection> {
+
+        public ChatHandler() {
+        }
+
+        public void messageReceived(HostedConnection source, Message m) {
+            if (m instanceof ChatMessage) {
+                // Keep track of the name just in case we 
+                // want to know it for some other reason later and it's
+                // a good example of session data
+                source.setAttribute("name", ((ChatMessage) m).getName());
+
+                System.out.println("Broadcasting:" + m + "  reliable:" + m.isReliable());
+
+                // Just rebroadcast... the reliable flag will stay the
+                // same so if it came in on UDP it will go out on that too
+                source.getServer().broadcast(m);
+            } else {
+                System.err.println("Received odd message:" + m);
+            }
+        }
+    }
+
+    @Serializable
+    public static class ChatMessage extends AbstractMessage {
+
+        private String name;
+        private String message;
+
+        public ChatMessage() {
+        }
+
+        public ChatMessage(String name, String message) {
+            setName(name);
+            setMessage(message);
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setMessage(String s) {
+            this.message = s;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public String toString() {
+            return name + ":" + message;
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/network/TestLatency.java b/engine/src/test/jme3test/network/TestLatency.java
new file mode 100644
index 0000000..91b04c4
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestLatency.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+
+public class TestLatency {
+
+    private static long startTime;
+    private static Client client;
+    private static MovingAverage average = new MovingAverage(100);
+
+    static {
+        startTime = System.currentTimeMillis();
+    }
+
+    private static long getTime(){
+        return System.currentTimeMillis() - startTime;
+    }
+
+    @Serializable
+    public static class TimestampMessage extends AbstractMessage {
+
+        long timeSent     = 0;
+        long timeReceived = 0;
+
+        public TimestampMessage(){
+            setReliable(false);
+        }
+
+        public TimestampMessage(long timeSent, long timeReceived){
+            setReliable(false);
+            this.timeSent = timeSent;
+            this.timeReceived = timeReceived;
+        }
+
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException{
+        Serializer.registerClass(TimestampMessage.class);
+
+        Server server = Network.createServer(5110);
+        server.start();
+
+        client = Network.connectToServer("localhost", 5110);
+        client.start();
+        
+        client.addMessageListener(new MessageListener<Client>(){
+            public void messageReceived(Client source, Message m) {
+                TimestampMessage timeMsg = (TimestampMessage) m;
+
+                long curTime = getTime();
+                //System.out.println("Time sent: " + timeMsg.timeSent);
+                //System.out.println("Time received by server: " + timeMsg.timeReceived);
+                //System.out.println("Time recieved by client: " + curTime);
+
+                long latency = (curTime - timeMsg.timeSent);
+                System.out.println("Latency: " + (latency) + " ms");
+                //long timeOffset = ((timeMsg.timeSent + curTime) / 2) - timeMsg.timeReceived;
+                //System.out.println("Approximate timeoffset: "+ (timeOffset) + " ms");
+
+                average.add(latency);
+                System.out.println("Average latency: " + average.getAverage());
+
+                long latencyOffset = latency - average.getAverage();
+                System.out.println("Latency offset: " + latencyOffset);
+
+                client.send(new TimestampMessage(getTime(), 0));
+            }
+        }, TimestampMessage.class);
+
+        server.addMessageListener(new MessageListener<HostedConnection>(){
+            public void messageReceived(HostedConnection source, Message m) {
+                TimestampMessage timeMsg = (TimestampMessage) m;
+                TimestampMessage outMsg = new TimestampMessage(timeMsg.timeSent, getTime());
+                source.send(outMsg);
+            }
+        }, TimestampMessage.class);
+
+        Thread.sleep(1);
+
+        client.send(new TimestampMessage(getTime(), 0));
+        
+        Object obj = new Object();
+        synchronized(obj){
+            obj.wait();
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/network/TestMessages.java b/engine/src/test/jme3test/network/TestMessages.java
new file mode 100644
index 0000000..b99e32b
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestMessages.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+
+public class TestMessages {
+
+    @Serializable
+    public static class PingMessage extends AbstractMessage {
+    }
+
+    @Serializable
+    public static class PongMessage extends AbstractMessage {
+    }
+
+    private static class ServerPingResponder implements MessageListener<HostedConnection> {
+        public void messageReceived(HostedConnection source, com.jme3.network.Message message) {
+            if (message instanceof PingMessage){
+                System.out.println("Server: Received ping message!");
+                source.send(new PongMessage());
+            }
+        }
+    }
+
+    private static class ClientPingResponder implements MessageListener<Client> {
+        public void messageReceived(Client source, com.jme3.network.Message message) {
+            if (message instanceof PongMessage){
+                System.out.println("Client: Received pong message!");
+            }
+        }
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException{
+        Serializer.registerClass(PingMessage.class);
+        Serializer.registerClass(PongMessage.class);
+
+        Server server = Network.createServer(5110);
+        server.start();
+
+        Client client = Network.connectToServer("localhost", 5110);
+        client.start();
+
+        server.addMessageListener(new ServerPingResponder(), PingMessage.class);
+        client.addMessageListener(new ClientPingResponder(), PongMessage.class);
+
+        System.out.println("Client: Sending ping message..");
+        client.send(new PingMessage());
+        
+        Object obj = new Object();
+        synchronized (obj){
+            obj.wait();
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/network/TestNetworkStress.java b/engine/src/test/jme3test/network/TestNetworkStress.java
new file mode 100644
index 0000000..ba8893c
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestNetworkStress.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+import com.jme3.network.*;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestNetworkStress implements ConnectionListener {
+    
+    public void connectionAdded(Server server, HostedConnection conn) {
+        System.out.println("Client Connected: "+conn.getId());
+        //conn.close("goodbye");
+    }
+
+    public void connectionRemoved(Server server, HostedConnection conn) {
+    }
+    
+    public static void main(String[] args) throws IOException, InterruptedException{
+        Logger.getLogger("").getHandlers()[0].setLevel(Level.OFF);
+        
+        Server server = Network.createServer(5110);
+        server.start();
+        server.addConnectionListener(new TestNetworkStress());
+
+        for (int i = 0; i < 1000; i++){
+            Client client = Network.connectToServer("localhost", 5110);
+            client.start();
+
+            Thread.sleep(10);
+            
+            client.close();
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/network/TestRemoteCall.java b/engine/src/test/jme3test/network/TestRemoteCall.java
new file mode 100644
index 0000000..cd1d38e
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestRemoteCall.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.export.Savable;
+import com.jme3.network.Client;
+import com.jme3.network.Network;
+import com.jme3.network.Server;
+import com.jme3.network.rmi.ObjectStore;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.serializers.SavableSerializer;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+public class TestRemoteCall {
+
+    private static SimpleApplication serverApp;
+
+    /**
+     * Interface implemented by the server, exposing
+     * RMI calls that clients can use.
+     */
+    public static interface ServerAccess {
+        /**
+         * Attaches the model with the given name to the server's scene.
+         * 
+         * @param model The model name
+         * 
+         * @return True if the model was attached.
+         * 
+         * @throws RuntimeException If some error occurs.
+         */
+        public boolean attachChild(String model);
+    }
+
+    public static class ServerAccessImpl implements ServerAccess {
+        public boolean attachChild(String model) {
+            if (model == null)
+                throw new RuntimeException("Cannot be null. .. etc");
+
+            final String finalModel = model;
+            serverApp.enqueue(new Callable<Void>() {
+                public Void call() throws Exception {
+                    Spatial spatial = serverApp.getAssetManager().loadModel(finalModel);
+                    serverApp.getRootNode().attachChild(spatial);
+                    return null;
+                }
+            });
+            return true;
+        }
+    }
+
+    public static void createServer(){
+        serverApp = new SimpleApplication() {
+            @Override
+            public void simpleInitApp() {
+            }
+        };
+        serverApp.start();
+
+        try {
+            Server server = Network.createServer(5110);
+            server.start();
+
+            ObjectStore store = new ObjectStore(server);
+            store.exposeObject("access", new ServerAccessImpl());
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException{
+        Serializer.registerClass(Savable.class, new SavableSerializer());
+
+        createServer();
+
+        Client client = Network.connectToServer("localhost", 5110);
+        client.start();
+
+        ObjectStore store = new ObjectStore(client);
+        ServerAccess access = store.getExposedObject("access", ServerAccess.class, true);
+        boolean result = access.attachChild("Models/Oto/Oto.mesh.xml");
+        System.out.println(result);
+    }
+}
diff --git a/engine/src/test/jme3test/network/TestSerialization.java b/engine/src/test/jme3test/network/TestSerialization.java
new file mode 100644
index 0000000..07854ce
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestSerialization.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009-2010 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.network;
+
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+import java.util.*;
+
+public class TestSerialization implements MessageListener<HostedConnection> {
+
+    @Serializable
+    public static class SomeObject {
+
+        private int val;
+
+        public SomeObject(){
+        }
+
+        public SomeObject(int val){
+            this.val = val;
+        }
+
+        public int getVal(){
+            return val;
+        }
+
+        @Override
+        public String toString(){
+            return "SomeObject[val="+val+"]";
+        }
+    }
+
+    public enum Status {
+        High,
+        Middle,
+        Low;
+    }
+
+    @Serializable
+    public static class TestSerializationMessage extends AbstractMessage {
+
+        boolean z;
+        byte b;
+        char c;
+        short s;
+        int i;
+        float f;
+        long l;
+        double d;
+        
+        int[] ia;
+        List<Object> ls;
+        Map<String, SomeObject> mp;
+
+        Status status1;
+        Status status2;
+
+        Date date;
+
+        public TestSerializationMessage(){
+            super(true);
+        }
+
+        public TestSerializationMessage(boolean initIt){
+            super(true);
+            if (initIt){
+                z = true;
+                b = -88;
+                c = 'Y';
+                s = 9999;
+                i = 123;
+                f = -75.4e8f;
+                l = 9438345072805034L;
+                d = -854834.914703e88;
+                ia = new int[]{ 456, 678, 999 };
+
+                ls = new ArrayList<Object>();
+                ls.add("hello");
+                ls.add(new SomeObject(-22));
+
+                mp = new HashMap<String, SomeObject>();
+                mp.put("abc", new SomeObject(555));
+
+                status1 = Status.High;
+                status2 = Status.Middle;
+
+                date = new Date(System.currentTimeMillis());
+            }
+        }
+    }
+
+    public void messageReceived(HostedConnection source, Message m) {
+        TestSerializationMessage cm = (TestSerializationMessage) m;
+        System.out.println(cm.z);
+        System.out.println(cm.b);
+        System.out.println(cm.c);
+        System.out.println(cm.s);
+        System.out.println(cm.i);
+        System.out.println(cm.f);
+        System.out.println(cm.l);
+        System.out.println(cm.d);
+        System.out.println(Arrays.toString(cm.ia));
+        System.out.println(cm.ls);
+        System.out.println(cm.mp);
+        System.out.println(cm.status1);
+        System.out.println(cm.status2);
+        System.out.println(cm.date);
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException{
+        Serializer.registerClass(SomeObject.class);
+        Serializer.registerClass(TestSerializationMessage.class);
+
+        Server server = Network.createServer( 5110 );
+        server.start();
+
+        Client client = Network.connectToServer( "localhost", 5110 ); 
+        client.start();
+
+        server.addMessageListener(new TestSerialization(), TestSerializationMessage.class);
+        client.send(new TestSerializationMessage(true));
+        
+        Thread.sleep(10000);
+    }
+
+}
diff --git a/engine/src/test/jme3test/network/TestThroughput.java b/engine/src/test/jme3test/network/TestThroughput.java
new file mode 100644
index 0000000..c261ec0
--- /dev/null
+++ b/engine/src/test/jme3test/network/TestThroughput.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2011 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.network;
+
+import com.jme3.network.*;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+import java.io.IOException;
+
+public class TestThroughput implements MessageListener<MessageConnection> { //extends MessageAdapter {
+
+    private static long lastTime = -1;
+    private static long counter = 0;
+    private static long total = 0;
+    // Change this flag to test UDP instead of TCP
+    private static boolean testReliable = false;
+    private boolean isOnServer;
+
+    public TestThroughput(boolean isOnServer) {
+        this.isOnServer = isOnServer;
+    }
+    
+    @Serializable
+    public static class TestMessage extends AbstractMessage {
+
+        public TestMessage() {
+            setReliable(testReliable);
+        }
+    }
+
+    @Override
+    public void messageReceived(MessageConnection source, Message msg) {
+
+        if (!isOnServer) {
+            // It's local to the client so we got it back
+            counter++;
+            total++;
+            long time = System.currentTimeMillis();
+//System.out.println( "total:" + total + "  counter:" + counter + "  lastTime:" + lastTime + "  time:" + time );
+            if (lastTime < 0) {
+                lastTime = time;
+            } else if (time - lastTime > 1000) {
+                long delta = time - lastTime;
+                double scale = delta / 1000.0;
+                double pps = counter / scale;
+                System.out.println("messages per second:" + pps + "  total messages:" + total);
+                counter = 0;
+                lastTime = time;
+            }
+        } else {
+            if (source == null) {
+                System.out.println("Received a message from a not fully connected source, msg:" + msg);
+            } else {
+//System.out.println( "sending:" + msg + " back to client:" + source );
+                // The 'reliable' flag is transient and the server doesn't
+                // (yet) reset this value for us.
+                ((com.jme3.network.Message) msg).setReliable(testReliable);
+                source.send(msg);
+            }
+        }
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+
+        Serializer.registerClass(TestMessage.class);
+
+        // Use this to test the client/server name version check
+        //Server server = Network.createServer( "bad name", 42, 5110, 5110 );
+        Server server = Network.createServer(5110, 5110);
+        server.start();
+
+        Client client = Network.connectToServer("localhost", 5110);
+        client.start();
+
+        client.addMessageListener(new TestThroughput(false), TestMessage.class);
+        server.addMessageListener(new TestThroughput(true), TestMessage.class);
+
+        Thread.sleep(1);
+
+        TestMessage test = new TestMessage();
+//        for( int i = 0; i < 10; i++ ) {
+        while (true) {
+//System.out.println( "sending." );
+            client.send(test);
+        }
+
+        //Thread.sleep(5000);
+    }
+}
diff --git a/engine/src/test/jme3test/niftygui/TestNiftyExamples.java b/engine/src/test/jme3test/niftygui/TestNiftyExamples.java
new file mode 100644
index 0000000..e6a114c
--- /dev/null
+++ b/engine/src/test/jme3test/niftygui/TestNiftyExamples.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.niftygui.NiftyJmeDisplay;
+import de.lessvoid.nifty.Nifty;
+
+public class TestNiftyExamples extends SimpleApplication {
+
+    private Nifty nifty;
+
+    public static void main(String[] args){
+        TestNiftyExamples app = new TestNiftyExamples();
+        app.setPauseOnLostFocus(false);
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
+                                                          inputManager,
+                                                          audioRenderer,
+                                                          guiViewPort);
+        nifty = niftyDisplay.getNifty();
+
+        nifty.fromXml("all/intro.xml", "start");
+
+        // attach the nifty display to the gui view port as a processor
+        guiViewPort.addProcessor(niftyDisplay);
+
+        // disable the fly cam
+        flyCam.setEnabled(false);
+    }
+
+}
diff --git a/engine/src/test/jme3test/niftygui/TestNiftyGui.java b/engine/src/test/jme3test/niftygui/TestNiftyGui.java
new file mode 100644
index 0000000..5a1ea1c
--- /dev/null
+++ b/engine/src/test/jme3test/niftygui/TestNiftyGui.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.niftygui.NiftyJmeDisplay;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.screen.Screen;
+import de.lessvoid.nifty.screen.ScreenController;
+
+public class TestNiftyGui extends SimpleApplication implements ScreenController {
+
+    private Nifty nifty;
+
+    public static void main(String[] args){
+        TestNiftyGui app = new TestNiftyGui();
+        app.setPauseOnLostFocus(false);
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+
+        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
+                                                          inputManager,
+                                                          audioRenderer,
+                                                          guiViewPort);
+        nifty = niftyDisplay.getNifty();
+        nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this);
+
+        // attach the nifty display to the gui view port as a processor
+        guiViewPort.addProcessor(niftyDisplay);
+
+        // disable the fly cam
+//        flyCam.setEnabled(false);
+//        flyCam.setDragToRotate(true);
+        inputManager.setCursorVisible(true);
+    }
+
+    public void bind(Nifty nifty, Screen screen) {
+        System.out.println("bind( " + screen.getScreenId() + ")");
+    }
+
+    public void onStartScreen() {
+        System.out.println("onStartScreen");
+    }
+
+    public void onEndScreen() {
+        System.out.println("onEndScreen");
+    }
+
+    public void quit(){
+        nifty.gotoScreen("end");
+    }
+
+}
diff --git a/engine/src/test/jme3test/niftygui/TestNiftyToMesh.java b/engine/src/test/jme3test/niftygui/TestNiftyToMesh.java
new file mode 100644
index 0000000..2aacbc9
--- /dev/null
+++ b/engine/src/test/jme3test/niftygui/TestNiftyToMesh.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.niftygui;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.niftygui.NiftyJmeDisplay;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import de.lessvoid.nifty.Nifty;
+
+public class TestNiftyToMesh extends SimpleApplication{
+
+    private Nifty nifty;
+
+    public static void main(String[] args){
+        TestNiftyToMesh app = new TestNiftyToMesh();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+       ViewPort niftyView = renderManager.createPreView("NiftyView", new Camera(1024, 768));
+       niftyView.setClearFlags(true, true, true);
+        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
+                                                          inputManager,
+                                                          audioRenderer,
+                                                          niftyView);
+        nifty = niftyDisplay.getNifty();
+        nifty.fromXml("all/intro.xml", "start");
+        niftyView.addProcessor(niftyDisplay);
+
+        Texture2D depthTex = new Texture2D(1024, 768, Format.Depth);
+        FrameBuffer fb = new FrameBuffer(1024, 768, 1);
+        fb.setDepthTexture(depthTex);
+
+        Texture2D tex = new Texture2D(1024, 768, Format.RGBA8);
+        tex.setMinFilter(MinFilter.Trilinear);
+        tex.setMagFilter(MagFilter.Bilinear);
+
+        fb.setColorTexture(tex);
+        niftyView.setClearFlags(true, true, true);
+        niftyView.setOutputFrameBuffer(fb);
+
+        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", tex);
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+    }
+}
diff --git a/engine/src/test/jme3test/post/BloomUI.java b/engine/src/test/jme3test/post/BloomUI.java
new file mode 100644
index 0000000..5029732
--- /dev/null
+++ b/engine/src/test/jme3test/post/BloomUI.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.post.filters.BloomFilter;
+
+/**
+ *
+ * @author nehon
+ */
+public class BloomUI {
+
+    public BloomUI(InputManager inputManager, final BloomFilter filter) {
+
+        System.out.println("----------------- Bloom UI Debugger --------------------");
+        System.out.println("-- blur Scale : press Y to increase, H to decrease");
+        System.out.println("-- exposure Power : press U to increase, J to decrease");
+        System.out.println("-- exposure CutOff : press I to increase, K to decrease");
+        System.out.println("-- bloom Intensity : press O to increase, P to decrease");
+        System.out.println("-------------------------------------------------------");
+
+        inputManager.addMapping("blurScaleUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("blurScaleDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("exposurePowerUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("exposurePowerDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("exposureCutOffUp", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("exposureCutOffDown", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("bloomIntensityUp", new KeyTrigger(KeyInput.KEY_O));
+        inputManager.addMapping("bloomIntensityDown", new KeyTrigger(KeyInput.KEY_L));
+
+
+        AnalogListener anl = new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("blurScaleUp")) {
+                    filter.setBlurScale(filter.getBlurScale() + 0.01f);
+                    System.out.println("blurScale : " + filter.getBlurScale());
+                }
+                if (name.equals("blurScaleDown")) {
+                    filter.setBlurScale(filter.getBlurScale() - 0.01f);
+                    System.out.println("blurScale : " + filter.getBlurScale());
+                }
+                if (name.equals("exposurePowerUp")) {
+                    filter.setExposurePower(filter.getExposurePower() + 0.01f);
+                    System.out.println("exposurePower : " + filter.getExposurePower());
+                }
+                if (name.equals("exposurePowerDown")) {
+                    filter.setExposurePower(filter.getExposurePower() - 0.01f);
+                    System.out.println("exposurePower : " + filter.getExposurePower());
+                }
+                if (name.equals("exposureCutOffUp")) {
+                    filter.setExposureCutOff(Math.min(1.0f, Math.max(0.0f, filter.getExposureCutOff() + 0.001f)));
+                    System.out.println("exposure CutOff : " + filter.getExposureCutOff());
+                }
+                if (name.equals("exposureCutOffDown")) {
+                    filter.setExposureCutOff(Math.min(1.0f, Math.max(0.0f, filter.getExposureCutOff() - 0.001f)));
+                    System.out.println("exposure CutOff : " + filter.getExposureCutOff());
+                }
+                if (name.equals("bloomIntensityUp")) {
+                    filter.setBloomIntensity(filter.getBloomIntensity() + 0.01f);
+                    System.out.println("bloom Intensity : " + filter.getBloomIntensity());
+                }
+                if (name.equals("bloomIntensityDown")) {
+                    filter.setBloomIntensity(filter.getBloomIntensity() - 0.01f);
+                    System.out.println("bloom Intensity : " + filter.getBloomIntensity());
+                }
+
+
+            }
+        };
+
+        inputManager.addListener(anl, "blurScaleUp", "blurScaleDown", "exposurePowerUp", "exposurePowerDown",
+                "exposureCutOffUp", "exposureCutOffDown", "bloomIntensityUp", "bloomIntensityDown");
+
+    }
+}
diff --git a/engine/src/test/jme3test/post/LightScatteringUI.java b/engine/src/test/jme3test/post/LightScatteringUI.java
new file mode 100644
index 0000000..22cc8c7
--- /dev/null
+++ b/engine/src/test/jme3test/post/LightScatteringUI.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.post.filters.LightScatteringFilter;
+
+/**
+ *
+ * @author nehon
+ */
+public class LightScatteringUI {
+    private LightScatteringFilter filter;
+    public LightScatteringUI(InputManager inputManager, LightScatteringFilter proc) {
+        filter=proc;
+
+
+        System.out.println("----------------- LightScattering UI Debugger --------------------");
+        System.out.println("-- Sample number : press Y to increase, H to decrease");
+        System.out.println("-- blur start : press U to increase, J to decrease");
+        System.out.println("-- blur width : press I to increase, K to decrease");
+        System.out.println("-- Light density : press O to increase, P to decrease");
+//        System.out.println("-- Toggle AO on/off : press space bar");
+//        System.out.println("-- Use only AO : press Num pad 0");
+//        System.out.println("-- Output config declaration : press P");
+        System.out.println("-------------------------------------------------------");
+    
+        inputManager.addMapping("sampleUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("sampleDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("blurStartUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("blurStartDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("blurWidthUp", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("blurWidthDown", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("lightDensityUp", new KeyTrigger(KeyInput.KEY_O));
+        inputManager.addMapping("lightDensityDown", new KeyTrigger(KeyInput.KEY_L));
+        inputManager.addMapping("outputConfig", new KeyTrigger(KeyInput.KEY_P));
+//        inputManager.addMapping("toggleUseAO", new KeyTrigger(KeyInput.KEY_SPACE));
+//        inputManager.addMapping("toggleUseOnlyAo", new KeyTrigger(KeyInput.KEY_NUMPAD0));
+        
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+
+                if (name.equals("sampleUp")) {
+                    filter.setNbSamples(filter.getNbSamples()+1);
+                    System.out.println("Nb Samples : "+filter.getNbSamples());
+                }
+                if (name.equals("sampleDown")) {
+                   filter.setNbSamples(filter.getNbSamples()-1);
+                   System.out.println("Nb Samples : "+filter.getNbSamples());
+                }
+                if (name.equals("outputConfig") && keyPressed) {
+                   System.out.println("lightScatteringFilter.setNbSamples("+filter.getNbSamples()+");");
+                   System.out.println("lightScatteringFilter.setBlurStart("+filter.getBlurStart()+"f);");
+                   System.out.println("lightScatteringFilter.setBlurWidth("+filter.getBlurWidth()+"f);");
+                   System.out.println("lightScatteringFilter.setLightDensity("+filter.getLightDensity()+"f);");
+                }
+               
+
+            }
+        };
+
+         AnalogListener anl = new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+               
+                if (name.equals("blurStartUp")) {
+                    filter.setBlurStart(filter.getBlurStart()+0.001f);
+                    System.out.println("Blur start : "+filter.getBlurStart());
+                }
+                if (name.equals("blurStartDown")) {
+                    filter.setBlurStart(filter.getBlurStart()-0.001f);
+                    System.out.println("Blur start : "+filter.getBlurStart());
+                }
+                 if (name.equals("blurWidthUp")) {
+                    filter.setBlurWidth(filter.getBlurWidth()+0.001f);
+                    System.out.println("Blur Width : "+filter.getBlurWidth());
+                }
+                if (name.equals("blurWidthDown")) {
+                    filter.setBlurWidth(filter.getBlurWidth()-0.001f);
+                    System.out.println("Blur Width : "+filter.getBlurWidth());
+                }
+                if (name.equals("lightDensityUp")) {
+                    filter.setLightDensity(filter.getLightDensity()+0.001f);
+                    System.out.println("light Density : "+filter.getLightDensity());
+                }
+                if (name.equals("lightDensityDown")) {
+                     filter.setLightDensity(filter.getLightDensity()-0.001f);
+                    System.out.println("light Density : "+filter.getLightDensity());
+                }
+
+            }
+        };
+        inputManager.addListener(acl,"sampleUp","sampleDown","outputConfig");
+
+        inputManager.addListener(anl, "blurStartUp","blurStartDown","blurWidthUp", "blurWidthDown","lightDensityUp", "lightDensityDown");
+     
+    }
+    
+    
+
+}
diff --git a/engine/src/test/jme3test/post/SSAOUI.java b/engine/src/test/jme3test/post/SSAOUI.java
new file mode 100644
index 0000000..21509f5
--- /dev/null
+++ b/engine/src/test/jme3test/post/SSAOUI.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.post.ssao.SSAOFilter;
+
+/**
+ *
+ * @author nehon
+ */
+public class SSAOUI {
+
+    SSAOFilter filter;
+
+    public SSAOUI(InputManager inputManager, SSAOFilter filter) {
+        this.filter = filter;
+        init(inputManager);
+    }
+
+    private void init(InputManager inputManager) {
+        System.out.println("----------------- Water UI Debugger --------------------");
+        System.out.println("-- Sample Radius : press Y to increase, H to decrease");
+        System.out.println("-- AO Intensity : press U to increase, J to decrease");
+        System.out.println("-- AO scale : press I to increase, K to decrease");
+        System.out.println("-- AO bias : press O to increase, P to decrease");
+        System.out.println("-- Toggle AO on/off : press space bar");
+        System.out.println("-- Use only AO : press Num pad 0");
+        System.out.println("-- Output config declaration : press P");
+        System.out.println("-------------------------------------------------------");
+
+        inputManager.addMapping("sampleRadiusUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("sampleRadiusDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("intensityUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("intensityDown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("scaleUp", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("scaleDown", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("biasUp", new KeyTrigger(KeyInput.KEY_O));
+        inputManager.addMapping("biasDown", new KeyTrigger(KeyInput.KEY_L));
+        inputManager.addMapping("outputConfig", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addMapping("toggleUseAO", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("toggleUseOnlyAo", new KeyTrigger(KeyInput.KEY_NUMPAD0));
+
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+
+                if (name.equals("toggleUseAO") && keyPressed) {
+                    filter.setEnabled(!filter.isEnabled());
+                    // filter.setUseAo(!filter.isUseAo());
+                    System.out.println("use AO : " + filter.isEnabled());
+                }
+                if (name.equals("toggleUseOnlyAo") && keyPressed) {
+                    filter.setUseOnlyAo(!filter.isUseOnlyAo());
+                    System.out.println("use Only AO : " + filter.isUseOnlyAo());
+
+                }
+                if (name.equals("outputConfig") && keyPressed) {
+                    System.out.println("new SSAOFilter(" + filter.getSampleRadius() + "f," + filter.getIntensity() + "f," + filter.getScale() + "f," + filter.getBias() + "f);");
+                }
+            }
+        };
+
+        AnalogListener anl = new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("sampleRadiusUp")) {
+                    filter.setSampleRadius(filter.getSampleRadius() + 0.01f);
+                    System.out.println("Sample Radius : " + filter.getSampleRadius());
+                }
+                if (name.equals("sampleRadiusDown")) {
+                    filter.setSampleRadius(filter.getSampleRadius() - 0.01f);
+                    System.out.println("Sample Radius : " + filter.getSampleRadius());
+                }
+                if (name.equals("intensityUp")) {
+                    filter.setIntensity(filter.getIntensity() + 0.01f);
+                    System.out.println("Intensity : " + filter.getIntensity());
+                }
+                if (name.equals("intensityDown")) {
+                    filter.setIntensity(filter.getIntensity() - 0.01f);
+                    System.out.println("Intensity : " + filter.getIntensity());
+                }
+                if (name.equals("scaleUp")) {
+                    filter.setScale(filter.getScale() + 0.01f);
+                    System.out.println("scale : " + filter.getScale());
+                }
+                if (name.equals("scaleDown")) {
+                    filter.setScale(filter.getScale() - 0.01f);
+                    System.out.println("scale : " + filter.getScale());
+                }
+                if (name.equals("biasUp")) {
+                    filter.setBias(filter.getBias() + 0.001f);
+                    System.out.println("bias : " + filter.getBias());
+                }
+                if (name.equals("biasDown")) {
+                    filter.setBias(filter.getBias() - 0.001f);
+                    System.out.println("bias : " + filter.getBias());
+                }
+
+            }
+        };
+        inputManager.addListener(acl, "toggleUseAO", "toggleUseOnlyAo", "outputConfig");
+        inputManager.addListener(anl, "sampleRadiusUp", "sampleRadiusDown", "intensityUp", "intensityDown", "scaleUp", "scaleDown",
+                "biasUp", "biasDown");
+
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestBloom.java b/engine/src/test/jme3test/post/TestBloom.java
new file mode 100644
index 0000000..1c371b4
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestBloom.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+public class TestBloom extends SimpleApplication {
+
+    float angle;
+    Spatial lightMdl;
+    Spatial teapot;
+    Geometry frustumMdl;
+    WireFrustum frustum;
+    boolean active=true;
+    FilterPostProcessor fpp;
+    
+    public static void main(String[] args){
+        TestBloom app = new TestBloom();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -7.139601f));
+        cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
+        //cam.setFrustumFar(1000);
+
+
+        Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 15f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
+
+    
+
+
+        Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        matSoil.setFloat("Shininess", 15f);
+        matSoil.setBoolean("UseMaterialColors", true);
+        matSoil.setColor("Ambient", ColorRGBA.Gray);
+        matSoil.setColor("Diffuse", ColorRGBA.Black);
+        matSoil.setColor("Specular", ColorRGBA.Gray);
+       
+
+
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalTranslation(0,0,10);
+
+        teapot.setMaterial(mat);
+        teapot.setShadowMode(ShadowMode.CastAndReceive);
+        teapot.setLocalScale(10.0f);
+        rootNode.attachChild(teapot);
+
+  
+
+        Geometry soil=new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+        soil.setMaterial(matSoil);
+        soil.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(soil);
+
+        DirectionalLight light=new DirectionalLight();
+        light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        light.setColor(ColorRGBA.White.mult(1.5f));
+        rootNode.addLight(light);
+
+        // load sky
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
+        sky.setCullHint(Spatial.CullHint.Never);
+        rootNode.attachChild(sky);
+
+        fpp=new FilterPostProcessor(assetManager);
+       // fpp.setNumSamples(4);
+        BloomFilter bloom=new BloomFilter();
+        bloom.setDownSamplingFactor(2);
+        bloom.setBlurScale(1.37f);
+        bloom.setExposurePower(3.30f);
+        bloom.setExposureCutOff(0.2f);
+        bloom.setBloomIntensity(2.45f);
+        BloomUI ui=new BloomUI(inputManager, bloom);
+   
+
+        viewPort.addProcessor(fpp);
+        fpp.addFilter(bloom);
+        initInputs();
+
+    }
+    
+         private void initInputs() {
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+     
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("toggle") && keyPressed) {
+                    if(active){
+                        active=false;
+                        viewPort.removeProcessor(fpp);
+                    }else{
+                        active=true;
+                        viewPort.addProcessor(fpp);
+                    }
+                }
+            }
+        };
+             
+        inputManager.addListener(acl, "toggle");
+
+    }
+
+ 
+
+}
diff --git a/engine/src/test/jme3test/post/TestCartoonEdge.java b/engine/src/test/jme3test/post/TestCartoonEdge.java
new file mode 100644
index 0000000..a57656d
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestCartoonEdge.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.CartoonEdgeFilter;
+import com.jme3.renderer.Caps;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.texture.Texture;
+
+public class TestCartoonEdge extends SimpleApplication {
+
+    private FilterPostProcessor fpp;
+
+    public static void main(String[] args){
+        TestCartoonEdge app = new TestCartoonEdge();
+        app.start();
+    }
+
+    public void setupFilters(){
+        if (renderer.getCaps().contains(Caps.GLSL100)){
+            fpp=new FilterPostProcessor(assetManager);
+            //fpp.setNumSamples(4);
+            CartoonEdgeFilter toon=new CartoonEdgeFilter();
+            toon.setEdgeColor(ColorRGBA.Yellow);
+            fpp.addFilter(toon);
+            viewPort.addProcessor(fpp);
+        }
+    }
+
+    public void makeToonish(Spatial spatial){
+        if (spatial instanceof Node){
+            Node n = (Node) spatial;
+            for (Spatial child : n.getChildren())
+                makeToonish(child);
+        }else if (spatial instanceof Geometry){
+            Geometry g = (Geometry) spatial;
+            Material m = g.getMaterial();
+            if (m.getMaterialDef().getName().equals("Phong Lighting")){
+                Texture t = assetManager.loadTexture("Textures/ColorRamp/toon.png");
+//                t.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+//                t.setMagFilter(Texture.MagFilter.Nearest);
+                m.setTexture("ColorRamp", t);
+                m.setBoolean("UseMaterialColors", true);
+                m.setColor("Specular", ColorRGBA.Black);
+                m.setColor("Diffuse", ColorRGBA.White);
+                m.setBoolean("VertexLighting", true);
+            }
+        }
+    }
+
+    public void setupLighting(){
+   
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, 1).normalizeLocal());
+        dl.setColor(new ColorRGBA(2,2,2,1));
+
+        rootNode.addLight(dl);
+    }
+
+    public void setupModel(){
+        Spatial model = assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml");
+        makeToonish(model);
+        model.rotate(0, FastMath.PI, 0);
+//        signpost.setLocalTranslation(12, 3.5f, 30);
+//        model.scale(0.10f);
+//        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(model);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.Gray);
+
+        cam.setLocation(new Vector3f(-5.6310086f, 5.0892987f, -13.000479f));
+        cam.setRotation(new Quaternion(0.1779095f, 0.20036356f, -0.03702727f, 0.96272093f));
+        cam.update();
+
+        cam.setFrustumFar(300);
+        flyCam.setMoveSpeed(30);
+
+        rootNode.setCullHint(CullHint.Never);
+
+        setupLighting();
+        setupModel();
+        setupFilters();
+    }
+
+}
diff --git a/engine/src/test/jme3test/post/TestCrossHatch.java b/engine/src/test/jme3test/post/TestCrossHatch.java
new file mode 100644
index 0000000..d5230fa
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestCrossHatch.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.CrossHatchFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+public class TestCrossHatch extends SimpleApplication {
+
+    float angle;
+    Spatial lightMdl;
+    Spatial teapot;
+    Geometry frustumMdl;
+    WireFrustum frustum;
+    boolean active=true;
+    FilterPostProcessor fpp;
+    
+    public static void main(String[] args){
+        TestCrossHatch app = new TestCrossHatch();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -7.139601f));
+        cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
+        //cam.setFrustumFar(1000);
+
+
+        Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 15f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
+
+    
+
+
+        Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        matSoil.setFloat("Shininess", 15f);
+        matSoil.setBoolean("UseMaterialColors", true);
+        matSoil.setColor("Ambient", ColorRGBA.Gray);
+        matSoil.setColor("Diffuse", ColorRGBA.Black);
+        matSoil.setColor("Specular", ColorRGBA.Gray);
+       
+
+
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalTranslation(0,0,10);
+
+        teapot.setMaterial(mat);
+        teapot.setShadowMode(ShadowMode.CastAndReceive);
+        teapot.setLocalScale(10.0f);
+        rootNode.attachChild(teapot);
+
+  
+
+        Geometry soil=new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+        soil.setMaterial(matSoil);
+        soil.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(soil);
+
+        DirectionalLight light=new DirectionalLight();
+        light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        light.setColor(ColorRGBA.White.mult(1.5f));
+        rootNode.addLight(light);
+
+        // load sky
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
+        sky.setCullHint(Spatial.CullHint.Never);
+        rootNode.attachChild(sky);
+
+        fpp=new FilterPostProcessor(assetManager);
+        CrossHatchFilter chf=new CrossHatchFilter();
+        
+   
+
+        viewPort.addProcessor(fpp);
+        fpp.addFilter(chf);
+        initInputs();
+
+    }
+    
+         private void initInputs() {
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+     
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("toggle") && keyPressed) {
+                    if(active){
+                        active=false;
+                        viewPort.removeProcessor(fpp);
+                    }else{
+                        active=true;
+                        viewPort.addProcessor(fpp);
+                    }
+                }
+            }
+        };
+             
+        inputManager.addListener(acl, "toggle");
+
+    }
+
+ 
+
+}
diff --git a/engine/src/test/jme3test/post/TestDepthOfField.java b/engine/src/test/jme3test/post/TestDepthOfField.java
new file mode 100644
index 0000000..5426a33
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestDepthOfField.java
@@ -0,0 +1,200 @@
+package jme3test.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.DepthOfFieldFilter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * test
+ * @author Nehon
+ */
+public class TestDepthOfField extends SimpleApplication {
+
+    private FilterPostProcessor fpp;
+    private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f);
+    TerrainQuad terrain;
+    Material matRock;
+    DepthOfFieldFilter dofFilter;
+
+    public static void main(String[] args) {
+        TestDepthOfField app = new TestDepthOfField();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+
+        Node mainScene = new Node("Main Scene");
+        rootNode.attachChild(mainScene);
+
+        createTerrain(mainScene);
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(1.7f));
+        mainScene.addLight(sun);
+
+        DirectionalLight l = new DirectionalLight();
+        l.setDirection(Vector3f.UNIT_Y.mult(-1));
+        l.setColor(ColorRGBA.White.clone().multLocal(0.3f));
+        mainScene.addLight(l);
+
+        flyCam.setMoveSpeed(50);
+        cam.setFrustumFar(3000);
+        cam.setLocation(new Vector3f(-700, 100, 300));
+        cam.setRotation(new Quaternion().fromAngles(new float[]{FastMath.PI * 0.06f, FastMath.PI * 0.65f, 0}));
+
+
+        Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
+        sky.setLocalScale(350);
+        mainScene.attachChild(sky);
+
+
+
+        fpp = new FilterPostProcessor(assetManager);
+        //     fpp.setNumSamples(4);
+
+        dofFilter = new DepthOfFieldFilter();
+        dofFilter.setFocusDistance(0);
+        dofFilter.setFocusRange(50);
+        dofFilter.setBlurScale(1.4f);
+        fpp.addFilter(dofFilter);
+        viewPort.addProcessor(fpp);
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) {
+                    if (name.equals("toggle")) {
+                        dofFilter.setEnabled(!dofFilter.isEnabled());
+                    }
+
+
+                }
+            }
+        }, "toggle");
+        inputManager.addListener(new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("blurScaleUp")) {
+                    dofFilter.setBlurScale(dofFilter.getBlurScale() + 0.01f);
+                    System.out.println("blurScale : " + dofFilter.getBlurScale());
+                }
+                if (name.equals("blurScaleDown")) {
+                    dofFilter.setBlurScale(dofFilter.getBlurScale() - 0.01f);
+                    System.out.println("blurScale : " + dofFilter.getBlurScale());
+                }
+                if (name.equals("focusRangeUp")) {
+                    dofFilter.setFocusRange(dofFilter.getFocusRange() + 1f);
+                    System.out.println("focusRange : " + dofFilter.getFocusRange());
+                }
+                if (name.equals("focusRangeDown")) {
+                    dofFilter.setFocusRange(dofFilter.getFocusRange() - 1f);
+                    System.out.println("focusRange : " + dofFilter.getFocusRange());
+                }
+                if (name.equals("focusDistanceUp")) {
+                    dofFilter.setFocusDistance(dofFilter.getFocusDistance() + 1f);
+                    System.out.println("focusDistance : " + dofFilter.getFocusDistance());
+                }
+                if (name.equals("focusDistanceDown")) {
+                    dofFilter.setFocusDistance(dofFilter.getFocusDistance() - 1f);
+                    System.out.println("focusDistance : " + dofFilter.getFocusDistance());
+                }
+
+            }
+        }, "blurScaleUp", "blurScaleDown", "focusRangeUp", "focusRangeDown", "focusDistanceUp", "focusDistanceDown");
+
+
+        inputManager.addMapping("toggle", new KeyTrigger(keyInput.KEY_SPACE));
+        inputManager.addMapping("blurScaleUp", new KeyTrigger(keyInput.KEY_U));
+        inputManager.addMapping("blurScaleDown", new KeyTrigger(keyInput.KEY_J));
+        inputManager.addMapping("focusRangeUp", new KeyTrigger(keyInput.KEY_I));
+        inputManager.addMapping("focusRangeDown", new KeyTrigger(keyInput.KEY_K));
+        inputManager.addMapping("focusDistanceUp", new KeyTrigger(keyInput.KEY_O));
+        inputManager.addMapping("focusDistanceDown", new KeyTrigger(keyInput.KEY_L));
+
+    }
+
+    private void createTerrain(Node rootNode) {
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matRock.setBoolean("useTriPlanarMapping", false);
+        matRock.setBoolean("WardIso", true);
+        matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap", grass);
+        matRock.setFloat("DiffuseMap_0_scale", 64);
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_1", dirt);
+        matRock.setFloat("DiffuseMap_1_scale", 16);
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_2", rock);
+        matRock.setFloat("DiffuseMap_2_scale", 128);
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matRock.setTexture("NormalMap", normalMap0);
+        matRock.setTexture("NormalMap_1", normalMap2);
+        matRock.setTexture("NormalMap_2", normalMap2);
+
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+            heightmap.load();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        List<Camera> cameras = new ArrayList<Camera>();
+        cameras.add(getCamera());
+        terrain.setMaterial(matRock);
+        terrain.setLocalScale(new Vector3f(5, 5, 5));
+        terrain.setLocalTranslation(new Vector3f(0, -30, 0));
+        terrain.setLocked(false); // unlock it so we can edit the height
+
+        terrain.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(terrain);
+
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f);
+        Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f);
+        direction.subtractLocal(origin).normalizeLocal();
+        Ray ray = new Ray(origin, direction);
+        CollisionResults results = new CollisionResults();
+        int numCollisions = terrain.collideWith(ray, results);
+        if (numCollisions > 0) {
+            CollisionResult hit = results.getClosestCollision();
+            fpsText.setText(""+hit.getDistance());
+            dofFilter.setFocusDistance(hit.getDistance()/10.0f);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestFBOPassthrough.java b/engine/src/test/jme3test/post/TestFBOPassthrough.java
new file mode 100644
index 0000000..fee11fe
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestFBOPassthrough.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * Demonstrates FrameBuffer usage.
+ * The scene is first rendered to an FB with a texture attached,
+ * the texture is then rendered onto the screen in ortho mode.
+ *
+ * @author Kirill
+ */
+public class TestFBOPassthrough extends SimpleApplication {
+
+    private Node fbNode = new Node("Framebuffer Node");
+    private FrameBuffer fb;
+
+    public static void main(String[] args){
+        TestFBOPassthrough app = new TestFBOPassthrough();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        int w = settings.getWidth();
+        int h = settings.getHeight();
+
+        //setup framebuffer
+        fb = new FrameBuffer(w, h, 1);
+
+        Texture2D fbTex = new Texture2D(w, h, Format.RGBA8);
+        fb.setDepthBuffer(Format.Depth);
+        fb.setColorTexture(fbTex);
+
+        // setup framebuffer's scene
+        Sphere sphMesh = new Sphere(20, 20, 1);
+        Material solidColor = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+
+        Geometry sphere = new Geometry("sphere", sphMesh);
+        sphere.setMaterial(solidColor);
+        fbNode.attachChild(sphere);
+
+        //setup main scene
+        Picture p = new Picture("Picture");
+        p.setPosition(0, 0);
+        p.setWidth(w);
+        p.setHeight(h);
+        p.setTexture(assetManager, fbTex, false);
+
+        rootNode.attachChild(p);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        fbNode.updateLogicalState(tpf);
+        fbNode.updateGeometricState();
+    }
+
+    @Override
+    public void simpleRender(RenderManager rm){
+        Renderer r = rm.getRenderer();
+
+        //do FBO rendering
+        r.setFrameBuffer(fb);
+
+        rm.setCamera(cam, false); // FBO uses current camera
+        r.clearBuffers(true, true, true);
+        rm.renderScene(fbNode, viewPort);
+        rm.flushQueue(viewPort);
+
+        //go back to default rendering and let
+        //SimpleApplication render the default scene
+        r.setFrameBuffer(null);
+    }
+
+}
diff --git a/engine/src/test/jme3test/post/TestFog.java b/engine/src/test/jme3test/post/TestFog.java
new file mode 100644
index 0000000..220a070
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestFog.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.FogFilter;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SkyFactory;
+import java.io.File;
+
+public class TestFog extends SimpleApplication {
+
+    private FilterPostProcessor fpp;
+    private boolean enabled=true;
+    private FogFilter fog;
+
+    // set default for applets
+    private static boolean useHttp = true;
+
+    public static void main(String[] args) {
+        File file = new File("wildhouse.zip");
+        if (file.exists()) {
+            useHttp = false;
+        }
+        TestFog app = new TestFog();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(10);
+        Node mainScene=new Node();
+        cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f));
+        cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f));
+
+        // load sky
+        mainScene.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        // create the geometry and attach it
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("wildhouse.zip", ZipLocator.class.getName());
+        }
+        Spatial scene = assetManager.loadModel("main.scene");
+
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f lightDir=new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f);
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+
+        mainScene.attachChild(scene);
+        rootNode.attachChild(mainScene);
+
+        fpp=new FilterPostProcessor(assetManager);
+        //fpp.setNumSamples(4);
+        fog=new FogFilter();
+        fog.setFogColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f));
+        fog.setFogDistance(155);
+        fog.setFogDensity(2.0f);
+        fpp.addFilter(fog);
+        viewPort.addProcessor(fpp);
+        initInputs();
+    }
+
+     private void initInputs() {
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("DensityUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("DensityDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("DistanceUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("DistanceDown", new KeyTrigger(KeyInput.KEY_J));
+
+
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("toggle") && keyPressed) {
+                    if(enabled){
+                        enabled=false;
+                        viewPort.removeProcessor(fpp);
+                    }else{
+                        enabled=true;
+                        viewPort.addProcessor(fpp);
+                    }
+                }
+
+            }
+        };
+
+        AnalogListener anl=new AnalogListener() {
+
+            public void onAnalog(String name, float isPressed, float tpf) {
+                if(name.equals("DensityUp")){
+                    fog.setFogDensity(fog.getFogDensity()+0.001f);
+                    System.out.println("Fog density : "+fog.getFogDensity());
+                }
+                if(name.equals("DensityDown")){
+                    fog.setFogDensity(fog.getFogDensity()-0.010f);
+                    System.out.println("Fog density : "+fog.getFogDensity());
+                }
+                if(name.equals("DistanceUp")){
+                    fog.setFogDistance(fog.getFogDistance()+0.5f);
+                    System.out.println("Fog Distance : "+fog.getFogDistance());
+                }
+                if(name.equals("DistanceDown")){
+                    fog.setFogDistance(fog.getFogDistance()-0.5f);
+                    System.out.println("Fog Distance : "+fog.getFogDistance());
+                }
+
+            }
+        };
+
+        inputManager.addListener(acl, "toggle");
+        inputManager.addListener(anl, "DensityUp","DensityDown","DistanceUp","DistanceDown");
+
+    }
+}
+
diff --git a/engine/src/test/jme3test/post/TestHDR.java b/engine/src/test/jme3test/post/TestHDR.java
new file mode 100644
index 0000000..4b26069
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestHDR.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.HDRRenderer;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.ui.Picture;
+
+public class TestHDR extends SimpleApplication {
+
+    private HDRRenderer hdrRender;
+    private Picture dispQuad;
+
+    public static void main(String[] args){
+        TestHDR app = new TestHDR();
+        app.start();
+    }
+
+    public Geometry createHDRBox(){
+        Box boxMesh = new Box(Vector3f.ZERO, 1, 1, 1);
+        Geometry box = new Geometry("Box", boxMesh);
+
+//        Material mat = assetManager.loadMaterial("Textures/HdrTest/Memorial.j3m");
+//        box.setMaterial(mat);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Textures/HdrTest/Memorial.hdr"));
+        box.setMaterial(mat);
+
+        return box;
+    }
+
+//    private Material disp;
+    
+    @Override
+    public void simpleInitApp() {
+        hdrRender = new HDRRenderer(assetManager, renderer);
+        hdrRender.setSamples(0);
+        hdrRender.setMaxIterations(20);
+        hdrRender.setExposure(0.87f);
+        hdrRender.setThrottle(0.33f);
+
+        viewPort.addProcessor(hdrRender);
+        
+//        config.setVisible(true);
+
+        rootNode.attachChild(createHDRBox());
+    }
+
+    public void simpleUpdate(float tpf){
+        if (hdrRender.isInitialized() && dispQuad == null){
+            dispQuad = hdrRender.createDisplayQuad();
+            dispQuad.setWidth(128);
+            dispQuad.setHeight(128);
+            dispQuad.setPosition(30, cam.getHeight() - 128 - 30);
+            guiNode.attachChild(dispQuad);
+        }
+    }
+
+//    public void displayAvg(Renderer r){
+//        r.setFrameBuffer(null);
+//        disp = prepare(-1, -1, settings.getWidth(), settings.getHeight(), 3, -1, scene64, disp);
+//        r.clearBuffers(true, true, true);
+//        r.renderGeometry(pic);
+//    }
+
+}
diff --git a/engine/src/test/jme3test/post/TestLightScattering.java b/engine/src/test/jme3test/post/TestLightScattering.java
new file mode 100644
index 0000000..cd07952
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestLightScattering.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.StatsView;
+import com.jme3.font.BitmapText;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.LightScatteringFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.shadow.PssmShadowRenderer;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestLightScattering extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestLightScattering app = new TestLightScattering();
+        
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(55.35316f, -0.27061665f, 27.092093f));
+        cam.setRotation(new Quaternion(0.010414706f, 0.9874893f, 0.13880467f, -0.07409228f));
+//        cam.setDirection(new Vector3f(0,-0.5f,1.0f));
+//        cam.setLocation(new Vector3f(0, 300, -500));
+        //cam.setFrustumFar(1000);
+        flyCam.setMoveSpeed(10);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Rocky/Rocky.j3m");
+        Spatial scene = assetManager.loadModel("Models/Terrain/Terrain.mesh.xml");
+        TangentBinormalGenerator.generate(((Geometry)((Node)scene).getChild(0)).getMesh());
+        scene.setMaterial(mat);
+        scene.setShadowMode(ShadowMode.CastAndReceive);
+        scene.setLocalScale(400);
+        scene.setLocalTranslation(0, -10, -120);
+
+        rootNode.attachChild(scene);
+
+        // load sky
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false));
+
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f lightDir = new Vector3f(-0.12f, -0.3729129f, 0.74847335f);
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+        PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager,1024,4);
+        pssmRenderer.setDirection(lightDir);
+        pssmRenderer.setShadowIntensity(0.55f);
+     //   viewPort.addProcessor(pssmRenderer);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+//        SSAOFilter ssaoFilter= new SSAOFilter(viewPort, new SSAOConfig(0.36f,1.8f,0.84f,0.16f,false,true));
+//        fpp.addFilter(ssaoFilter);
+
+
+//           Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+//        mat2.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+//
+//        Sphere lite=new Sphere(8, 8, 10.0f);
+//        Geometry lightSphere=new Geometry("lightsphere", lite);
+//        lightSphere.setMaterial(mat2);
+        Vector3f lightPos = lightDir.multLocal(-3000);
+//        lightSphere.setLocalTranslation(lightPos);
+        // rootNode.attachChild(lightSphere);
+        LightScatteringFilter filter = new LightScatteringFilter(lightPos);
+        LightScatteringUI ui = new LightScatteringUI(inputManager, filter);
+        fpp.addFilter(filter);
+//fpp.setNumSamples(4);
+        //fpp.addFilter(new RadialBlurFilter(0.3f,15.0f));
+        //    SSAOUI ui=new SSAOUI(inputManager, ssaoFilter.getConfig());
+
+        viewPort.addProcessor(fpp);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestMultiRenderTarget.java b/engine/src/test/jme3test/post/TestMultiRenderTarget.java
new file mode 100644
index 0000000..5b31bdd
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestMultiRenderTarget.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+public class TestMultiRenderTarget extends SimpleApplication implements SceneProcessor {
+
+    private FrameBuffer fb;
+    private Texture2D diffuseData, normalData, specularData, depthData;
+    private Geometry sphere;
+    private Picture display1, display2, display3, display4;
+    
+    private Picture display;
+    private Material mat;
+
+    public static void main(String[] args){
+        TestMultiRenderTarget app = new TestMultiRenderTarget();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.addProcessor(this);
+        renderManager.setForcedTechnique("GBuf");
+
+//        flyCam.setEnabled(false);
+        cam.setLocation(new Vector3f(4.8037705f, 4.851632f, 10.789033f));
+        cam.setRotation(new Quaternion(-0.05143692f, 0.9483723f, -0.21131563f, -0.230846f));
+
+        Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml");
+        
+        //tankMesh.getMaterial().setColor("Specular", ColorRGBA.Black);
+        rootNode.attachChild(tank);
+
+        display1 = new Picture("Picture");
+        display1.move(0, 0, -1); // make it appear behind stats view
+        display2 = (Picture) display1.clone();
+        display3 = (Picture) display1.clone();
+        display4 = (Picture) display1.clone();
+        display  = (Picture) display1.clone();
+
+        ColorRGBA[] colors = new ColorRGBA[]{
+            ColorRGBA.White,
+            ColorRGBA.Blue,
+            ColorRGBA.Cyan,
+            ColorRGBA.DarkGray,
+            ColorRGBA.Green,
+            ColorRGBA.Magenta,
+            ColorRGBA.Orange,
+            ColorRGBA.Pink,
+            ColorRGBA.Red,
+            ColorRGBA.Yellow
+        };
+
+        for (int i = 0; i < 3; i++){
+            PointLight pl = new PointLight();
+            float angle = 0.314159265f * i;
+            pl.setPosition( new Vector3f(FastMath.cos(angle)*2f, 0,
+                                         FastMath.sin(angle)*2f));
+            pl.setColor(colors[i]);
+            pl.setRadius(5);
+            rootNode.addLight(pl);
+            display.addLight(pl);
+        }
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+        reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+        viewPort.setOutputFrameBuffer(fb);
+        guiViewPort.setClearFlags(true, true, true);
+        guiNode.attachChild(display);
+//        guiNode.attachChild(display1);
+//        guiNode.attachChild(display2);
+//        guiNode.attachChild(display3);
+//        guiNode.attachChild(display4);
+        guiNode.updateGeometricState();
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+        diffuseData  = new Texture2D(w, h, Format.RGBA8);
+        normalData   = new Texture2D(w, h, Format.RGBA8);
+        specularData = new Texture2D(w, h, Format.RGBA8);
+        depthData    = new Texture2D(w, h, Format.Depth);
+
+        mat = new Material(assetManager, "Common/MatDefs/Light/Deferred.j3md");
+        mat.setTexture("DiffuseData",  diffuseData);
+        mat.setTexture("SpecularData", specularData);
+        mat.setTexture("NormalData",   normalData);
+        mat.setTexture("DepthData",    depthData);
+
+        display.setMaterial(mat);
+        display.setPosition(0, 0);
+        display.setWidth(w);
+        display.setHeight(h);
+
+        display1.setTexture(assetManager, diffuseData, false);
+        display2.setTexture(assetManager, normalData, false);
+        display3.setTexture(assetManager, specularData, false);
+        display4.setTexture(assetManager, depthData, false);
+
+        display1.setPosition(0, 0);
+        display2.setPosition(w/2, 0);
+        display3.setPosition(0, h/2);
+        display4.setPosition(w/2, h/2);
+
+        display1.setWidth(w/2);
+        display1.setHeight(h/2);
+
+        display2.setWidth(w/2);
+        display2.setHeight(h/2);
+
+        display3.setWidth(w/2);
+        display3.setHeight(h/2);
+
+        display4.setWidth(w/2);
+        display4.setHeight(h/2);
+
+        guiNode.updateGeometricState();
+        
+        fb = new FrameBuffer(w, h, 1);
+        fb.setDepthTexture(depthData);
+        fb.addColorTexture(diffuseData);
+        fb.addColorTexture(normalData);
+        fb.addColorTexture(specularData);
+        fb.setMultiTarget(true);
+
+        /*
+         * Marks pixels in front of the far light boundary
+            Render back-faces of light volume
+            Depth test GREATER-EQUAL
+            Write to stencil on depth pass
+            Skipped for very small distant lights
+         */
+        
+        /*
+         * Find amount of lit pixels inside the volume
+             Start pixel query
+             Render front faces of light volume
+             Depth test LESS-EQUAL
+             Don’t write anything – only EQUAL stencil test
+         */
+
+        /*
+         * Enable conditional rendering
+            Based on query results from previous stage
+            GPU skips rendering for invisible lights
+         */
+
+        /*
+         * Render front-faces of light volume
+            Depth test - LESS-EQUAL
+            Stencil test - EQUAL
+            Runs only on marked pixels inside light
+         */
+    }
+
+    public boolean isInitialized() {
+        return diffuseData != null;
+    }
+
+    public void preFrame(float tpf) {
+        Matrix4f inverseViewProj = cam.getViewProjectionMatrix().invert();
+        mat.setMatrix4("ViewProjectionMatrixInverse", inverseViewProj);
+    }
+
+    public void postQueue(RenderQueue rq) {
+    }
+
+    public void postFrame(FrameBuffer out) {
+    }
+
+    public void cleanup() {
+    }
+
+}
diff --git a/engine/src/test/jme3test/post/TestMultiViewsFilters.java b/engine/src/test/jme3test/post/TestMultiViewsFilters.java
new file mode 100644
index 0000000..d2ef9d4
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestMultiViewsFilters.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.*;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.util.SkyFactory;
+
+public class TestMultiViewsFilters extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestMultiViewsFilters app = new TestMultiViewsFilters();
+        app.start();
+    }
+    private boolean filterEnabled = true;
+
+    public void simpleInitApp() {
+        // create the geometry and attach it
+        Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teaGeom.scale(3);
+        teaGeom.getMaterial().setColor("GlowColor", ColorRGBA.Green);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White);
+        dl.setDirection(Vector3f.UNIT_XYZ.negate());
+
+        rootNode.addLight(dl);
+        rootNode.attachChild(teaGeom);
+
+        // Setup first view      
+        cam.setViewPort(.5f, 1f, 0f, 0.5f);
+        cam.setLocation(new Vector3f(3.3212643f, 4.484704f, 4.2812433f));
+        cam.setRotation(new Quaternion(-0.07680723f, 0.92299235f, -0.2564353f, -0.27645364f));
+
+        // Setup second view
+        Camera cam2 = cam.clone();
+        cam2.setViewPort(0f, 0.5f, 0f, 0.5f);
+        cam2.setLocation(new Vector3f(-0.10947256f, 1.5760219f, 4.81758f));
+        cam2.setRotation(new Quaternion(0.0010108891f, 0.99857414f, -0.04928594f, 0.020481428f));
+
+        final ViewPort view2 = renderManager.createMainView("Bottom Left", cam2);
+        view2.setClearFlags(true, true, true);
+        view2.attachScene(rootNode);
+
+        // Setup third view
+        Camera cam3 = cam.clone();
+        cam3.setName("cam3");
+        cam3.setViewPort(0f, .5f, .5f, 1f);
+        cam3.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f));
+        cam3.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f));
+
+        final ViewPort view3 = renderManager.createMainView("Top Left", cam3);
+        view3.setClearFlags(true, true, true);
+        view3.attachScene(rootNode);
+
+
+        // Setup fourth view
+        Camera cam4 = cam.clone();
+        cam4.setName("cam4");
+        cam4.setViewPort(.5f, 1f, .5f, 1f);
+
+        cam4.setLocation(new Vector3f(4.775564f, 1.4548365f, 0.11491505f));
+        cam4.setRotation(new Quaternion(0.02356979f, -0.74957186f, 0.026729556f, 0.66096294f));
+
+        final ViewPort view4 = renderManager.createMainView("Top Right", cam4);
+        view4.setClearFlags(true, true, true);
+        view4.attachScene(rootNode);
+
+//        Camera cam5 = new Camera(200, 200);
+//        cam5.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
+//        cam5.setName("cam5");
+//        cam5.setViewPort(5.23f, 6.33f, 0.56f, 1.66f);
+//          this.setViewPortAreas(5.23f, 6.33f, 0.56f, 1.66f);
+//          this.setViewPortCamSize(200, 200);
+//          1046,1266,112,332
+        Camera cam5 = cam.clone();
+        cam5.setName("cam5");
+        cam5.setViewPort(1046f/settings.getWidth(), 1266f/settings.getWidth(), 112f/settings.getHeight(), 332f/settings.getHeight());
+        cam5.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f));
+        cam5.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f));
+
+        final ViewPort view5 = renderManager.createMainView("center", cam5);
+        view5.setClearFlags(true, true, true);
+        view5.attachScene(rootNode);
+
+        
+        
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        final FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        final FilterPostProcessor fpp2 = new FilterPostProcessor(assetManager);
+        final FilterPostProcessor fpp3 = new FilterPostProcessor(assetManager);
+        final FilterPostProcessor fpp4 = new FilterPostProcessor(assetManager);
+        final FilterPostProcessor fpp5 = new FilterPostProcessor(assetManager);
+
+
+        //  fpp.addFilter(new WaterFilter(rootNode, Vector3f.UNIT_Y.mult(-1)));
+        fpp3.addFilter(new CartoonEdgeFilter());
+
+        fpp2.addFilter(new CrossHatchFilter());
+        final FogFilter ff = new FogFilter(ColorRGBA.Yellow, 0.7f, 2);
+        fpp.addFilter(ff);
+
+        final RadialBlurFilter rbf = new RadialBlurFilter(1, 10);
+        //    rbf.setEnabled(false);
+        fpp.addFilter(rbf);
+
+
+        SSAOFilter f = new SSAOFilter(1.8899765f, 20.490374f, 0.4699998f, 0.1f);;
+        fpp4.addFilter(f);
+        SSAOUI ui = new SSAOUI(inputManager, f);
+        
+        fpp5.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects));
+
+        viewPort.addProcessor(fpp);
+        view2.addProcessor(fpp2);
+        view3.addProcessor(fpp3);
+        view4.addProcessor(fpp4);
+        view5.addProcessor(fpp5);
+
+
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("press") && isPressed) {
+                    if (filterEnabled) {
+                        viewPort.removeProcessor(fpp);
+                        view2.removeProcessor(fpp2);
+                        view3.removeProcessor(fpp3);
+                        view4.removeProcessor(fpp4);
+                        view5.removeProcessor(fpp5);
+                    } else {
+                        viewPort.addProcessor(fpp);
+                        view2.addProcessor(fpp2);
+                        view3.addProcessor(fpp3);
+                        view4.addProcessor(fpp4);
+                        view5.addProcessor(fpp5);
+                    }
+                    filterEnabled = !filterEnabled;
+                }
+                if (name.equals("filter") && isPressed) {
+                    ff.setEnabled(!ff.isEnabled());
+                    rbf.setEnabled(!rbf.isEnabled());
+                }
+            }
+        }, "press", "filter");
+
+        inputManager.addMapping("press", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("filter", new KeyTrigger(KeyInput.KEY_F));
+
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestMultiplesFilters.java b/engine/src/test/jme3test/post/TestMultiplesFilters.java
new file mode 100644
index 0000000..87e4c4a
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestMultiplesFilters.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+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.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.ColorOverlayFilter;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SkyFactory;
+import com.jme3.water.WaterFilter;
+import java.io.File;
+
+public class TestMultiplesFilters extends SimpleApplication {
+
+    private static boolean useHttp = false;
+
+    public static void main(String[] args) {
+        File file = new File("wildhouse.zip");
+        if (!file.exists()) {
+            useHttp = true;
+        }
+        TestMultiplesFilters app = new TestMultiplesFilters();
+        app.start();
+    }
+    SSAOFilter ssaoFilter;
+    FilterPostProcessor fpp;
+    boolean en = true;
+
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(10);
+        cam.setLocation(new Vector3f(6.0344796f, 1.5054002f, 55.572033f));
+        cam.setRotation(new Quaternion(0.0016069f, 0.9810479f, -0.008143323f, 0.19358753f));
+
+        // load sky
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        // create the geometry and attach it
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("wildhouse.zip", ZipLocator.class.getName());
+        }
+        Spatial scene = assetManager.loadModel("main.scene");
+
+
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f));
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+        fpp = new FilterPostProcessor(assetManager);
+      //  fpp.setNumSamples(4);
+        ssaoFilter = new SSAOFilter(0.92f, 2.2f, 0.46f, 0.2f);
+        final WaterFilter water=new WaterFilter(rootNode,new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f));
+        water.setWaterHeight(-20);
+        SSAOUI ui=new SSAOUI(inputManager,ssaoFilter);
+        final BloomFilter bloom = new BloomFilter();
+        final ColorOverlayFilter overlay = new ColorOverlayFilter(ColorRGBA.LightGray);
+        
+
+        fpp.addFilter(ssaoFilter);
+        
+        fpp.addFilter(water);
+
+        fpp.addFilter(bloom);
+
+        fpp.addFilter(overlay);
+
+        viewPort.addProcessor(fpp);
+
+        rootNode.attachChild(scene);
+        
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if ("toggleSSAO".equals(name) && isPressed) {
+                    if (ssaoFilter.isEnabled()) {
+                        ssaoFilter.setEnabled(false);
+                    } else {
+                        ssaoFilter.setEnabled(true);
+                    }
+                }
+                if ("toggleWater".equals(name) && isPressed) {
+                    if (water.isEnabled()) {
+                        water.setEnabled(false);
+                    } else {
+                        water.setEnabled(true);
+                    }
+                }
+                if ("toggleBloom".equals(name) && isPressed) {
+                    if (bloom.isEnabled()) {
+                        bloom.setEnabled(false);
+                    } else {
+                        bloom.setEnabled(true);
+                    }
+                }
+                if ("toggleOverlay".equals(name) && isPressed) {
+                    if (overlay.isEnabled()) {
+                        overlay.setEnabled(false);
+                    } else {
+                        overlay.setEnabled(true);
+                    }
+                }
+            }
+        }, "toggleSSAO", "toggleBloom", "toggleWater","toggleOverlay");
+        inputManager.addMapping("toggleSSAO", new KeyTrigger(KeyInput.KEY_1));
+        inputManager.addMapping("toggleWater", new KeyTrigger(KeyInput.KEY_2));
+        inputManager.addMapping("toggleBloom", new KeyTrigger(KeyInput.KEY_3));
+        inputManager.addMapping("toggleOverlay", new KeyTrigger(KeyInput.KEY_4));
+        
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestPostFilters.java b/engine/src/test/jme3test/post/TestPostFilters.java
new file mode 100644
index 0000000..6b41059
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestPostFilters.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.ColorOverlayFilter;
+import com.jme3.post.filters.FadeFilter;
+import com.jme3.post.filters.RadialBlurFilter;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestPostFilters extends SimpleApplication implements ActionListener {
+
+    private FilterPostProcessor fpp;
+    private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal();
+    FadeFilter fade;
+
+    public static void main(String[] args) {
+        TestPostFilters app = new TestPostFilters();
+        app.start();
+    }
+
+    public void setupFilters() {
+        if (renderer.getCaps().contains(Caps.GLSL100)) {
+            fpp = new FilterPostProcessor(assetManager);
+            // fpp.setNumSamples(4);
+            fpp.addFilter(new ColorOverlayFilter(ColorRGBA.LightGray));
+            fpp.addFilter(new RadialBlurFilter());
+            //fade=new FadeFilter(1.0f);
+            //fpp.addFilter(fade);
+
+
+            viewPort.addProcessor(fpp);
+        }
+    }
+
+    public void setupSkyBox() {
+        Texture envMap;
+        if (renderer.getCaps().contains(Caps.FloatTexture)) {
+            envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.hdr");
+        } else {
+            envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.jpg");
+        }
+        rootNode.attachChild(SkyFactory.createSky(assetManager, envMap, new Vector3f(-1, -1, -1), true));
+    }
+
+    public void setupLighting() {
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(lightDir);
+
+        dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1));
+
+        rootNode.addLight(dl);
+
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, 0, -1).normalizeLocal());
+
+        dl.setColor(new ColorRGBA(.4f, .4f, .4f, 1));
+
+        rootNode.addLight(dl);
+    }
+
+    public void setupFloor() {
+        Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m");
+        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
+        mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
+        Box floor = new Box(Vector3f.ZERO, 50, 1f, 50);
+        TangentBinormalGenerator.generate(floor);
+        floor.scaleTextureCoordinates(new Vector2f(5, 5));
+        Geometry floorGeom = new Geometry("Floor", floor);
+        floorGeom.setMaterial(mat);
+        floorGeom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(floorGeom);
+    }
+
+    public void setupSignpost() {
+        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
+        signpost.setMaterial(mat);
+        signpost.rotate(0, FastMath.HALF_PI, 0);
+        signpost.setLocalTranslation(12, 3.5f, 30);
+        signpost.setLocalScale(4);
+        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(signpost);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(-32.295086f, 54.80136f, 79.59805f));
+        cam.setRotation(new Quaternion(0.074364014f, 0.92519957f, -0.24794696f, 0.27748522f));
+        cam.update();
+
+        cam.setFrustumFar(300);
+        flyCam.setMoveSpeed(30);
+
+        rootNode.setCullHint(CullHint.Never);
+
+        setupLighting();
+        setupSkyBox();
+
+
+        setupFloor();
+
+        setupSignpost();
+
+        setupFilters();
+
+        initInput();
+
+    }
+
+    protected void initInput() {
+        flyCam.setMoveSpeed(3);
+        //init input
+        inputManager.addMapping("fadein", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addListener(this, "fadein");
+        inputManager.addMapping("fadeout", new KeyTrigger(KeyInput.KEY_O));
+        inputManager.addListener(this, "fadeout");
+
+    }
+
+    public void onAction(String name, boolean value, float tpf) {
+        if (name.equals("fadein") && value) {
+            fade.fadeIn();
+            System.out.println("fade in");
+
+        }
+        if (name.equals("fadeout") && value) {
+            fade.fadeOut();
+            System.out.println("fade out");
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestPosterization.java b/engine/src/test/jme3test/post/TestPosterization.java
new file mode 100644
index 0000000..e2c24e8
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestPosterization.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.PosterizationFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+public class TestPosterization extends SimpleApplication {
+
+    float angle;
+    Spatial lightMdl;
+    Spatial teapot;
+    Geometry frustumMdl;
+    WireFrustum frustum;
+    boolean active=true;
+    FilterPostProcessor fpp;
+    
+    public static void main(String[] args){
+        TestPosterization app = new TestPosterization();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -7.139601f));
+        cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
+        //cam.setFrustumFar(1000);
+
+
+        Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 15f);
+        mat.setBoolean("UseMaterialColors", true);
+        mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
+        mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
+
+    
+
+
+        Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
+        matSoil.setFloat("Shininess", 15f);
+        matSoil.setBoolean("UseMaterialColors", true);
+        matSoil.setColor("Ambient", ColorRGBA.Gray);
+        matSoil.setColor("Diffuse", ColorRGBA.Black);
+        matSoil.setColor("Specular", ColorRGBA.Gray);
+       
+
+
+        teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teapot.setLocalTranslation(0,0,10);
+
+        teapot.setMaterial(mat);
+        teapot.setShadowMode(ShadowMode.CastAndReceive);
+        teapot.setLocalScale(10.0f);
+        rootNode.attachChild(teapot);
+
+  
+
+        Geometry soil=new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+        soil.setMaterial(matSoil);
+        soil.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(soil);
+
+        DirectionalLight light=new DirectionalLight();
+        light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        light.setColor(ColorRGBA.White.mult(1.5f));
+        rootNode.addLight(light);
+
+        // load sky
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
+        sky.setCullHint(Spatial.CullHint.Never);
+        rootNode.attachChild(sky);
+
+        fpp=new FilterPostProcessor(assetManager);
+        PosterizationFilter pf=new PosterizationFilter();
+        
+   
+
+        viewPort.addProcessor(fpp);
+        fpp.addFilter(pf);
+        initInputs();
+
+    }
+    
+         private void initInputs() {
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+     
+        ActionListener acl = new ActionListener() {
+
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (name.equals("toggle") && keyPressed) {
+                    if(active){
+                        active=false;
+                        viewPort.removeProcessor(fpp);
+                    }else{
+                        active=true;
+                        viewPort.addProcessor(fpp);
+                    }
+                }
+            }
+        };
+             
+        inputManager.addListener(acl, "toggle");
+
+    }
+
+ 
+
+}
diff --git a/engine/src/test/jme3test/post/TestRenderToMemory.java b/engine/src/test/jme3test/post/TestRenderToMemory.java
new file mode 100644
index 0000000..4a97844
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestRenderToMemory.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.Screenshots;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+/**
+ * This test renders a scene to an offscreen framebuffer, then copies
+ * the contents to a Swing JFrame. Note that some parts are done inefficently,
+ * this is done to make the code more readable.
+ */
+public class TestRenderToMemory extends SimpleApplication implements SceneProcessor {
+
+    private Geometry offBox;
+    private float angle = 0;
+
+    private FrameBuffer offBuffer;
+    private ViewPort offView;
+    private Texture2D offTex;
+    private Camera offCamera;
+    private ImageDisplay display;
+
+    private static final int width = 800, height = 600;
+
+    private final ByteBuffer cpuBuf = BufferUtils.createByteBuffer(width * height * 4);
+    private final byte[] cpuArray = new byte[width * height * 4];
+    private final BufferedImage image = new BufferedImage(width, height,
+                                            BufferedImage.TYPE_4BYTE_ABGR);
+
+    private class ImageDisplay extends JPanel {
+
+        private long t;
+        private long total;
+        private int frames;
+        private int fps;
+
+        @Override
+        public void paintComponent(Graphics gfx) {
+            super.paintComponent(gfx);
+            Graphics2D g2d = (Graphics2D) gfx;
+
+            if (t == 0)
+                t = timer.getTime();
+
+//            g2d.setBackground(Color.BLACK);
+//            g2d.clearRect(0,0,width,height);
+
+            synchronized (image){
+                g2d.drawImage(image, null, 0, 0);
+            }
+
+            long t2 = timer.getTime();
+            long dt = t2 - t;
+            total += dt;
+            frames ++;
+            t = t2;
+
+            if (total > 1000){
+                fps = frames;
+                total = 0;
+                frames = 0;
+            }
+
+            g2d.setColor(Color.white);
+            g2d.drawString("FPS: "+fps, 0, getHeight() - 100);
+        }
+    }
+
+    public static void main(String[] args){
+        TestRenderToMemory app = new TestRenderToMemory();
+        app.setPauseOnLostFocus(false);
+        AppSettings settings = new AppSettings(true);
+        settings.setResolution(1, 1);
+        app.setSettings(settings);
+        app.start(Type.OffscreenSurface);
+    }
+
+    public void createDisplayFrame(){
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                JFrame frame = new JFrame("Render Display");
+                display = new ImageDisplay();
+                display.setPreferredSize(new Dimension(width, height));
+                frame.getContentPane().add(display);
+                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+                frame.addWindowListener(new WindowAdapter(){
+                    public void windowClosed(WindowEvent e){
+                        stop();
+                    }
+                });
+                frame.pack();
+                frame.setLocationRelativeTo(null);
+                frame.setResizable(false);
+                frame.setVisible(true);
+            }
+        });
+    }
+
+    public void updateImageContents(){
+        cpuBuf.clear();
+        renderer.readFrameBuffer(offBuffer, cpuBuf);
+
+        synchronized (image) {
+            Screenshots.convertScreenShot(cpuBuf, image);    
+        }
+
+        if (display != null)
+            display.repaint();
+    }
+
+    public void setupOffscreenView(){
+        offCamera = new Camera(width, height);
+
+        // create a pre-view. a view that is rendered before the main view
+        offView = renderManager.createPreView("Offscreen View", offCamera);
+        offView.setBackgroundColor(ColorRGBA.DarkGray);
+        offView.setClearFlags(true, true, true);
+        
+        // this will let us know when the scene has been rendered to the 
+        // frame buffer
+        offView.addProcessor(this);
+
+        // create offscreen framebuffer
+        offBuffer = new FrameBuffer(width, height, 1);
+
+        //setup framebuffer's cam
+        offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
+        offCamera.setLocation(new Vector3f(0f, 0f, -5f));
+        offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
+
+        //setup framebuffer's texture
+//        offTex = new Texture2D(width, height, Format.RGBA8);
+
+        //setup framebuffer to use renderbuffer
+        // this is faster for gpu -> cpu copies
+        offBuffer.setDepthBuffer(Format.Depth);
+        offBuffer.setColorBuffer(Format.RGBA8);
+//        offBuffer.setColorTexture(offTex);
+        
+        //set viewport to render to offscreen framebuffer
+        offView.setOutputFrameBuffer(offBuffer);
+
+        // setup framebuffer's scene
+        Box boxMesh = new Box(Vector3f.ZERO, 1,1,1);
+        Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m");
+        offBox = new Geometry("box", boxMesh);
+        offBox.setMaterial(material);
+
+        // attach the scene to the viewport to be rendered
+        offView.attachScene(offBox);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setupOffscreenView();
+        createDisplayFrame();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Quaternion q = new Quaternion();
+        angle += tpf;
+        angle %= FastMath.TWO_PI;
+        q.fromAngles(angle, 0, angle);
+
+        offBox.setLocalRotation(q);
+        offBox.updateLogicalState(tpf);
+        offBox.updateGeometricState();
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    public boolean isInitialized() {
+        return true;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+    }
+
+    /**
+     * Update the CPU image's contents after the scene has
+     * been rendered to the framebuffer.
+     */
+    public void postFrame(FrameBuffer out) {
+        updateImageContents();
+    }
+
+    public void cleanup() {
+    }
+
+
+}
diff --git a/engine/src/test/jme3test/post/TestRenderToTexture.java b/engine/src/test/jme3test/post/TestRenderToTexture.java
new file mode 100644
index 0000000..ccc993a
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestRenderToTexture.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+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.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+
+/**
+ * This test renders a scene to a texture, then displays the texture on a cube.
+ */
+public class TestRenderToTexture extends SimpleApplication implements ActionListener {
+
+    private static final String TOGGLE_UPDATE = "Toggle Update";
+    private Geometry offBox;
+    private float angle = 0;
+    private ViewPort offView;
+
+    public static void main(String[] args){
+        TestRenderToTexture app = new TestRenderToTexture();
+        app.start();
+    }
+
+    public Texture setupOffscreenView(){
+        Camera offCamera = new Camera(512, 512);
+
+        offView = renderManager.createPreView("Offscreen View", offCamera);
+        offView.setClearFlags(true, true, true);
+        offView.setBackgroundColor(ColorRGBA.DarkGray);
+
+        // create offscreen framebuffer
+        FrameBuffer offBuffer = new FrameBuffer(512, 512, 1);
+
+        //setup framebuffer's cam
+        offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
+        offCamera.setLocation(new Vector3f(0f, 0f, -5f));
+        offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
+
+        //setup framebuffer's texture
+        Texture2D offTex = new Texture2D(512, 512, Format.RGBA8);
+        offTex.setMinFilter(Texture.MinFilter.Trilinear);
+        offTex.setMagFilter(Texture.MagFilter.Bilinear);
+
+        //setup framebuffer to use texture
+        offBuffer.setDepthBuffer(Format.Depth);
+        offBuffer.setColorTexture(offTex);
+        
+        //set viewport to render to offscreen framebuffer
+        offView.setOutputFrameBuffer(offBuffer);
+
+        // setup framebuffer's scene
+        Box boxMesh = new Box(Vector3f.ZERO, 1,1,1);
+        Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m");
+        offBox = new Geometry("box", boxMesh);
+        offBox.setMaterial(material);
+
+        // attach the scene to the viewport to be rendered
+        offView.attachScene(offBox);
+        
+        return offTex;
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(3, 3, 3));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+
+        //setup main scene
+        Geometry quad = new Geometry("box", new Box(Vector3f.ZERO, 1,1,1));
+
+        Texture offTex = setupOffscreenView();
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", offTex);
+        quad.setMaterial(mat);
+        rootNode.attachChild(quad);
+        inputManager.addMapping(TOGGLE_UPDATE, new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this, TOGGLE_UPDATE);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Quaternion q = new Quaternion();
+        
+        if (offView.isEnabled()) {
+            angle += tpf;
+            angle %= FastMath.TWO_PI;
+            q.fromAngles(angle, 0, angle);
+            
+            offBox.setLocalRotation(q);
+            offBox.updateLogicalState(tpf);
+            offBox.updateGeometricState();
+        }
+    }
+
+    @Override
+    public void onAction(String name, boolean isPressed, float tpf) {
+        if (name.equals(TOGGLE_UPDATE) && isPressed) {
+            offView.setEnabled(!offView.isEnabled());
+        }
+    }
+
+
+}
diff --git a/engine/src/test/jme3test/post/TestSSAO.java b/engine/src/test/jme3test/post/TestSSAO.java
new file mode 100644
index 0000000..e9cef98
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestSSAO.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 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.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+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.scene.Geometry;
+import com.jme3.texture.Texture;
+
+public class TestSSAO extends SimpleApplication {
+
+    Geometry model;
+
+    public static void main(String[] args) {
+        TestSSAO app = new TestSSAO();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(68.45442f, 8.235511f, 7.9676695f));
+        cam.setRotation(new Quaternion(0.046916496f, -0.69500375f, 0.045538206f, 0.7160271f));
+
+
+        flyCam.setMoveSpeed(50);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Texture diff = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg");
+        diff.setWrap(Texture.WrapMode.Repeat);
+        Texture norm = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg");
+        norm.setWrap(Texture.WrapMode.Repeat);
+        mat.setTexture("DiffuseMap", diff);
+        mat.setTexture("NormalMap", norm);
+        mat.setFloat("Shininess", 2.0f);
+
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(new ColorRGBA(1.8f, 1.8f, 1.8f, 1.0f));
+
+        rootNode.addLight(al);
+
+        model = (Geometry) assetManager.loadModel("Models/Sponza/Sponza.j3o");
+        model.getMesh().scaleTextureCoordinates(new Vector2f(2, 2));
+
+        model.setMaterial(mat);
+
+        rootNode.attachChild(model);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        SSAOFilter ssaoFilter = new SSAOFilter(12.940201f, 43.928635f, 0.32999992f, 0.6059958f);
+        fpp.addFilter(ssaoFilter);
+        SSAOUI ui = new SSAOUI(inputManager, ssaoFilter);
+
+        viewPort.addProcessor(fpp);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestTransparentCartoonEdge.java b/engine/src/test/jme3test/post/TestTransparentCartoonEdge.java
new file mode 100644
index 0000000..46ba0ac
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestTransparentCartoonEdge.java
@@ -0,0 +1,97 @@
+package jme3test.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.CartoonEdgeFilter;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture;
+
+public class TestTransparentCartoonEdge extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestTransparentCartoonEdge app = new TestTransparentCartoonEdge();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        renderManager.setAlphaToCoverage(true);
+        cam.setLocation(new Vector3f(0.14914267f, 0.58147097f, 4.7686534f));
+        cam.setRotation(new Quaternion(-0.0044764364f, 0.9767943f, 0.21314798f, 0.020512417f));
+
+//        cam.setLocation(new Vector3f(2.0606942f, 3.20342f, 6.7860126f));
+//        cam.setRotation(new Quaternion(-0.017481906f, 0.98241085f, -0.12393151f, -0.13857932f));
+
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Quad q = new Quad(20, 20);
+        q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5));
+        Geometry geom = new Geometry("floor", q);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        geom.setMaterial(mat);
+        
+        geom.rotate(-FastMath.HALF_PI, 0, 0);
+        geom.center();
+        geom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(geom);
+
+        // create the geometry and attach it
+        Spatial teaGeom = assetManager.loadModel("Models/Tree/Tree.mesh.j3o");
+        teaGeom.setQueueBucket(Bucket.Transparent);
+        teaGeom.setShadowMode(ShadowMode.Cast);
+        makeToonish(teaGeom);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(2));
+        rootNode.addLight(al);
+
+        DirectionalLight dl1 = new DirectionalLight();
+        dl1.setDirection(new Vector3f(1, -1, 1).normalizeLocal());
+        dl1.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl1);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl);
+
+        rootNode.attachChild(teaGeom);
+
+        FilterPostProcessor fpp=new FilterPostProcessor(assetManager);
+        CartoonEdgeFilter toon=new CartoonEdgeFilter();
+        toon.setEdgeWidth(0.5f);
+        toon.setEdgeIntensity(1.0f);
+        toon.setNormalThreshold(0.8f);
+        fpp.addFilter(toon);
+        viewPort.addProcessor(fpp);
+    }
+
+        public void makeToonish(Spatial spatial){
+        if (spatial instanceof Node){
+            Node n = (Node) spatial;
+            for (Spatial child : n.getChildren())
+                makeToonish(child);
+        }else if (spatial instanceof Geometry){
+            Geometry g = (Geometry) spatial;
+            Material m = g.getMaterial();
+            if (m.getMaterialDef().getName().equals("Phong Lighting")){
+                Texture t = assetManager.loadTexture("Textures/ColorRamp/toon.png");
+//                t.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+//                t.setMagFilter(Texture.MagFilter.Nearest);
+                m.setTexture("ColorRamp", t);
+                m.setBoolean("UseMaterialColors", true);
+                m.setColor("Specular", ColorRGBA.Black);
+                m.setColor("Diffuse", ColorRGBA.White);
+                m.setBoolean("VertexLighting", true);
+            }
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/post/TestTransparentSSAO.java b/engine/src/test/jme3test/post/TestTransparentSSAO.java
new file mode 100644
index 0000000..3d45b4a
--- /dev/null
+++ b/engine/src/test/jme3test/post/TestTransparentSSAO.java
@@ -0,0 +1,74 @@
+package jme3test.post;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+
+public class TestTransparentSSAO extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestTransparentSSAO app = new TestTransparentSSAO();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        renderManager.setAlphaToCoverage(true);
+        cam.setLocation(new Vector3f(0.14914267f, 0.58147097f, 4.7686534f));
+        cam.setRotation(new Quaternion(-0.0044764364f, 0.9767943f, 0.21314798f, 0.020512417f));
+
+//        cam.setLocation(new Vector3f(2.0606942f, 3.20342f, 6.7860126f));
+//        cam.setRotation(new Quaternion(-0.017481906f, 0.98241085f, -0.12393151f, -0.13857932f));
+
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Quad q = new Quad(20, 20);
+        q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5));
+        Geometry geom = new Geometry("floor", q);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        geom.setMaterial(mat);
+
+        geom.rotate(-FastMath.HALF_PI, 0, 0);
+        geom.center();
+        geom.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(geom);
+
+        // create the geometry and attach it
+        Spatial teaGeom = assetManager.loadModel("Models/Tree/Tree.mesh.j3o");
+        teaGeom.setQueueBucket(Bucket.Transparent);
+        teaGeom.setShadowMode(ShadowMode.Cast);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(2));
+        rootNode.addLight(al);
+
+        DirectionalLight dl1 = new DirectionalLight();
+        dl1.setDirection(new Vector3f(1, -1, 1).normalizeLocal());
+        dl1.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl1);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f));
+        rootNode.addLight(dl);
+
+        rootNode.attachChild(teaGeom);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+
+        SSAOFilter ssao = new SSAOFilter(0.49997783f, 42.598858f, 35.999966f, 0.39299846f);
+        fpp.addFilter(ssao);
+
+        SSAOUI ui = new SSAOUI(inputManager, ssao);
+
+        viewPort.addProcessor(fpp);
+    }
+}
diff --git a/engine/src/test/jme3test/renderer/TestMultiViews.java b/engine/src/test/jme3test/renderer/TestMultiViews.java
new file mode 100644
index 0000000..aa6b67e
--- /dev/null
+++ b/engine/src/test/jme3test/renderer/TestMultiViews.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 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.renderer;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+
+public class TestMultiViews extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestMultiViews app = new TestMultiViews();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        // create the geometry and attach it
+        Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+        teaGeom.scale(3);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White);
+        dl.setDirection(Vector3f.UNIT_XYZ.negate());
+
+        rootNode.addLight(dl);
+        rootNode.attachChild(teaGeom);
+
+        // Setup first view
+        viewPort.setBackgroundColor(ColorRGBA.Blue);
+        cam.setViewPort(.5f, 1f, 0f, 0.5f);
+        cam.setLocation(new Vector3f(3.3212643f, 4.484704f, 4.2812433f));
+        cam.setRotation(new Quaternion(-0.07680723f, 0.92299235f, -0.2564353f, -0.27645364f));
+
+        // Setup second view
+        Camera cam2 = cam.clone();
+        cam2.setViewPort(0f, 0.5f, 0f, 0.5f);
+        cam2.setLocation(new Vector3f(-0.10947256f, 1.5760219f, 4.81758f));
+        cam2.setRotation(new Quaternion(0.0010108891f, 0.99857414f, -0.04928594f, 0.020481428f));
+
+        ViewPort view2 = renderManager.createMainView("Bottom Left", cam2);
+        view2.setClearFlags(true, true, true);
+        view2.attachScene(rootNode);
+
+        // Setup third view
+        Camera cam3 = cam.clone();
+        cam3.setViewPort(0f, .5f, .5f, 1f);
+        cam3.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f));
+        cam3.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f));
+
+        ViewPort view3 = renderManager.createMainView("Top Left", cam3);
+        view3.setClearFlags(true, true, true);
+        view3.attachScene(rootNode);
+
+        // Setup fourth view
+        Camera cam4 = cam.clone();
+        cam4.setViewPort(.5f, 1f, .5f, 1f);
+        cam4.setLocation(new Vector3f(4.775564f, 1.4548365f, 0.11491505f));
+        cam4.setRotation(new Quaternion(0.02356979f, -0.74957186f, 0.026729556f, 0.66096294f));
+
+        ViewPort view4 = renderManager.createMainView("Top Right", cam4);
+        view4.setClearFlags(true, true, true);
+        view4.attachScene(rootNode);
+
+        //test multiview for gui 
+        guiViewPort.getCamera().setViewPort(.5f, 1f, .5f, 1f);
+
+        // Setup second gui view
+        Camera guiCam2 = guiViewPort.getCamera().clone();
+        guiCam2.setViewPort(0f, 0.5f, 0f, 0.5f);
+        ViewPort guiViewPort2 = renderManager.createPostView("Gui 2", guiCam2);
+        guiViewPort2.setClearFlags(false, false, false);
+        guiViewPort2.attachScene(guiViewPort.getScenes().get(0));
+
+    }
+}
diff --git a/engine/src/test/jme3test/renderer/TestParallelProjection.java b/engine/src/test/jme3test/renderer/TestParallelProjection.java
new file mode 100644
index 0000000..2b90027
--- /dev/null
+++ b/engine/src/test/jme3test/renderer/TestParallelProjection.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 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.renderer;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+
+public class TestParallelProjection  extends SimpleApplication implements AnalogListener {
+
+    private float frustumSize = 1;
+
+    public static void main(String[] args){
+        TestParallelProjection app = new TestParallelProjection();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White);
+        dl.setDirection(Vector3f.UNIT_XYZ.negate());
+
+        rootNode.addLight(dl);
+        rootNode.attachChild(teaGeom);
+
+        // Setup first view
+        cam.setParallelProjection(true);
+        float aspect = (float) cam.getWidth() / cam.getHeight();
+        cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize);
+
+        inputManager.addListener(this, "Size+", "Size-");
+        inputManager.addMapping("Size+", new KeyTrigger(KeyInput.KEY_W));
+        inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S));
+    }
+
+    public void onAnalog(String name, float value, float tpf) {
+        // Instead of moving closer/farther to object, we zoom in/out.
+        if (name.equals("Size-"))
+            frustumSize += 0.3f * tpf;
+        else
+            frustumSize -= 0.3f * tpf;
+
+        float aspect = (float) cam.getWidth() / cam.getHeight();
+        cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize);
+    }
+}
diff --git a/engine/src/test/jme3test/scene/TestSceneLoading.java b/engine/src/test/jme3test/scene/TestSceneLoading.java
new file mode 100644
index 0000000..fddef16
--- /dev/null
+++ b/engine/src/test/jme3test/scene/TestSceneLoading.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 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.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.SkyFactory;
+import java.io.File;
+
+public class TestSceneLoading extends SimpleApplication {
+
+    private Sphere sphereMesh = new Sphere(32, 32, 10, false, true);
+    private Geometry sphere = new Geometry("Sky", sphereMesh);
+    private static boolean useHttp = false;
+
+    public static void main(String[] args) {
+     
+        TestSceneLoading app = new TestSceneLoading();
+        app.start();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        sphere.setLocalTranslation(cam.getLocation());
+    }
+
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(10);
+
+        // load sky
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        File file = new File("wildhouse.zip");
+        if (!file.exists()) {
+            useHttp = true;
+        }
+        // create the geometry and attach it
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("wildhouse.zip", ZipLocator.class.getName());
+        }
+        Spatial scene = assetManager.loadModel("main.scene");
+
+        AmbientLight al = new AmbientLight();
+        scene.addLight(al);
+
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(0.69077975f, -0.6277887f, -0.35875428f).normalizeLocal());
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+        rootNode.attachChild(scene);
+    }
+}
diff --git a/engine/src/test/jme3test/scene/TestUserData.java b/engine/src/test/jme3test/scene/TestUserData.java
new file mode 100644
index 0000000..10bafd6
--- /dev/null
+++ b/engine/src/test/jme3test/scene/TestUserData.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2009-2010 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.scene.Node;
+import com.jme3.scene.Spatial;
+
+public class TestUserData extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestUserData app = new TestUserData();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Node scene = (Node) assetManager.loadModel("Scenes/DotScene/DotScene.scene");
+        System.out.println("Scene: " + scene);
+
+        Spatial testNode = scene.getChild("TestNode");
+        System.out.println("TestNode: "+ testNode);
+
+        for (String key : testNode.getUserDataKeys()){
+            System.out.println("Property " + key + " = " + testNode.getUserData(key));
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/stress/TestBatchLod.java b/engine/src/test/jme3test/stress/TestBatchLod.java
new file mode 100644
index 0000000..f5d2660
--- /dev/null
+++ b/engine/src/test/jme3test/stress/TestBatchLod.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 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.stress;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.LodControl;
+import jme3tools.optimize.GeometryBatchFactory;
+
+public class TestBatchLod extends SimpleApplication {
+
+    private boolean lod = false;
+
+    public static void main(String[] args) {
+        TestBatchLod app = new TestBatchLod();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+//        inputManager.registerKeyBinding("USELOD", KeyInput.KEY_L);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(dl);
+
+        Node teapotNode = (Node) assetManager.loadModel("Models/Teapot/Teapot.mesh.xml");
+        Geometry teapot = (Geometry) teapotNode.getChild(0);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 16f);
+        mat.setBoolean("VertexLighting", true);
+        teapot.setMaterial(mat);
+
+        // show normals as material
+        //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+        flyCam.setMoveSpeed(5);
+        for (int y = -5; y < 5; y++) {
+            for (int x = -5; x < 5; x++) {
+                Geometry clonePot = teapot.clone();
+
+                //clonePot.setMaterial(mat);
+                clonePot.setLocalTranslation(x * .5f, 0, y * .5f);
+                clonePot.setLocalScale(.15f);
+                clonePot.setMaterial(mat);
+                rootNode.attachChild(clonePot);
+            }
+        }
+        GeometryBatchFactory.optimize(rootNode, true);
+        LodControl control = new LodControl();
+        rootNode.getChild(0).addControl(control);
+        cam.setLocation(new Vector3f(-1.0748308f, 1.35778f, -1.5380064f));
+        cam.setRotation(new Quaternion(0.18343268f, 0.34531063f, -0.069015436f, 0.9177962f));
+
+    }
+}
diff --git a/engine/src/test/jme3test/stress/TestLeakingGL.java b/engine/src/test/jme3test/stress/TestLeakingGL.java
new file mode 100644
index 0000000..dba4863
--- /dev/null
+++ b/engine/src/test/jme3test/stress/TestLeakingGL.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2010 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.stress;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.NativeObjectManager;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Generates 400 new meshes every frame then leaks them.
+ * Notice how memory usage stays constant and OpenGL objects
+ * are properly destroyed.
+ */
+public class TestLeakingGL extends SimpleApplication {
+
+    private Material solidColor;
+    private Sphere original;
+
+    public static void main(String[] args){
+        TestLeakingGL app = new TestLeakingGL();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        original = new Sphere(4, 4, 1);
+        original.setStatic();
+        original.setInterleaved();
+
+        // this will make sure all spheres are rendered always
+        rootNode.setCullHint(CullHint.Never);
+        solidColor = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+        cam.setLocation(new Vector3f(0, 5, 0));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+
+        Logger.getLogger(Node.class.getName()).setLevel(Level.WARNING);
+        Logger.getLogger(NativeObjectManager.class.getName()).setLevel(Level.WARNING);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        rootNode.detachAllChildren();
+        for (int y = -15; y < 15; y++){
+            for (int x = -15; x < 15; x++){
+                Mesh sphMesh = original.deepClone();
+                Geometry sphere = new Geometry("sphere", sphMesh);
+
+                sphere.setMaterial(solidColor);
+                sphere.setLocalTranslation(x * 1.5f, 0, y * 1.5f);
+                rootNode.attachChild(sphere);
+            }
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/stress/TestLodStress.java b/engine/src/test/jme3test/stress/TestLodStress.java
new file mode 100644
index 0000000..84a3376
--- /dev/null
+++ b/engine/src/test/jme3test/stress/TestLodStress.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 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.stress;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.LodControl;
+
+public class TestLodStress extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestLodStress app = new TestLodStress();
+        app.setShowSettings(false);
+        app.setPauseOnLostFocus(false);
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1,-1,-1).normalizeLocal());
+        rootNode.addLight(dl);
+
+        Node teapotNode = (Node) assetManager.loadModel("Models/Teapot/Teapot.mesh.xml");
+        Geometry teapot = (Geometry) teapotNode.getChild(0);
+        
+//        Sphere sph = new Sphere(16, 16, 4);
+//        Geometry teapot = new Geometry("teapot", sph);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setFloat("Shininess", 16f);
+        mat.setBoolean("VertexLighting", true);
+        teapot.setMaterial(mat);
+        
+       // show normals as material
+        //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+
+        for (int y = -10; y < 10; y++){
+            for (int x = -10; x < 10; x++){
+                Geometry clonePot = teapot.clone();
+                
+                //clonePot.setMaterial(mat);
+                clonePot.setLocalTranslation(x * .5f, 0, y * .5f);
+                clonePot.setLocalScale(.15f);
+                
+                LodControl control = new LodControl();
+                clonePot.addControl(control);
+                rootNode.attachChild(clonePot);
+            }
+        }
+
+        cam.setLocation(new Vector3f(8.378951f, 5.4324f, 8.795956f));
+        cam.setRotation(new Quaternion(-0.083419204f, 0.90370524f, -0.20599906f, -0.36595422f));
+    }
+
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainFractalGridTest.java b/engine/src/test/jme3test/terrain/TerrainFractalGridTest.java
new file mode 100644
index 0000000..499a8d6
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainFractalGridTest.java
@@ -0,0 +1,147 @@
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+
+public class TerrainFractalGridTest extends SimpleApplication {
+
+    private Material mat_terrain;
+    private TerrainGrid terrain;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+
+    public static void main(final String[] args) {
+        TerrainFractalGridTest app = new TerrainFractalGridTest();
+        app.start();
+    }
+    private CharacterControl player3;
+    private FractalSum base;
+    private PerturbFilter perturb;
+    private OptimizedErode therm;
+    private SmoothFilter smooth;
+    private IterativeFilter iterate;
+
+    @Override
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(100f);
+        ScreenshotAppState state = new ScreenshotAppState();
+        this.stateManager.attach(state);
+
+        // TERRAIN TEXTURE material
+        this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
+
+        // Parameters to material:
+        // regionXColorMap: X = 1..4 the texture that should be appliad to state X
+        // regionX: a Vector3f containing the following information:
+        //      regionX.x: the start height of the region
+        //      regionX.y: the end height of the region
+        //      regionX.z: the texture scale for the region
+        //  it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
+        // slopeColorMap: the texture to be used for cliffs, and steep mountain sites
+        // slopeTileFactor: the texture scale for slopes
+        // terrainSize: the total size of the terrain (used for scaling the texture)
+        // GRASS texture
+        Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region1ColorMap", grass);
+        this.mat_terrain.setVector3("region1", new Vector3f(15, 200, this.grassScale));
+
+        // DIRT texture
+        Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region2ColorMap", dirt);
+        this.mat_terrain.setVector3("region2", new Vector3f(0, 20, this.dirtScale));
+
+        // ROCK texture
+        Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region3ColorMap", rock);
+        this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("region4ColorMap", rock);
+        this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("slopeColorMap", rock);
+        this.mat_terrain.setFloat("slopeTileFactor", 32);
+
+        this.mat_terrain.setFloat("terrainSize", 513);
+
+        this.base = new FractalSum();
+        this.base.setRoughness(0.7f);
+        this.base.setFrequency(1.0f);
+        this.base.setAmplitude(1.0f);
+        this.base.setLacunarity(2.12f);
+        this.base.setOctaves(8);
+        this.base.setScale(0.02125f);
+        this.base.addModulator(new NoiseModulator() {
+
+            @Override
+            public float value(float... in) {
+                return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
+            }
+        });
+
+        FilteredBasis ground = new FilteredBasis(this.base);
+
+        this.perturb = new PerturbFilter();
+        this.perturb.setMagnitude(0.119f);
+
+        this.therm = new OptimizedErode();
+        this.therm.setRadius(5);
+        this.therm.setTalus(0.011f);
+
+        this.smooth = new SmoothFilter();
+        this.smooth.setRadius(1);
+        this.smooth.setEffect(0.7f);
+
+        this.iterate = new IterativeFilter();
+        this.iterate.addPreFilter(this.perturb);
+        this.iterate.addPostFilter(this.smooth);
+        this.iterate.setFilter(this.therm);
+        this.iterate.setIterations(1);
+
+        ground.addPreFilter(this.iterate);
+
+        this.terrain = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f));
+
+        this.terrain.setMaterial(this.mat_terrain);
+        this.terrain.setLocalTranslation(0, 0, 0);
+        this.terrain.setLocalScale(2f, 1f, 2f);
+        this.rootNode.attachChild(this.terrain);
+
+        TerrainLodControl control = new TerrainLodControl(this.terrain, this.getCamera());
+        control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch size, and a multiplier
+        this.terrain.addControl(control);
+
+
+
+        this.getCamera().setLocation(new Vector3f(0, 300, 0));
+
+        this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+        
+    }
+
+    @Override
+    public void simpleUpdate(final float tpf) {
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainGridAlphaMapTest.java b/engine/src/test/jme3test/terrain/TerrainGridAlphaMapTest.java
new file mode 100644
index 0000000..6c45e72
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainGridAlphaMapTest.java
@@ -0,0 +1,359 @@
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
+
+public class TerrainGridAlphaMapTest extends SimpleApplication {
+
+    private TerrainGrid terrain;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    private boolean usePhysics = false;
+
+    public static void main(final String[] args) {
+        TerrainGridAlphaMapTest app = new TerrainGridAlphaMapTest();
+        app.start();
+    }
+    private CharacterControl player3;
+    private FractalSum base;
+    private PerturbFilter perturb;
+    private OptimizedErode therm;
+    private SmoothFilter smooth;
+    private IterativeFilter iterate;
+    private Material material;
+    private Material matWire;
+
+    @Override
+    public void simpleInitApp() {
+        DirectionalLight sun = new DirectionalLight();
+        sun.setColor(ColorRGBA.White);
+        sun.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(sun);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(1.3f));
+        rootNode.addLight(al);
+
+        File file = new File("TerrainGridTestData.zip");
+        if (!file.exists()) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+        } else {
+            assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+        }
+
+        this.flyCam.setMoveSpeed(100f);
+        ScreenshotAppState state = new ScreenshotAppState();
+        this.stateManager.attach(state);
+
+        // TERRAIN TEXTURE material
+        material = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        material.setBoolean("useTriPlanarMapping", false);
+        //material.setBoolean("isTerrainGrid", true);
+        material.setFloat("Shininess", 0.0f);
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        material.setTexture("DiffuseMap", grass);
+        material.setFloat("DiffuseMap_0_scale", grassScale);
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        material.setTexture("DiffuseMap_1", dirt);
+        material.setFloat("DiffuseMap_1_scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        material.setTexture("DiffuseMap_2", rock);
+        material.setFloat("DiffuseMap_2_scale", rockScale);
+
+        // WIREFRAME material
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+
+        this.base = new FractalSum();
+        this.base.setRoughness(0.7f);
+        this.base.setFrequency(1.0f);
+        this.base.setAmplitude(1.0f);
+        this.base.setLacunarity(2.12f);
+        this.base.setOctaves(8);
+        this.base.setScale(0.02125f);
+        this.base.addModulator(new NoiseModulator() {
+
+            @Override
+            public float value(float... in) {
+                return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
+            }
+        });
+
+        FilteredBasis ground = new FilteredBasis(this.base);
+
+        this.perturb = new PerturbFilter();
+        this.perturb.setMagnitude(0.119f);
+
+        this.therm = new OptimizedErode();
+        this.therm.setRadius(5);
+        this.therm.setTalus(0.011f);
+
+        this.smooth = new SmoothFilter();
+        this.smooth.setRadius(1);
+        this.smooth.setEffect(0.7f);
+
+        this.iterate = new IterativeFilter();
+        this.iterate.addPreFilter(this.perturb);
+        this.iterate.addPostFilter(this.smooth);
+        this.iterate.setFilter(this.therm);
+        this.iterate.setIterations(1);
+
+        ground.addPreFilter(this.iterate);
+
+        this.terrain = new TerrainGrid("terrain", 33, 257, new FractalTileLoader(ground, 256));
+        this.terrain.setMaterial(this.material);
+
+        this.terrain.setLocalTranslation(0, 0, 0);
+        this.terrain.setLocalScale(2f, 1f, 2f);
+        this.rootNode.attachChild(this.terrain);
+
+        List<Camera> cameras = new ArrayList<Camera>();
+        cameras.add(this.getCamera());
+        TerrainLodControl control = new TerrainLodControl(this.terrain, cameras);
+        control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier
+        this.terrain.addControl(control);
+
+        final BulletAppState bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+
+
+        this.getCamera().setLocation(new Vector3f(0, 256, 0));
+
+        this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+        if (usePhysics) {
+            CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+            player3 = new CharacterControl(capsuleShape, 0.5f);
+            player3.setJumpSpeed(20);
+            player3.setFallSpeed(10);
+            player3.setGravity(10);
+
+            player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+            bulletAppState.getPhysicsSpace().add(player3);
+
+        }
+        terrain.addListener(new TerrainGridListener() {
+
+            public void gridMoved(Vector3f newCenter) {
+            }
+
+            public void tileAttached(Vector3f cell, TerrainQuad quad) {
+                Texture alpha = null;
+                try {
+                    alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_" + (int)cell.x+ "_" + (int)cell.z + ".png");
+                } catch (Exception e) {
+                    alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_default.png");
+                }
+                quad.getMaterial().setTexture("AlphaMap", alpha);
+                if (usePhysics) {
+                    quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+                    bulletAppState.getPhysicsSpace().add(quad);
+                }
+                updateMarkerElevations();
+            }
+
+            public void tileDetached(Vector3f cell, TerrainQuad quad) {
+                if (usePhysics) {
+                    bulletAppState.getPhysicsSpace().remove(quad);
+                    quad.removeControl(RigidBodyControl.class);
+                }
+                updateMarkerElevations();
+            }
+        });
+        
+        this.initKeys();
+    
+        markers = new Node();
+        rootNode.attachChild(markers);
+        createMarkerPoints(1);
+    }
+    
+    Node markers;
+    
+    
+    private void createMarkerPoints(float count) {
+        Node center = createAxisMarker(10);
+        markers.attachChild(center);
+        
+        float xS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2);
+        float zS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2);
+        float xSi = xS;
+        float zSi = zS;
+        for (int x=0; x<count*2; x++) {
+            for (int z=0; z<count*2; z++) {
+                Node m = createAxisMarker(5);
+                m.setLocalTranslation(xSi, 0, zSi);
+                markers.attachChild(m);
+                zSi += terrain.getTerrainSize();
+            }
+            zSi = zS;
+            xSi += terrain.getTerrainSize();
+        }
+    }
+    
+    private void updateMarkerElevations() {
+        for (Spatial s : markers.getChildren()) {
+            float h = terrain.getHeight(new Vector2f(s.getLocalTranslation().x, s.getLocalTranslation().z));
+            s.setLocalTranslation(s.getLocalTranslation().x, h+1, s.getLocalTranslation().z);
+        }
+    }
+    
+    private void initKeys() {
+        // You can map one or several inputs to one named action
+        this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+        this.inputManager.addListener(this.actionListener, "Lefts");
+        this.inputManager.addListener(this.actionListener, "Rights");
+        this.inputManager.addListener(this.actionListener, "Ups");
+        this.inputManager.addListener(this.actionListener, "Downs");
+        this.inputManager.addListener(this.actionListener, "Jumps");
+    }
+    private boolean left;
+    private boolean right;
+    private boolean up;
+    private boolean down;
+    private final ActionListener actionListener = new ActionListener() {
+
+        @Override
+        public void onAction(final String name, final boolean keyPressed, final float tpf) {
+            if (name.equals("Lefts")) {
+                if (keyPressed) {
+                    TerrainGridAlphaMapTest.this.left = true;
+                } else {
+                    TerrainGridAlphaMapTest.this.left = false;
+                }
+            } else if (name.equals("Rights")) {
+                if (keyPressed) {
+                    TerrainGridAlphaMapTest.this.right = true;
+                } else {
+                    TerrainGridAlphaMapTest.this.right = false;
+                }
+            } else if (name.equals("Ups")) {
+                if (keyPressed) {
+                    TerrainGridAlphaMapTest.this.up = true;
+                } else {
+                    TerrainGridAlphaMapTest.this.up = false;
+                }
+            } else if (name.equals("Downs")) {
+                if (keyPressed) {
+                    TerrainGridAlphaMapTest.this.down = true;
+                } else {
+                    TerrainGridAlphaMapTest.this.down = false;
+                }
+            } else if (name.equals("Jumps")) {
+                TerrainGridAlphaMapTest.this.player3.jump();
+            }
+        }
+    };
+    private final Vector3f walkDirection = new Vector3f();
+
+    @Override
+    public void simpleUpdate(final float tpf) {
+        Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+        Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+        this.walkDirection.set(0, 0, 0);
+        if (this.left) {
+            this.walkDirection.addLocal(camLeft);
+        }
+        if (this.right) {
+            this.walkDirection.addLocal(camLeft.negate());
+        }
+        if (this.up) {
+            this.walkDirection.addLocal(camDir);
+        }
+        if (this.down) {
+            this.walkDirection.addLocal(camDir.negate());
+        }
+
+        if (usePhysics) {
+            this.player3.setWalkDirection(this.walkDirection);
+            this.cam.setLocation(this.player3.getPhysicsLocation());
+        }
+    }
+    
+    protected Node createAxisMarker(float arrowSize) {
+
+        Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        redMat.getAdditionalRenderState().setWireframe(true);
+        redMat.setColor("Color", ColorRGBA.Red);
+        
+        Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        greenMat.getAdditionalRenderState().setWireframe(true);
+        greenMat.setColor("Color", ColorRGBA.Green);
+        
+        Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        blueMat.getAdditionalRenderState().setWireframe(true);
+        blueMat.setColor("Color", ColorRGBA.Blue);
+
+        Node axis = new Node();
+
+        // create arrows
+        Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0)));
+        arrowX.setMaterial(redMat);
+        Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0)));
+        arrowY.setMaterial(greenMat);
+        Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize)));
+        arrowZ.setMaterial(blueMat);
+        axis.attachChild(arrowX);
+        axis.attachChild(arrowY);
+        axis.attachChild(arrowZ);
+
+        //axis.setModelBound(new BoundingBox());
+        return axis;
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainGridSerializationTest.java b/engine/src/test/jme3test/terrain/TerrainGridSerializationTest.java
new file mode 100644
index 0000000..ec07e7a
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainGridSerializationTest.java
@@ -0,0 +1,179 @@
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+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.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import java.io.File;
+
+public class TerrainGridSerializationTest extends SimpleApplication {
+
+    private TerrainGrid terrain;
+    private boolean usePhysics = true;
+
+    public static void main(final String[] args) {
+        TerrainGridSerializationTest app = new TerrainGridSerializationTest();
+        app.start();
+    }
+    private CharacterControl player3;
+
+    @Override
+    public void simpleInitApp() {
+        File file = new File("TerrainGridTestData.zip");
+        if (!file.exists()) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+        } else {
+            assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+        }
+
+        this.flyCam.setMoveSpeed(100f);
+        ScreenshotAppState state = new ScreenshotAppState();
+        this.stateManager.attach(state);
+
+        this.terrain= (TerrainGrid) assetManager.loadModel("TerrainGrid/TerrainGrid.j3o");
+        
+        this.rootNode.attachChild(this.terrain);
+        
+        TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        this.terrain.addControl(control);
+        
+        final BulletAppState bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+
+        this.getCamera().setLocation(new Vector3f(0, 256, 0));
+
+        this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+        if (usePhysics) {
+            CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+            player3 = new CharacterControl(capsuleShape, 0.5f);
+            player3.setJumpSpeed(20);
+            player3.setFallSpeed(10);
+            player3.setGravity(10);
+
+            player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+            bulletAppState.getPhysicsSpace().add(player3);
+
+            terrain.addListener(new TerrainGridListener() {
+
+                public void gridMoved(Vector3f newCenter) {
+                }
+
+                public Material tileLoaded(Material material, Vector3f cell) {
+                    return material;
+                }
+
+                public void tileAttached(Vector3f cell, TerrainQuad quad) {
+                    //workaround for bugged test j3o's
+                    while(quad.getControl(RigidBodyControl.class)!=null){
+                        quad.removeControl(RigidBodyControl.class);
+                    }
+                    quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+                    bulletAppState.getPhysicsSpace().add(quad);
+                }
+
+                public void tileDetached(Vector3f cell, TerrainQuad quad) {
+                    bulletAppState.getPhysicsSpace().remove(quad);
+                    quad.removeControl(RigidBodyControl.class);
+                }
+
+            });
+        }
+        
+        this.initKeys();
+    }
+
+    private void initKeys() {
+        // You can map one or several inputs to one named action
+        this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+        this.inputManager.addListener(this.actionListener, "Lefts");
+        this.inputManager.addListener(this.actionListener, "Rights");
+        this.inputManager.addListener(this.actionListener, "Ups");
+        this.inputManager.addListener(this.actionListener, "Downs");
+        this.inputManager.addListener(this.actionListener, "Jumps");
+    }
+    private boolean left;
+    private boolean right;
+    private boolean up;
+    private boolean down;
+    private final ActionListener actionListener = new ActionListener() {
+
+        @Override
+        public void onAction(final String name, final boolean keyPressed, final float tpf) {
+            if (name.equals("Lefts")) {
+                if (keyPressed) {
+                    TerrainGridSerializationTest.this.left = true;
+                } else {
+                    TerrainGridSerializationTest.this.left = false;
+                }
+            } else if (name.equals("Rights")) {
+                if (keyPressed) {
+                    TerrainGridSerializationTest.this.right = true;
+                } else {
+                    TerrainGridSerializationTest.this.right = false;
+                }
+            } else if (name.equals("Ups")) {
+                if (keyPressed) {
+                    TerrainGridSerializationTest.this.up = true;
+                } else {
+                    TerrainGridSerializationTest.this.up = false;
+                }
+            } else if (name.equals("Downs")) {
+                if (keyPressed) {
+                    TerrainGridSerializationTest.this.down = true;
+                } else {
+                    TerrainGridSerializationTest.this.down = false;
+                }
+            } else if (name.equals("Jumps")) {
+                TerrainGridSerializationTest.this.player3.jump();
+            }
+        }
+    };
+    private final Vector3f walkDirection = new Vector3f();
+
+    @Override
+    public void simpleUpdate(final float tpf) {
+        Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+        Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+        this.walkDirection.set(0, 0, 0);
+        if (this.left) {
+            this.walkDirection.addLocal(camLeft);
+        }
+        if (this.right) {
+            this.walkDirection.addLocal(camLeft.negate());
+        }
+        if (this.up) {
+            this.walkDirection.addLocal(camDir);
+        }
+        if (this.down) {
+            this.walkDirection.addLocal(camDir.negate());
+        }
+
+        if (usePhysics) {
+            this.player3.setWalkDirection(this.walkDirection);
+            this.cam.setLocation(this.player3.getPhysicsLocation());
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainGridTest.java b/engine/src/test/jme3test/terrain/TerrainGridTest.java
new file mode 100644
index 0000000..85f8ee4
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainGridTest.java
@@ -0,0 +1,239 @@
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.ImageTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.Namer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.File;
+
+public class TerrainGridTest extends SimpleApplication {
+
+    private Material mat_terrain;
+    private TerrainGrid terrain;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    private boolean usePhysics = false;
+    private boolean physicsAdded = false;
+
+    public static void main(final String[] args) {
+        TerrainGridTest app = new TerrainGridTest();
+        app.start();
+    }
+    private CharacterControl player3;
+
+    @Override
+    public void simpleInitApp() {
+        File file = new File("TerrainGridTestData.zip");
+        if (!file.exists()) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+        } else {
+            assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+        }
+
+        this.flyCam.setMoveSpeed(100f);
+        ScreenshotAppState state = new ScreenshotAppState();
+        this.stateManager.attach(state);
+
+        // TERRAIN TEXTURE material
+        this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
+
+        // Parameters to material:
+        // regionXColorMap: X = 1..4 the texture that should be appliad to state X
+        // regionX: a Vector3f containing the following information:
+        //      regionX.x: the start height of the region
+        //      regionX.y: the end height of the region
+        //      regionX.z: the texture scale for the region
+        //  it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
+        // slopeColorMap: the texture to be used for cliffs, and steep mountain sites
+        // slopeTileFactor: the texture scale for slopes
+        // terrainSize: the total size of the terrain (used for scaling the texture)
+        // GRASS texture
+        Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region1ColorMap", grass);
+        this.mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale));
+
+        // DIRT texture
+        Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region2ColorMap", dirt);
+        this.mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale));
+
+        // ROCK texture
+        Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region3ColorMap", rock);
+        this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("region4ColorMap", rock);
+        this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("slopeColorMap", rock);
+        this.mat_terrain.setFloat("slopeTileFactor", 32);
+
+        this.mat_terrain.setFloat("terrainSize", 129);
+
+        this.terrain = new TerrainGrid("terrain", 65, 257, new ImageTileLoader(assetManager, new Namer() {
+
+            public String getName(int x, int y) {
+                return "Scenes/TerrainMountains/terrain_" + x + "_" + y + ".png";
+            }
+        }));
+        
+        this.terrain.setMaterial(mat_terrain);
+        this.terrain.setLocalTranslation(0, 0, 0);
+        this.terrain.setLocalScale(1f, 1f, 1f);
+        this.rootNode.attachChild(this.terrain);
+
+        TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        this.terrain.addControl(control);
+
+        final BulletAppState bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+
+        this.getCamera().setLocation(new Vector3f(0, 200, 0));
+
+        this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+        rootNode.addLight(light);
+        
+        if (usePhysics) {
+            CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+            player3 = new CharacterControl(capsuleShape, 0.5f);
+            player3.setJumpSpeed(20);
+            player3.setFallSpeed(10);
+            player3.setGravity(10);
+
+            player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+            bulletAppState.getPhysicsSpace().add(player3);
+
+            terrain.addListener(new TerrainGridListener() {
+
+                public void gridMoved(Vector3f newCenter) {
+                }
+
+                public Material tileLoaded(Material material, Vector3f cell) {
+                    return material;
+                }
+
+                public void tileAttached(Vector3f cell, TerrainQuad quad) {
+                    while(quad.getControl(RigidBodyControl.class)!=null){
+                        quad.removeControl(RigidBodyControl.class);
+                    }
+                    quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+                    bulletAppState.getPhysicsSpace().add(quad);
+                }
+
+                public void tileDetached(Vector3f cell, TerrainQuad quad) {
+                    bulletAppState.getPhysicsSpace().remove(quad);
+                    quad.removeControl(RigidBodyControl.class);
+                }
+
+            });
+        }
+        
+        this.initKeys();
+    }
+
+    private void initKeys() {
+        // You can map one or several inputs to one named action
+        this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+        this.inputManager.addListener(this.actionListener, "Lefts");
+        this.inputManager.addListener(this.actionListener, "Rights");
+        this.inputManager.addListener(this.actionListener, "Ups");
+        this.inputManager.addListener(this.actionListener, "Downs");
+        this.inputManager.addListener(this.actionListener, "Jumps");
+    }
+    private boolean left;
+    private boolean right;
+    private boolean up;
+    private boolean down;
+    private final ActionListener actionListener = new ActionListener() {
+
+        @Override
+        public void onAction(final String name, final boolean keyPressed, final float tpf) {
+            if (name.equals("Lefts")) {
+                if (keyPressed) {
+                    TerrainGridTest.this.left = true;
+                } else {
+                    TerrainGridTest.this.left = false;
+                }
+            } else if (name.equals("Rights")) {
+                if (keyPressed) {
+                    TerrainGridTest.this.right = true;
+                } else {
+                    TerrainGridTest.this.right = false;
+                }
+            } else if (name.equals("Ups")) {
+                if (keyPressed) {
+                    TerrainGridTest.this.up = true;
+                } else {
+                    TerrainGridTest.this.up = false;
+                }
+            } else if (name.equals("Downs")) {
+                if (keyPressed) {
+                    TerrainGridTest.this.down = true;
+                } else {
+                    TerrainGridTest.this.down = false;
+                }
+            } else if (name.equals("Jumps")) {
+                TerrainGridTest.this.player3.jump();
+            }
+        }
+    };
+    private final Vector3f walkDirection = new Vector3f();
+
+    @Override
+    public void simpleUpdate(final float tpf) {
+        Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+        Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+        this.walkDirection.set(0, 0, 0);
+        if (this.left) {
+            this.walkDirection.addLocal(camLeft);
+        }
+        if (this.right) {
+            this.walkDirection.addLocal(camLeft.negate());
+        }
+        if (this.up) {
+            this.walkDirection.addLocal(camDir);
+        }
+        if (this.down) {
+            this.walkDirection.addLocal(camDir.negate());
+        }
+
+        if (usePhysics) {
+            this.player3.setWalkDirection(this.walkDirection);
+            this.cam.setLocation(this.player3.getPhysicsLocation());
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainGridTileLoaderTest.java b/engine/src/test/jme3test/terrain/TerrainGridTileLoaderTest.java
new file mode 100644
index 0000000..86fbbf9
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainGridTileLoaderTest.java
@@ -0,0 +1,236 @@
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+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.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.AssetTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.File;
+
+public class TerrainGridTileLoaderTest extends SimpleApplication {
+
+    private Material mat_terrain;
+    private TerrainGrid terrain;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    private boolean usePhysics = true;
+    private boolean physicsAdded = false;
+
+    public static void main(final String[] args) {
+        TerrainGridTileLoaderTest app = new TerrainGridTileLoaderTest();
+        app.start();
+    }
+    private CharacterControl player3;
+
+    @Override
+    public void simpleInitApp() {
+        File file = new File("TerrainGridTestData.zip");
+        if (!file.exists()) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+        } else {
+            assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+        }
+
+        this.flyCam.setMoveSpeed(100f);
+        ScreenshotAppState state = new ScreenshotAppState();
+        this.stateManager.attach(state);
+
+        // TERRAIN TEXTURE material
+        this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
+
+        // Parameters to material:
+        // regionXColorMap: X = 1..4 the texture that should be appliad to state X
+        // regionX: a Vector3f containing the following information:
+        //      regionX.x: the start height of the region
+        //      regionX.y: the end height of the region
+        //      regionX.z: the texture scale for the region
+        //  it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
+        // slopeColorMap: the texture to be used for cliffs, and steep mountain sites
+        // slopeTileFactor: the texture scale for slopes
+        // terrainSize: the total size of the terrain (used for scaling the texture)
+        // GRASS texture
+        Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region1ColorMap", grass);
+        this.mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale));
+
+        // DIRT texture
+        Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region2ColorMap", dirt);
+        this.mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale));
+
+        // ROCK texture
+        Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        this.mat_terrain.setTexture("region3ColorMap", rock);
+        this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("region4ColorMap", rock);
+        this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
+
+        this.mat_terrain.setTexture("slopeColorMap", rock);
+        this.mat_terrain.setFloat("slopeTileFactor", 32);
+
+        this.mat_terrain.setFloat("terrainSize", 129);
+//quad.getHeightMap(), terrain.getLocalScale()), 0
+        AssetTileLoader grid = new AssetTileLoader(assetManager, "testgrid", "TerrainGrid");
+        this.terrain = new TerrainGrid("terrain", 65, 257, grid);
+
+        this.terrain.setMaterial(this.mat_terrain);
+        this.terrain.setLocalTranslation(0, 0, 0);
+        this.terrain.setLocalScale(2f, 1f, 2f);
+//        try {
+//            BinaryExporter.getInstance().save(terrain, new File("/Users/normenhansen/Documents/Code/jme3/engine/src/test-data/TerrainGrid/"
+//                    + "TerrainGrid.j3o"));
+//        } catch (IOException ex) {
+//            Logger.getLogger(TerrainFractalGridTest.class.getName()).log(Level.SEVERE, null, ex);
+//        }
+        
+        this.rootNode.attachChild(this.terrain);
+        
+        TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        this.terrain.addControl(control);
+        
+        final BulletAppState bulletAppState = new BulletAppState();
+        stateManager.attach(bulletAppState);
+
+        this.getCamera().setLocation(new Vector3f(0, 256, 0));
+
+        this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+        if (usePhysics) {
+            CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+            player3 = new CharacterControl(capsuleShape, 0.5f);
+            player3.setJumpSpeed(20);
+            player3.setFallSpeed(10);
+            player3.setGravity(10);
+
+            player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+            bulletAppState.getPhysicsSpace().add(player3);
+
+            terrain.addListener(new TerrainGridListener() {
+
+                public void gridMoved(Vector3f newCenter) {
+                }
+
+                public Material tileLoaded(Material material, Vector3f cell) {
+                    return material;
+                }
+
+                public void tileAttached(Vector3f cell, TerrainQuad quad) {
+                    while(quad.getControl(RigidBodyControl.class)!=null){
+                        quad.removeControl(RigidBodyControl.class);
+                    }
+                    quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+                    bulletAppState.getPhysicsSpace().add(quad);
+                }
+
+                public void tileDetached(Vector3f cell, TerrainQuad quad) {
+                    bulletAppState.getPhysicsSpace().remove(quad);
+                    quad.removeControl(RigidBodyControl.class);
+                }
+
+            });
+        }
+        
+        this.initKeys();
+    }
+
+    private void initKeys() {
+        // You can map one or several inputs to one named action
+        this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+        this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+        this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+        this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+        this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+        this.inputManager.addListener(this.actionListener, "Lefts");
+        this.inputManager.addListener(this.actionListener, "Rights");
+        this.inputManager.addListener(this.actionListener, "Ups");
+        this.inputManager.addListener(this.actionListener, "Downs");
+        this.inputManager.addListener(this.actionListener, "Jumps");
+    }
+    private boolean left;
+    private boolean right;
+    private boolean up;
+    private boolean down;
+    private final ActionListener actionListener = new ActionListener() {
+
+        @Override
+        public void onAction(final String name, final boolean keyPressed, final float tpf) {
+            if (name.equals("Lefts")) {
+                if (keyPressed) {
+                    TerrainGridTileLoaderTest.this.left = true;
+                } else {
+                    TerrainGridTileLoaderTest.this.left = false;
+                }
+            } else if (name.equals("Rights")) {
+                if (keyPressed) {
+                    TerrainGridTileLoaderTest.this.right = true;
+                } else {
+                    TerrainGridTileLoaderTest.this.right = false;
+                }
+            } else if (name.equals("Ups")) {
+                if (keyPressed) {
+                    TerrainGridTileLoaderTest.this.up = true;
+                } else {
+                    TerrainGridTileLoaderTest.this.up = false;
+                }
+            } else if (name.equals("Downs")) {
+                if (keyPressed) {
+                    TerrainGridTileLoaderTest.this.down = true;
+                } else {
+                    TerrainGridTileLoaderTest.this.down = false;
+                }
+            } else if (name.equals("Jumps")) {
+                TerrainGridTileLoaderTest.this.player3.jump();
+            }
+        }
+    };
+    private final Vector3f walkDirection = new Vector3f();
+
+    @Override
+    public void simpleUpdate(final float tpf) {
+        Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+        Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+        this.walkDirection.set(0, 0, 0);
+        if (this.left) {
+            this.walkDirection.addLocal(camLeft);
+        }
+        if (this.right) {
+            this.walkDirection.addLocal(camLeft.negate());
+        }
+        if (this.up) {
+            this.walkDirection.addLocal(camDir);
+        }
+        if (this.down) {
+            this.walkDirection.addLocal(camDir.negate());
+        }
+
+        if (usePhysics) {
+            this.player3.setWalkDirection(this.walkDirection);
+            this.cam.setLocation(this.player3.getPhysicsLocation());
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainTest.java b/engine/src/test/jme3test/terrain/TerrainTest.java
new file mode 100644
index 0000000..4180c47
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+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.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.asset.TextureKey;
+
+/**
+ * Demonstrates how to use terrain.
+ * The base terrain class it uses is TerrainQuad, which is a quad tree of actual
+ * meshes called TerainPatches.
+ * There are a couple options for the terrain in this test:
+ * The first is wireframe mode. Here you can see the underlying trianglestrip structure.
+ * You will notice some off lines; these are degenerate triangles and are part of the
+ * trianglestrip. They are only noticeable in wireframe mode.
+ * Second is Tri-Planar texture mode. Here the textures are rendered on all 3 axes and
+ * then blended together to reduce distortion and stretching.
+ * Third, which you have to modify the code to see, is Entropy LOD calculations.
+ * In the constructor for the TerrainQuad, un-comment the final parameter that is
+ * the LodPerspectiveCalculatorFactory. Then you will see the terrain flicker to start
+ * while it calculates the entropies. Once it is done, it will pick the best LOD value
+ * based on entropy. This method reduces "popping" of terrain greatly when LOD levels
+ * change. It is highly suggested you use it in your app.
+ *
+ * @author bowens
+ */
+public class TerrainTest extends SimpleApplication {
+
+    private TerrainQuad terrain;
+    Material matRock;
+    Material matWire;
+    boolean wireframe = false;
+    boolean triPlanar = false;
+    protected BitmapText hintText;
+    PointLight pl;
+    Geometry lightMdl;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+
+    public static void main(String[] args) {
+        TerrainTest app = new TerrainTest();
+        app.start();
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        loadHintText();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setupKeys();
+
+        // First, we load up our textures and the heightmap texture for the terrain
+
+        // TERRAIN TEXTURE material
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
+        matRock.setBoolean("useTriPlanarMapping", false);
+
+        // ALPHA map (for splat textures)
+        matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+
+        // HEIGHTMAP image (for the terrain heightmap)
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex1", grass);
+        matRock.setFloat("Tex1Scale", grassScale);
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex2", dirt);
+        matRock.setFloat("Tex2Scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex3", rock);
+        matRock.setFloat("Tex3Scale", rockScale);
+
+        // WIREFRAME material
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+
+        // CREATE HEIGHTMAP
+        AbstractHeightMap heightmap = null;
+        try {
+            //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);
+
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
+            heightmap.load();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        /*
+         * Here we create the actual terrain. The tiles will be 65x65, and the total size of the
+         * terrain will be 513x513. It uses the heightmap we created to generate the height values.
+         */
+        /**
+         * Optimal terrain patch size is 65 (64x64).
+         * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at
+         * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles...
+         */
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        terrain.addControl(control);
+        terrain.setMaterial(matRock);
+        terrain.setLocalTranslation(0, -100, 0);
+        terrain.setLocalScale(2f, 1f, 2f);
+        rootNode.attachChild(terrain);
+
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+        rootNode.addLight(light);
+
+        cam.setLocation(new Vector3f(0, 10, -10));
+        cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
+    }
+
+    public void loadHintText() {
+        hintText = new BitmapText(guiFont, false);
+        hintText.setSize(guiFont.getCharSet().getRenderedSize());
+        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+        hintText.setText("Hit T to switch to wireframe,  P to switch to tri-planar texturing");
+        guiNode.attachChild(hintText);
+    }
+
+    private void setupKeys() {
+        flyCam.setMoveSpeed(50);
+        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(actionListener, "wireframe");
+        inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addListener(actionListener, "triPlanar");
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("wireframe") && !pressed) {
+                wireframe = !wireframe;
+                if (!wireframe) {
+                    terrain.setMaterial(matWire);
+                } else {
+                    terrain.setMaterial(matRock);
+                }
+            } else if (name.equals("triPlanar") && !pressed) {
+                triPlanar = !triPlanar;
+                if (triPlanar) {
+                    matRock.setBoolean("useTriPlanarMapping", true);
+                    // planar textures don't use the mesh's texture coordinates but real world coordinates,
+                    // so we need to convert these texture coordinate scales into real world scales so it looks
+                    // the same when we switch to/from tr-planar mode
+                    matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale));
+                    matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale));
+                    matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale));
+                } else {
+                    matRock.setBoolean("useTriPlanarMapping", false);
+                    matRock.setFloat("Tex1Scale", grassScale);
+                    matRock.setFloat("Tex2Scale", dirtScale);
+                    matRock.setFloat("Tex3Scale", rockScale);
+                }
+            }
+        }
+    };
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainTestAdvanced.java b/engine/src/test/jme3test/terrain/TerrainTestAdvanced.java
new file mode 100644
index 0000000..92a88d3
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainTestAdvanced.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.font.BitmapText;
+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.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.SkyFactory;
+import com.jme3.scene.Node;
+import com.jme3.scene.debug.Arrow;
+
+/**
+ * Uses the terrain's lighting texture with normal maps and lights.
+ *
+ * @author bowens
+ */
+public class TerrainTestAdvanced extends SimpleApplication {
+
+    private TerrainQuad terrain;
+    Material matTerrain;
+    Material matWire;
+    boolean wireframe = false;
+    boolean triPlanar = false;
+    boolean wardiso = false;
+    boolean minnaert = false;
+    protected BitmapText hintText;
+    PointLight pl;
+    Geometry lightMdl;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+
+    public static void main(String[] args) {
+        TerrainTestAdvanced app = new TerrainTestAdvanced();
+        app.start();
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        loadHintText();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setupKeys();
+
+        // First, we load up our textures and the heightmap texture for the terrain
+
+        // TERRAIN TEXTURE material
+        matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matTerrain.setBoolean("useTriPlanarMapping", false);
+        matTerrain.setFloat("Shininess", 0.0f);
+
+        // ALPHA map (for splat textures)
+        matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png"));
+        matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png"));
+
+        // HEIGHTMAP image (for the terrain heightmap)
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        //matTerrain.setTexture("DiffuseMap_1", grass);
+        //matTerrain.setFloat("DiffuseMap_1_scale", grassScale);
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap", dirt);
+        matTerrain.setFloat("DiffuseMap_0_scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        //matTerrain.setTexture("DiffuseMap_2", rock);
+        //matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
+
+        // BRICK texture
+        Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg");
+        brick.setWrap(WrapMode.Repeat);
+        //matTerrain.setTexture("DiffuseMap_3", brick);
+        //matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
+
+        // RIVER ROCK texture
+        Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
+        riverRock.setWrap(WrapMode.Repeat);
+        //matTerrain.setTexture("DiffuseMap_4", riverRock);
+        //matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
+
+
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("NormalMap", normalMap0);
+        //matTerrain.setTexture("NormalMap_1", normalMap2);
+        //matTerrain.setTexture("NormalMap_2", normalMap2);
+        //matTerrain.setTexture("NormalMap_4", normalMap2);
+
+        // WIREFRAME material
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+
+        //createSky();
+
+        // CREATE HEIGHTMAP
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f);
+            heightmap.load();
+            heightmap.smooth(0.9f, 1);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        /*
+         * Here we create the actual terrain. The tiles will be 65x65, and the total size of the
+         * terrain will be 513x513. It uses the heightmap we created to generate the height values.
+         */
+        /**
+         * Optimal terrain patch size is 65 (64x64).
+         * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at
+         * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles...
+         */
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
+        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        terrain.addControl(control);
+        terrain.setMaterial(matTerrain);
+        terrain.setModelBound(new BoundingBox());
+        terrain.updateModelBound();
+        terrain.setLocalTranslation(0, -100, 0);
+        terrain.setLocalScale(1f, 1f, 1f);
+        rootNode.attachChild(terrain);
+        
+        Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+        //terrain.generateDebugTangents(debugMat);
+
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalize());
+        rootNode.addLight(light);
+
+        cam.setLocation(new Vector3f(0, 10, -10));
+        cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
+        flyCam.setMoveSpeed(400);
+        
+        rootNode.attachChild(createAxisMarker(20));
+    }
+
+    public void loadHintText() {
+        hintText = new BitmapText(guiFont, false);
+        hintText.setSize(guiFont.getCharSet().getRenderedSize());
+        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+        hintText.setText("Hit T to switch to wireframe,  P to switch to tri-planar texturing");
+        guiNode.attachChild(hintText);
+    }
+
+    private void setupKeys() {
+        flyCam.setMoveSpeed(50);
+        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(actionListener, "wireframe");
+        inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addListener(actionListener, "triPlanar");
+        inputManager.addMapping("WardIso", new KeyTrigger(KeyInput.KEY_9));
+        inputManager.addListener(actionListener, "WardIso");
+        inputManager.addMapping("Minnaert", new KeyTrigger(KeyInput.KEY_0));
+        inputManager.addListener(actionListener, "Minnaert");
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("wireframe") && !pressed) {
+                wireframe = !wireframe;
+                if (!wireframe) {
+                    terrain.setMaterial(matWire);
+                } else {
+                    terrain.setMaterial(matTerrain);
+                }
+            } else if (name.equals("triPlanar") && !pressed) {
+                triPlanar = !triPlanar;
+                if (triPlanar) {
+                    matTerrain.setBoolean("useTriPlanarMapping", true);
+                    // planar textures don't use the mesh's texture coordinates but real world coordinates,
+                    // so we need to convert these texture coordinate scales into real world scales so it looks
+                    // the same when we switch to/from tr-planar mode
+                    matTerrain.setFloat("DiffuseMap_0_scale", 1f / (float) (512f / grassScale));
+                    matTerrain.setFloat("DiffuseMap_1_scale", 1f / (float) (512f / dirtScale));
+                    matTerrain.setFloat("DiffuseMap_2_scale", 1f / (float) (512f / rockScale));
+                    matTerrain.setFloat("DiffuseMap_3_scale", 1f / (float) (512f / rockScale));
+                    matTerrain.setFloat("DiffuseMap_4_scale", 1f / (float) (512f / rockScale));
+                } else {
+                    matTerrain.setBoolean("useTriPlanarMapping", false);
+                    matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
+                    matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
+                    matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
+                    matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
+                    matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
+                }
+            }
+        }
+    };
+
+    private void createSky() {
+        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
+        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
+        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
+        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
+        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
+        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
+
+        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
+        rootNode.attachChild(sky);
+    }
+    
+    protected Node createAxisMarker(float arrowSize) {
+
+        Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        redMat.getAdditionalRenderState().setWireframe(true);
+        redMat.setColor("Color", ColorRGBA.Red);
+        
+        Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        greenMat.getAdditionalRenderState().setWireframe(true);
+        greenMat.setColor("Color", ColorRGBA.Green);
+        
+        Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        blueMat.getAdditionalRenderState().setWireframe(true);
+        blueMat.setColor("Color", ColorRGBA.Blue);
+
+        Node axis = new Node();
+
+        // create arrows
+        Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0)));
+        arrowX.setMaterial(redMat);
+        Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0)));
+        arrowY.setMaterial(greenMat);
+        Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize)));
+        arrowZ.setMaterial(blueMat);
+        axis.attachChild(arrowX);
+        axis.attachChild(arrowY);
+        axis.attachChild(arrowZ);
+
+        //axis.setModelBound(new BoundingBox());
+        return axis;
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainTestCollision.java b/engine/src/test/jme3test/terrain/TerrainTestCollision.java
new file mode 100644
index 0000000..9bcd48a
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainTestCollision.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import jme3tools.converters.ImageToAwt;
+
+/**
+ * Creates a terrain object and a collision node to go with it. Then
+ * drops several balls from the sky that collide with the terrain
+ * and roll around.
+ * Left click to place a sphere on the ground where the crosshairs intersect the terrain.
+ * Hit keys 1 or 2 to raise/lower the terrain at that spot.
+ *
+ * @author Brent Owens
+ */
+public class TerrainTestCollision extends SimpleApplication {
+
+    TerrainQuad terrain;
+    Node terrainPhysicsNode;
+    Material matRock;
+    Material matWire;
+    boolean wireframe = false;
+    protected BitmapText hintText;
+    PointLight pl;
+    Geometry lightMdl;
+    Geometry collisionMarker;
+    private BulletAppState bulletAppState;
+    Geometry collisionSphere;
+    Geometry collisionBox;
+    Geometry selectedCollisionObject;
+
+    public static void main(String[] args) {
+        TerrainTestCollision app = new TerrainTestCollision();
+        app.start();
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+        loadHintText();
+        initCrossHairs();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+        stateManager.attach(bulletAppState);
+        setupKeys();
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
+        matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex1", grass);
+        matRock.setFloat("Tex1Scale", 64f);
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex2", dirt);
+        matRock.setFloat("Tex2Scale", 32f);
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("Tex3", rock);
+        matRock.setFloat("Tex3Scale", 128f);
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+            heightmap.load();
+
+        } catch (Exception e) {
+        }
+
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        terrain.addControl(control);
+        terrain.setMaterial(matRock);
+        terrain.setLocalScale(new Vector3f(2, 2, 2));
+        terrain.setLocked(false); // unlock it so we can edit the height
+        rootNode.attachChild(terrain);
+
+
+        /**
+         * Create PhysicsRigidBodyControl for collision
+         */
+        terrain.addControl(new RigidBodyControl(0));
+        bulletAppState.getPhysicsSpace().addAll(terrain);
+
+
+        // Add 5 physics spheres to the world, with random sizes and positions
+        // let them drop from the sky
+        for (int i = 0; i < 5; i++) {
+            float r = (float) (8 * Math.random());
+            Geometry sphere = new Geometry("cannonball", new Sphere(10, 10, r));
+            sphere.setMaterial(matWire);
+            float x = (float) (20 * Math.random()) - 40; // random position
+            float y = (float) (20 * Math.random()) - 40; // random position
+            float z = (float) (20 * Math.random()) - 40; // random position
+            sphere.setLocalTranslation(new Vector3f(x, 100 + y, z));
+            sphere.addControl(new RigidBodyControl(new SphereCollisionShape(r), 2));
+            rootNode.attachChild(sphere);
+            bulletAppState.getPhysicsSpace().add(sphere);
+        }
+
+        collisionBox = new Geometry("collisionBox", new Box(2, 2, 2));
+        collisionBox.setModelBound(new BoundingBox());
+        collisionBox.setLocalTranslation(new Vector3f(20, 95, 30));
+        collisionBox.setMaterial(matWire);
+        rootNode.attachChild(collisionBox);
+        selectedCollisionObject = collisionBox;
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(1, -0.5f, -0.1f).normalizeLocal());
+        dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f));
+        rootNode.addLight(dl);
+
+        cam.setLocation(new Vector3f(0, 25, -10));
+        cam.lookAtDirection(new Vector3f(0, -1, 0).normalizeLocal(), Vector3f.UNIT_Y);
+    }
+
+    public void loadHintText() {
+        hintText = new BitmapText(guiFont, false);
+        hintText.setSize(guiFont.getCharSet().getRenderedSize());
+        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+        //hintText.setText("Hit T to switch to wireframe");
+        hintText.setText("");
+        guiNode.attachChild(hintText);
+    }
+
+    protected void initCrossHairs() {
+        //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+
+    private void setupKeys() {
+        flyCam.setMoveSpeed(50);
+        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(actionListener, "wireframe");
+        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("Forwards", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("Backs", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addListener(actionListener, "Lefts");
+        inputManager.addListener(actionListener, "Rights");
+        inputManager.addListener(actionListener, "Ups");
+        inputManager.addListener(actionListener, "Downs");
+        inputManager.addListener(actionListener, "Forwards");
+        inputManager.addListener(actionListener, "Backs");
+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(actionListener, "shoot");
+        inputManager.addMapping("cameraDown", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+        inputManager.addListener(actionListener, "cameraDown");
+    }
+
+    @Override
+    public void update() {
+        super.update();
+    }
+
+    private void createCollisionMarker() {
+        Sphere s = new Sphere(6, 6, 1);
+        collisionMarker = new Geometry("collisionMarker");
+        collisionMarker.setMesh(s);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Orange);
+        collisionMarker.setMaterial(mat);
+        rootNode.attachChild(collisionMarker);
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String binding, boolean keyPressed, float tpf) {
+            if (binding.equals("wireframe") && !keyPressed) {
+                wireframe = !wireframe;
+                if (!wireframe) {
+                    terrain.setMaterial(matWire);
+                } else {
+                    terrain.setMaterial(matRock);
+                }
+            } else if (binding.equals("shoot") && !keyPressed) {
+
+                Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f);
+                Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f);
+                direction.subtractLocal(origin).normalizeLocal();
+
+
+                Ray ray = new Ray(origin, direction);
+                CollisionResults results = new CollisionResults();
+                int numCollisions = terrain.collideWith(ray, results);
+                if (numCollisions > 0) {
+                    CollisionResult hit = results.getClosestCollision();
+                    if (collisionMarker == null) {
+                        createCollisionMarker();
+                    }
+                    Vector2f loc = new Vector2f(hit.getContactPoint().x, hit.getContactPoint().z);
+                    float height = terrain.getHeight(loc);
+                    System.out.println("collide " + hit.getContactPoint() + ", height: " + height + ", distance: " + hit.getDistance());
+                    collisionMarker.setLocalTranslation(new Vector3f(hit.getContactPoint().x, height, hit.getContactPoint().z));
+                }
+            } else if (binding.equals("cameraDown") && !keyPressed) {
+                getCamera().lookAtDirection(new Vector3f(0, -1, 0), Vector3f.UNIT_Y);
+            } else if (binding.equals("Lefts") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(-0.5f, 0, 0);
+                testCollision(oldLoc);
+            } else if (binding.equals("Rights") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(0.5f, 0, 0);
+                testCollision(oldLoc);
+            } else if (binding.equals("Forwards") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(0, 0, 0.5f);
+                testCollision(oldLoc);
+            } else if (binding.equals("Backs") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(0, 0, -0.5f);
+                testCollision(oldLoc);
+            } else if (binding.equals("Ups") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(0, 0.5f, 0);
+                testCollision(oldLoc);
+            } else if (binding.equals("Downs") && !keyPressed) {
+                Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone();
+                selectedCollisionObject.move(0, -0.5f, 0);
+                testCollision(oldLoc);
+            }
+
+        }
+    };
+
+    private void testCollision(Vector3f oldLoc) {
+        if (terrain.collideWith(selectedCollisionObject.getWorldBound(), new CollisionResults()) > 0) {
+            selectedCollisionObject.setLocalTranslation(oldLoc);
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java b/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java
new file mode 100644
index 0000000..f813f08
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Brent Owens
+ */
+public class TerrainTestModifyHeight extends SimpleApplication {
+
+    private TerrainQuad terrain;
+    Material matTerrain;
+    Material matWire;
+    boolean wireframe = true;
+    boolean triPlanar = false;
+    boolean wardiso = false;
+    boolean minnaert = false;
+    protected BitmapText hintText;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    
+    private boolean raiseTerrain = false;
+    private boolean lowerTerrain = false;
+    
+    private Geometry marker;
+    private Geometry markerNormal;
+
+    public static void main(String[] args) {
+        TerrainTestModifyHeight app = new TerrainTestModifyHeight();
+        app.start();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf){
+        Vector3f intersection = getWorldIntersection();
+        updateHintText(intersection);
+        
+        if (raiseTerrain){
+            
+            if (intersection != null) {
+                adjustHeight(intersection, 64, tpf * 60);
+            }
+        }else if (lowerTerrain){
+            if (intersection != null) {
+                adjustHeight(intersection, 64, -tpf * 60);
+            }
+        }
+        
+        if (terrain != null && intersection != null) {
+            float h = terrain.getHeight(new Vector2f(intersection.x, intersection.z));
+            Vector3f tl = terrain.getWorldTranslation();
+            marker.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) );
+            markerNormal.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) );
+            
+            Vector3f normal = terrain.getNormal(new Vector2f(intersection.x, intersection.z));
+            ((Arrow)markerNormal.getMesh()).setArrowExtent(normal);
+        }
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        loadHintText();
+        initCrossHairs();
+        setupKeys();
+        
+        createMarker();
+
+        // WIREFRAME material
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+        
+        createTerrain();
+        //createTerrainGrid();
+        
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+        rootNode.addLight(light);
+
+        AmbientLight ambLight = new AmbientLight();
+        ambLight.setColor(new ColorRGBA(1f, 1f, 0.8f, 0.2f));
+        rootNode.addLight(ambLight);
+
+        cam.setLocation(new Vector3f(0, 256, 0));
+        cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X);
+    }
+    
+    public void loadHintText() {
+        hintText = new BitmapText(guiFont, false);
+        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+        hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain");
+        guiNode.attachChild(hintText);
+    }
+
+    public void updateHintText(Vector3f target) {
+        int x = (int) getCamera().getLocation().x;
+        int y = (int) getCamera().getLocation().y;
+        int z = (int) getCamera().getLocation().z;
+        String targetText = "";
+        if (target!= null)
+            targetText = "  intersect: "+target.toString();
+        hintText.setText("Press left mouse button to raise terrain, press right mouse button to lower terrain.  " + x + "," + y + "," + z+targetText);
+    }
+
+    protected void initCrossHairs() {
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+
+    private void setupKeys() {
+        flyCam.setMoveSpeed(100);
+        inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(actionListener, "wireframe");
+        inputManager.addMapping("Raise", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(actionListener, "Raise");
+        inputManager.addMapping("Lower", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+        inputManager.addListener(actionListener, "Lower");
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("wireframe") && !pressed) {
+                wireframe = !wireframe;
+                if (!wireframe) {
+                    terrain.setMaterial(matWire);
+                } else {
+                    terrain.setMaterial(matTerrain);
+                }
+            } else if (name.equals("Raise")) {
+                raiseTerrain = pressed;
+            } else if (name.equals("Lower")) {
+                lowerTerrain = pressed;
+            }
+        }
+    };
+
+    private void adjustHeight(Vector3f loc, float radius, float height) {
+
+        // offset it by radius because in the loop we iterate through 2 radii
+        int radiusStepsX = (int) (radius / terrain.getLocalScale().x);
+        int radiusStepsZ = (int) (radius / terrain.getLocalScale().z);
+
+        float xStepAmount = terrain.getLocalScale().x;
+        float zStepAmount = terrain.getLocalScale().z;
+        long start = System.currentTimeMillis();
+        List<Vector2f> locs = new ArrayList<Vector2f>();
+        List<Float> heights = new ArrayList<Float>();
+        
+        for (int z = -radiusStepsZ; z < radiusStepsZ; z++) {
+            for (int x = -radiusStepsX; x < radiusStepsX; x++) {
+
+                float locX = loc.x + (x * xStepAmount);
+                float locZ = loc.z + (z * zStepAmount);
+
+                if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {
+                    // see if it is in the radius of the tool
+                    float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z);
+                    locs.add(new Vector2f(locX, locZ));
+                    heights.add(h);
+                }
+            }
+        }
+
+        terrain.adjustHeight(locs, heights);
+        //System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms");
+        terrain.updateModelBound();
+    }
+
+    private boolean isInRadius(float x, float y, float radius) {
+        Vector2f point = new Vector2f(x, y);
+        // return true if the distance is less than equal to the radius
+        return point.length() <= radius;
+    }
+
+    private float calculateHeight(float radius, float heightFactor, float x, float z) {
+        // find percentage for each 'unit' in radius
+        Vector2f point = new Vector2f(x, z);
+        float val = point.length() / radius;
+        val = 1 - val;
+        if (val <= 0) {
+            val = 0;
+        }
+        return heightFactor * val;
+    }
+
+    private Vector3f getWorldIntersection() {
+        Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f);
+        Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f);
+        direction.subtractLocal(origin).normalizeLocal();
+
+        Ray ray = new Ray(origin, direction);
+        CollisionResults results = new CollisionResults();
+        int numCollisions = terrain.collideWith(ray, results);
+        if (numCollisions > 0) {
+            CollisionResult hit = results.getClosestCollision();
+            return hit.getContactPoint();
+        }
+        return null;
+    }
+    
+    private void createTerrain() {
+        // First, we load up our textures and the heightmap texture for the terrain
+
+        // TERRAIN TEXTURE material
+        matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matTerrain.setBoolean("useTriPlanarMapping", false);
+        matTerrain.setBoolean("WardIso", true);
+        matTerrain.setFloat("Shininess", 0);
+
+        // ALPHA map (for splat textures)
+        matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap", grass);
+        matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_1", dirt);
+        matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_2", rock);
+        matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
+
+        // HEIGHTMAP image (for the terrain heightmap)
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f);
+            heightmap.load();
+            heightmap.smooth(0.9f, 1);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        
+        // CREATE THE TERRAIN
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+        control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+        terrain.addControl(control);
+        terrain.setMaterial(matTerrain);
+        terrain.setLocalTranslation(0, -100, 0);
+        terrain.setLocalScale(2.5f, 0.5f, 2.5f);
+        rootNode.attachChild(terrain);
+    }
+
+    private void createTerrainGrid() {
+        
+        // TERRAIN TEXTURE material
+        matTerrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
+
+        // Parameters to material:
+        // regionXColorMap: X = 1..4 the texture that should be appliad to state X
+        // regionX: a Vector3f containing the following information:
+        //      regionX.x: the start height of the region
+        //      regionX.y: the end height of the region
+        //      regionX.z: the texture scale for the region
+        //  it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
+        // slopeColorMap: the texture to be used for cliffs, and steep mountain sites
+        // slopeTileFactor: the texture scale for slopes
+        // terrainSize: the total size of the terrain (used for scaling the texture)
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("region1ColorMap", grass);
+        matTerrain.setVector3("region1", new Vector3f(88, 200, this.grassScale));
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("region2ColorMap", dirt);
+        matTerrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale));
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("region3ColorMap", rock);
+        matTerrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
+
+        matTerrain.setTexture("region4ColorMap", rock);
+        matTerrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
+
+        matTerrain.setTexture("slopeColorMap", rock);
+        matTerrain.setFloat("slopeTileFactor", 32);
+
+        matTerrain.setFloat("terrainSize", 513);
+
+        FractalSum base = new FractalSum();
+        base.setRoughness(0.7f);
+        base.setFrequency(1.0f);
+        base.setAmplitude(1.0f);
+        base.setLacunarity(2.12f);
+        base.setOctaves(8);
+        base.setScale(0.02125f);
+        base.addModulator(new NoiseModulator() {
+            @Override
+            public float value(float... in) {
+                return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
+            }
+        });
+
+        FilteredBasis ground = new FilteredBasis(base);
+
+        PerturbFilter perturb = new PerturbFilter();
+        perturb.setMagnitude(0.119f);
+
+        OptimizedErode therm = new OptimizedErode();
+        therm.setRadius(5);
+        therm.setTalus(0.011f);
+
+        SmoothFilter smooth = new SmoothFilter();
+        smooth.setRadius(1);
+        smooth.setEffect(0.7f);
+
+        IterativeFilter iterate = new IterativeFilter();
+        iterate.addPreFilter(perturb);
+        iterate.addPostFilter(smooth);
+        iterate.setFilter(therm);
+        iterate.setIterations(1);
+
+        ground.addPreFilter(iterate);
+
+        this.terrain = new TerrainGrid("terrain", 65, 257, new FractalTileLoader(ground, 256f));
+
+
+        terrain.setMaterial(matTerrain);
+        terrain.setLocalTranslation(0, 0, 0);
+        terrain.setLocalScale(2f, 1f, 2f);
+        
+        rootNode.attachChild(this.terrain);
+
+        TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
+        this.terrain.addControl(control);
+    }
+
+    private void createMarker() {
+        // collision marker
+        Sphere sphere = new Sphere(8, 8, 0.5f);
+        marker = new Geometry("Marker");
+        marker.setMesh(sphere);
+        
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", new ColorRGBA(251f/255f, 130f/255f, 0f, 0.6f));
+        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+        
+        marker.setMaterial(mat);
+        rootNode.attachChild(marker);
+        
+        
+        // surface normal marker
+        Arrow arrow = new Arrow(new Vector3f(0,1,0));
+        markerNormal = new Geometry("MarkerNormal");
+        markerNormal.setMesh(arrow);
+        markerNormal.setMaterial(mat);
+        rootNode.attachChild(markerNormal);
+    }
+}
diff --git a/engine/src/test/jme3test/terrain/TerrainTestReadWrite.java b/engine/src/test/jme3test/terrain/TerrainTestReadWrite.java
new file mode 100644
index 0000000..8ba9404
--- /dev/null
+++ b/engine/src/test/jme3test/terrain/TerrainTestReadWrite.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2009-2010 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.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.export.Savable;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.font.BitmapText;
+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.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Saves and loads terrain.
+ *
+ * @author Brent Owens
+ */
+public class TerrainTestReadWrite extends SimpleApplication {
+
+    private Terrain terrain;
+    protected BitmapText hintText;
+    private float grassScale = 64;
+    private float dirtScale = 16;
+    private float rockScale = 128;
+    private Material matTerrain;
+    private Material matWire;
+
+    public static void main(String[] args) {
+        TerrainTestReadWrite app = new TerrainTestReadWrite();
+        app.start();
+        //testHeightmapBuilding();
+    }
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        loadHintText();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+
+        createControls();
+        createMap();
+    }
+
+    private void createMap() {
+        matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matTerrain.setBoolean("useTriPlanarMapping", false);
+        matTerrain.setBoolean("WardIso", true);
+
+        // ALPHA map (for splat textures)
+        matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+
+        // HEIGHTMAP image (for the terrain heightmap)
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+
+        // GRASS texture
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap", grass);
+        matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
+
+
+        // DIRT texture
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_1", dirt);
+        matTerrain.setFloat("DiffuseMap_1_scale", dirtScale);
+
+        // ROCK texture
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("DiffuseMap_2", rock);
+        matTerrain.setFloat("DiffuseMap_2_scale", rockScale);
+
+
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matTerrain.setTexture("NormalMap", normalMap0);
+        matTerrain.setTexture("NormalMap_1", normalMap2);
+        matTerrain.setTexture("NormalMap_2", normalMap2);
+
+        matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matWire.getAdditionalRenderState().setWireframe(true);
+        matWire.setColor("Color", ColorRGBA.Green);
+
+
+        // CREATE HEIGHTMAP
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
+            heightmap.load();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        if (new File("terrainsave.jme").exists()) {
+            loadTerrain();
+        } else {
+            // create the terrain as normal, and give it a control for LOD management
+            TerrainQuad terrainQuad = new TerrainQuad("terrain", 65, 129, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
+            TerrainLodControl control = new TerrainLodControl(terrainQuad, getCamera());
+            control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+            terrainQuad.addControl(control);
+            terrainQuad.setMaterial(matTerrain);
+            terrainQuad.setLocalTranslation(0, -100, 0);
+            terrainQuad.setLocalScale(4f, 0.25f, 4f);
+            rootNode.attachChild(terrainQuad);
+            
+            this.terrain = terrainQuad;
+        }
+
+        DirectionalLight light = new DirectionalLight();
+        light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+        rootNode.addLight(light);
+    }
+
+    /**
+     * Create the save and load actions and add them to the input listener
+     */
+    private void createControls() {
+        flyCam.setMoveSpeed(50);
+        cam.setLocation(new Vector3f(0, 100, 0));
+
+        inputManager.addMapping("save", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(saveActionListener, "save");
+
+        inputManager.addMapping("load", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addListener(loadActionListener, "load");
+
+        inputManager.addMapping("clone", new KeyTrigger(KeyInput.KEY_C));
+        inputManager.addListener(cloneActionListener, "clone");
+    }
+
+    public void loadHintText() {
+        hintText = new BitmapText(guiFont, false);
+        hintText.setSize(guiFont.getCharSet().getRenderedSize());
+        hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+        hintText.setText("Hit T to save, and Y to load");
+        guiNode.attachChild(hintText);
+    }
+    private ActionListener saveActionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("save") && !pressed) {
+
+                FileOutputStream fos = null;
+                try {
+                    long start = System.currentTimeMillis();
+                    fos = new FileOutputStream(new File("terrainsave.jme"));
+
+                    // we just use the exporter and pass in the terrain
+                    BinaryExporter.getInstance().save((Savable)terrain, new BufferedOutputStream(fos));
+
+                    fos.flush();
+                    float duration = (System.currentTimeMillis() - start) / 1000.0f;
+                    System.out.println("Save took " + duration + " seconds");
+                } catch (IOException ex) {
+                    Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
+                } finally {
+                    try {
+                        if (fos != null) {
+                            fos.close();
+                        }
+                    } catch (IOException e) {
+                        Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, e);
+                    }
+                }
+            }
+        }
+    };
+
+    private void loadTerrain() {
+        FileInputStream fis = null;
+        try {
+            long start = System.currentTimeMillis();
+            // remove the existing terrain and detach it from the root node.
+            if (terrain != null) {
+                Node existingTerrain = (Node)terrain;
+                existingTerrain.removeFromParent();
+                existingTerrain.removeControl(TerrainLodControl.class);
+                existingTerrain.detachAllChildren();
+                terrain = null;
+            }
+
+            // import the saved terrain, and attach it back to the root node
+            File f = new File("terrainsave.jme");
+            fis = new FileInputStream(f);
+            BinaryImporter imp = BinaryImporter.getInstance();
+            imp.setAssetManager(assetManager);
+            terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis));
+            rootNode.attachChild((Node)terrain);
+
+            float duration = (System.currentTimeMillis() - start) / 1000.0f;
+            System.out.println("Load took " + duration + " seconds");
+
+            // now we have to add back the camera to the LOD control
+            TerrainLodControl lodControl = ((Node)terrain).getControl(TerrainLodControl.class);
+            if (lodControl != null)
+                lodControl.setCamera(getCamera());
+
+        } catch (IOException ex) {
+            Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
+        } finally {
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+            } catch (IOException ex) {
+                Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+    private ActionListener loadActionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("load") && !pressed) {
+                loadTerrain();
+            }
+        }
+    };
+    private ActionListener cloneActionListener = new ActionListener() {
+
+        public void onAction(String name, boolean pressed, float tpf) {
+            if (name.equals("clone") && !pressed) {
+
+                Terrain clone = (Terrain) ((Node)terrain).clone();
+                ((Node)terrain).removeFromParent();
+                terrain = clone;
+                getRootNode().attachChild((Node)terrain);
+            }
+        }
+    };
+
+    // no junit tests, so this has to be hand-tested:
+    private static void testHeightmapBuilding() {
+        int s = 9;
+        int b = 3;
+        float[] hm = new float[s * s];
+        for (int i = 0; i < s; i++) {
+            for (int j = 0; j < s; j++) {
+                hm[(i * s) + j] = i * j;
+            }
+        }
+
+        for (int i = 0; i < s; i++) {
+            for (int j = 0; j < s; j++) {
+                System.out.print(hm[i * s + j] + " ");
+            }
+            System.out.println("");
+        }
+
+        TerrainQuad terrain = new TerrainQuad("terrain", b, s, hm);
+        float[] hm2 = terrain.getHeightMap();
+        boolean failed = false;
+        for (int i = 0; i < s * s; i++) {
+            if (hm[i] != hm2[i]) {
+                failed = true;
+            }
+        }
+
+        System.out.println("");
+        if (failed) {
+            System.out.println("Terrain heightmap building FAILED!!!");
+            for (int i = 0; i < s; i++) {
+                for (int j = 0; j < s; j++) {
+                    System.out.print(hm2[i * s + j] + " ");
+                }
+                System.out.println("");
+            }
+        } else {
+            System.out.println("Terrain heightmap building PASSED");
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/texture/TestSkyLoading.java b/engine/src/test/jme3test/texture/TestSkyLoading.java
new file mode 100644
index 0000000..7101f1b
--- /dev/null
+++ b/engine/src/test/jme3test/texture/TestSkyLoading.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2009-2010 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.texture;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Texture;
+import com.jme3.util.SkyFactory;
+
+public class TestSkyLoading extends SimpleApplication {
+
+    public static void main(String[] args){
+        TestSkyLoading app = new TestSkyLoading();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg");
+        Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg");
+        Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg");
+        Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg");
+        Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg");
+        Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg");
+
+        Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
+        rootNode.attachChild(sky);
+    }
+
+}
diff --git a/engine/src/test/jme3test/texture/TestTexture3D.java b/engine/src/test/jme3test/texture/TestTexture3D.java
new file mode 100644
index 0000000..de93f8c
--- /dev/null
+++ b/engine/src/test/jme3test/texture/TestTexture3D.java
@@ -0,0 +1,103 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.texture;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture3D;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+public class TestTexture3D extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestTexture3D app = new TestTexture3D();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        //mouseInput.setCursorVisible(true);
+        flyCam.setMoveSpeed(10);
+        //creating a sphere
+        Sphere sphere = new Sphere(32, 32, 1);
+        //getting the boundingbox
+        sphere.updateBound();
+        BoundingBox bb = (BoundingBox) sphere.getBound();
+        Vector3f min = bb.getMin(null);
+        float[] ext = new float[]{bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2};
+        //we need to change the UV coordinates (the sphere is assumet to be inside the 3D image box)
+        sphere.clearBuffer(Type.TexCoord);
+        VertexBuffer vb = sphere.getBuffer(Type.Position);
+        FloatBuffer fb = (FloatBuffer) vb.getData();
+        float[] uvCoordinates = BufferUtils.getFloatArray(fb);
+        //now transform the coordinates so that they are in the range of <0; 1>
+        for (int i = 0; i < uvCoordinates.length; i += 3) {
+            uvCoordinates[i] = (uvCoordinates[i] - min.x) / ext[0];
+            uvCoordinates[i + 1] = (uvCoordinates[i + 1] - min.y) / ext[1];
+            uvCoordinates[i + 2] = (uvCoordinates[i + 2] - min.z) / ext[2];
+        }
+        //apply new texture coordinates
+        VertexBuffer uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
+        uvCoordsBuffer.setupData(Usage.Static, 3, com.jme3.scene.VertexBuffer.Format.Float,
+                BufferUtils.createFloatBuffer(uvCoordinates));
+        sphere.setBuffer(uvCoordsBuffer);
+        //create geometry, and apply material and our 3D texture
+        Geometry g = new Geometry("sphere", sphere);
+        Material material = new Material(assetManager, "jme3test/texture/tex3D.j3md");
+        try {
+            Texture texture = this.getTexture();
+            material.setTexture("Texture", texture);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        g.setMaterial(material);
+        rootNode.attachChild(g);
+        //add some light so that it is visible
+        PointLight light = new PointLight();
+        light.setColor(ColorRGBA.White);
+        light.setPosition(new Vector3f(5, 5, 5));
+        light.setRadius(20);
+        rootNode.addLight(light);
+        light = new PointLight();
+        light.setColor(ColorRGBA.White);
+        light.setPosition(new Vector3f(-5, -5, -5));
+        light.setRadius(20);
+        rootNode.addLight(light);
+    }
+
+    /**
+         * This method creates a RGB8 texture with the sizes of 10x10x10 pixels.
+         */
+    private Texture getTexture() throws IOException {
+        ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(1);
+        ByteBuffer bb = BufferUtils.createByteBuffer(10 * 10 * 10 * 3);//all data must be inside one buffer
+        for (int i = 0; i < 10; ++i) {
+            for (int j = 0; j < 10 * 10; ++j) {
+                bb.put((byte) (255f*i/10f));
+                bb.put((byte) (255f*i/10f));
+                bb.put((byte) (255f));
+            }
+        }
+        bb.rewind();
+        data.add(bb);
+        return new Texture3D(new Image(Format.RGB8, 10, 10, 10, data));
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/TestTexture3DLoading.java b/engine/src/test/jme3test/texture/TestTexture3DLoading.java
new file mode 100644
index 0000000..bc53362
--- /dev/null
+++ b/engine/src/test/jme3test/texture/TestTexture3DLoading.java
@@ -0,0 +1,54 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.texture;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture;
+
+public class TestTexture3DLoading extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestTexture3DLoading app = new TestTexture3DLoading();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        flyCam.setEnabled(false);
+
+
+        Quad q = new Quad(10, 10);
+
+        Geometry geom = new Geometry("Quad", q);
+        Material material = new Material(assetManager, "jme3test/texture/tex3DThumb.j3md");
+        TextureKey key = new TextureKey("Textures/3D/flame.dds");
+        key.setGenerateMips(true);
+        key.setAsTexture3D(true);
+
+        Texture t = assetManager.loadTexture(key);
+
+        int rows = 4;//4 * 4
+
+        q.scaleTextureCoordinates(new Vector2f(rows, rows));
+
+        //The image only have 8 pictures and we have 16 thumbs, the data will be interpolated by the GPU
+        material.setFloat("InvDepth", 1f / 16f);
+        material.setInt("Rows", rows);
+        material.setTexture("Texture", t);
+        geom.setMaterial(material);
+
+        rootNode.attachChild(geom);
+
+        cam.setLocation(new Vector3f(4.7444625f, 5.160054f, 13.1939f));
+    }
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/TestTextureArray.java b/engine/src/test/jme3test/texture/TestTextureArray.java
new file mode 100644
index 0000000..823fd78
--- /dev/null
+++ b/engine/src/test/jme3test/texture/TestTextureArray.java
@@ -0,0 +1,86 @@
+package jme3test.texture;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Caps;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.TextureArray;
+import com.jme3.util.BufferUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestTextureArray extends SimpleApplication
+{
+
+   @Override
+   public void simpleInitApp()
+   {
+       Material mat = new Material(assetManager, "jme3test/texture/UnshadedArray.j3md");
+       
+       for (Caps caps : renderManager.getRenderer().getCaps()) {
+           System.out.println(caps.name());
+       }
+       if(!renderManager.getRenderer().getCaps().contains(Caps.TextureArray)){
+           throw new UnsupportedOperationException("Your hardware does not support TextureArray");
+       }
+       
+       
+       Texture tex1 = assetManager.loadTexture( "Textures/Terrain/Pond/Pond.jpg");
+       Texture tex2 = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+       List<Image> images = new ArrayList<Image>();
+       images.add(tex1.getImage());
+       images.add(tex2.getImage());
+       TextureArray tex3 = new TextureArray(images);
+       mat.setTexture("ColorMap", tex3);
+
+       Mesh m = new Mesh();
+       Vector3f[] vertices = new Vector3f[8];
+       vertices[0] = new Vector3f(0, 0, 0);
+       vertices[1] = new Vector3f(3, 0, 0);
+       vertices[2] = new Vector3f(0, 3, 0);
+       vertices[3] = new Vector3f(3, 3, 0);
+
+       vertices[4] = new Vector3f(3, 0, 0);
+       vertices[5] = new Vector3f(6, 0, 0);
+       vertices[6] = new Vector3f(3, 3, 0);
+       vertices[7] = new Vector3f(6, 3, 0);
+
+       Vector3f[] texCoord = new Vector3f[8];
+       texCoord[0] = new Vector3f(0, 0, 0);
+       texCoord[1] = new Vector3f(1, 0, 0);
+       texCoord[2] = new Vector3f(0, 1, 0);
+       texCoord[3] = new Vector3f(1, 1, 0);
+
+       texCoord[4] = new Vector3f(0, 0, 1);
+       texCoord[5] = new Vector3f(1, 0, 1);
+       texCoord[6] = new Vector3f(0, 1, 1);
+       texCoord[7] = new Vector3f(1, 1, 1);
+
+       int[] indexes = { 2, 0, 1, 1, 3, 2 , 6, 4, 5, 5, 7, 6};
+
+       m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
+       m.setBuffer(Type.TexCoord, 3, BufferUtils.createFloatBuffer(texCoord));
+       m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
+       m.updateBound();
+
+       Geometry geom = new Geometry("Mesh", m);
+       geom.setMaterial(mat);
+       rootNode.attachChild(geom);
+   }
+
+   /**
+    * @param args
+    */
+   public static void main(String[] args)
+   {
+       TestTextureArray app = new TestTextureArray();
+       app.start();
+   }
+
+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/UnshadedArray.frag b/engine/src/test/jme3test/texture/UnshadedArray.frag
new file mode 100644
index 0000000..655ff9b
--- /dev/null
+++ b/engine/src/test/jme3test/texture/UnshadedArray.frag
@@ -0,0 +1,50 @@
+uniform vec4 m_Color;

+

+#if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))

+    #define NEED_TEXCOORD1

+#endif

+

+#ifdef HAS_COLORMAP

+    uniform sampler2DArray m_ColorMap;

+#endif

+

+#ifdef NEED_TEXCOORD1

+    varying vec3 texCoord1;

+#endif

+

+#ifdef HAS_LIGHTMAP

+    uniform sampler2D m_LightMap;

+    #ifdef SEPERATE_TEXCOORD

+        varying vec3 texCoord2;

+    #endif

+#endif

+

+#ifdef HAS_VERTEXCOLOR

+    varying vec4 vertColor;

+#endif

+

+void main(){

+    vec4 color = vec4(1.0);

+

+    #ifdef HAS_COLORMAP

+        color *= texture2DArray(m_ColorMap, texCoord1);

+    #endif

+

+    #ifdef HAS_VERTEXCOLOR

+        color *= vertColor;

+    #endif

+

+    #ifdef HAS_COLOR

+        color *= m_Color;

+    #endif

+

+    #ifdef HAS_LIGHTMAP

+        #ifdef SEPARATE_TEXCOORD

+            color.rgb *= texture2D(m_LightMap, texCoord2).rgb;

+        #else

+            color.rgb *= texture2D(m_LightMap, texCoord1).rgb;

+        #endif

+    #endif

+

+    gl_FragColor = color;

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/UnshadedArray.j3md b/engine/src/test/jme3test/texture/UnshadedArray.j3md
new file mode 100644
index 0000000..19af9bf
--- /dev/null
+++ b/engine/src/test/jme3test/texture/UnshadedArray.j3md
@@ -0,0 +1,70 @@
+MaterialDef Unshaded {

+

+    MaterialParameters {

+        TextureArray ColorMap

+        Texture2D LightMap

+        Color Color (Color)

+        Boolean VertexColor

+        Boolean SeparateTexCoord

+

+        // Texture of the glowing parts of the material

+        Texture2D GlowMap

+        // The glow color of the object

+        Color GlowColor

+    }

+

+    Technique {

+        VertexShader GLSL100:   jme3test/texture/UnshadedArray.vert

+        FragmentShader GLSL100: jme3test/texture/UnshadedArray.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            SEPARATE_TEXCOORD : SeparateTexCoord

+            HAS_COLORMAP : ColorMap

+            HAS_LIGHTMAP : LightMap

+            HAS_VERTEXCOLOR : VertexColor

+            HAS_COLOR : Color

+        }

+    }

+

+      Technique PreNormalPass {

+

+            VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert

+            FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

+

+            WorldParameters {

+                WorldViewProjectionMatrix

+                WorldViewMatrix

+                NormalMatrix

+            }

+

+            RenderState {

+

+            }

+

+        }

+

+

+    Technique Glow {

+

+        VertexShader GLSL100:   Cjme3test/texture/UnshadedArray.vert

+        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

+

+        WorldParameters {

+            WorldViewProjectionMatrix

+        }

+

+        Defines {

+            HAS_GLOWMAP : GlowMap

+            HAS_GLOWCOLOR : GlowColor

+            HAS_COLORMAP // Must be passed so that Unshaded.vert exports texCoord.

+        }

+    }

+

+    Technique FixedFunc {

+    }

+

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/UnshadedArray.vert b/engine/src/test/jme3test/texture/UnshadedArray.vert
new file mode 100644
index 0000000..9d1153c
--- /dev/null
+++ b/engine/src/test/jme3test/texture/UnshadedArray.vert
@@ -0,0 +1,38 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+attribute vec3 inPosition;

+

+#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))

+    #define NEED_TEXCOORD1

+#endif

+

+#ifdef NEED_TEXCOORD1

+    attribute vec3 inTexCoord;

+    varying vec3 texCoord1;

+#endif

+

+#ifdef SEPARATE_TEXCOORD

+    attribute vec3 inTexCoord2;

+    varying vec3 texCoord2;

+#endif

+

+#ifdef HAS_VERTEXCOLOR

+    attribute vec4 inColor;

+    varying vec4 vertColor;

+#endif

+

+void main(){

+    #ifdef NEED_TEXCOORD1

+        texCoord1 = inTexCoord;

+    #endif

+

+    #ifdef SEPARATE_TEXCOORD

+        texCoord2 = inTexCoord2;

+    #endif

+

+    #ifdef HAS_VERTEXCOLOR

+        vertColor = inColor;

+    #endif

+

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);

+}

+

diff --git a/engine/src/test/jme3test/texture/tex3D.frag b/engine/src/test/jme3test/texture/tex3D.frag
new file mode 100644
index 0000000..55862ac
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3D.frag
@@ -0,0 +1,7 @@
+uniform sampler3D m_Texture;

+

+varying vec3 texCoord;

+

+void main(){

+    gl_FragColor= texture3D(m_Texture,texCoord);

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/tex3D.j3md b/engine/src/test/jme3test/texture/tex3D.j3md
new file mode 100644
index 0000000..1ba2605
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3D.j3md
@@ -0,0 +1,16 @@
+MaterialDef My MaterialDef {
+
+    MaterialParameters {
+        Texture3D Texture
+    }
+
+    Technique {
+        VertexShader GLSL100:   jme3test/texture/tex3D.vert
+        FragmentShader GLSL100: jme3test/texture/tex3D.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/texture/tex3D.vert b/engine/src/test/jme3test/texture/tex3D.vert
new file mode 100644
index 0000000..f91b7b3
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3D.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec3 inTexCoord;

+attribute vec3 inPosition;

+

+varying vec3 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);

+    texCoord=inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/tex3DThumb.frag b/engine/src/test/jme3test/texture/tex3DThumb.frag
new file mode 100644
index 0000000..f6eb25a
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3DThumb.frag
@@ -0,0 +1,14 @@
+uniform sampler3D m_Texture;

+uniform int m_Rows;

+uniform float m_InvDepth;

+

+varying vec2 texCoord;

+

+void main(){

+float depthx=floor(texCoord.x);

+    float depthy=(m_Rows-1.0) - floor(texCoord.y);    

+    //vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);//

+  

+        vec3 texC=vec3(fract(texCoord.x),fract(texCoord.y),(depthy*m_Rows+depthx)*m_InvDepth);//

+    gl_FragColor= texture3D(m_Texture,texC);

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/texture/tex3DThumb.j3md b/engine/src/test/jme3test/texture/tex3DThumb.j3md
new file mode 100644
index 0000000..a42bd38
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3DThumb.j3md
@@ -0,0 +1,18 @@
+MaterialDef Tex3DThumb {
+
+    MaterialParameters {
+        Texture3D Texture
+        Int Rows;
+        Float InvDepth;
+    }
+
+    Technique {
+        VertexShader GLSL100:   jme3test/texture/tex3DThumb.vert
+        FragmentShader GLSL100: jme3test/texture/tex3DThumb.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+    }
+
+}
diff --git a/engine/src/test/jme3test/texture/tex3DThumb.vert b/engine/src/test/jme3test/texture/tex3DThumb.vert
new file mode 100644
index 0000000..6d27bc0
--- /dev/null
+++ b/engine/src/test/jme3test/texture/tex3DThumb.vert
@@ -0,0 +1,11 @@
+uniform mat4 g_WorldViewProjectionMatrix;

+

+attribute vec2 inTexCoord;

+attribute vec3 inPosition;

+

+varying vec2 texCoord;

+

+void main(){

+    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);

+    texCoord=inTexCoord;

+}
\ No newline at end of file
diff --git a/engine/src/test/jme3test/tools/TestOctree.java b/engine/src/test/jme3test/tools/TestOctree.java
new file mode 100644
index 0000000..01eac3b
--- /dev/null
+++ b/engine/src/test/jme3test/tools/TestOctree.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2009-2010 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.tools;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialList;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireBox;
+import com.jme3.scene.plugins.ogre.MeshLoader;
+import com.jme3.scene.plugins.ogre.OgreMeshKey;
+import com.jme3.texture.FrameBuffer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import jme3tools.optimize.FastOctnode;
+import jme3tools.optimize.Octree;
+
+
+public class TestOctree extends SimpleApplication implements SceneProcessor {
+
+    private Octree tree;
+    private FastOctnode fastRoot;
+    private Geometry[] globalGeoms;
+    private BoundingBox octBox;
+
+    private Set<Geometry> renderSet = new HashSet<Geometry>(300);
+    private Material mat, mat2;
+    private WireBox box = new WireBox(1,1,1);
+
+    public static void main(String[] args){
+        TestOctree app = new TestOctree();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+//        this.flyCam.setMoveSpeed(2000);
+//        this.cam.setFrustumFar(10000);
+        MeshLoader.AUTO_INTERLEAVE = false;
+
+//        mat = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md");
+//        mat.setColor("Color", ColorRGBA.White);
+
+//        mat2 = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+
+        assetManager.registerLocator("quake3level.zip", "com.jme3.asset.plugins.ZipLocator");
+        MaterialList matList = (MaterialList) assetManager.loadAsset("Scene.material");
+        OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
+        Spatial scene = assetManager.loadModel(key);
+
+//        Spatial scene = assetManager.loadModel("Models/Teapot/teapot.obj");
+//        scene.scale(3);
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setColor(ColorRGBA.White);
+        dl.setDirection(new Vector3f(-1, -1, -1).normalize());
+        rootNode.addLight(dl);
+
+        DirectionalLight dl2 = new DirectionalLight();
+        dl2.setColor(ColorRGBA.White);
+        dl2.setDirection(new Vector3f(1, -1, 1).normalize());
+        rootNode.addLight(dl2);
+
+        // generate octree
+//        tree = new Octree(scene, 20000);
+        tree = new Octree(scene, 50);
+        tree.construct();
+        
+        ArrayList<Geometry> globalGeomList = new ArrayList<Geometry>();
+        tree.createFastOctnodes(globalGeomList);
+        tree.generateFastOctnodeLinks();
+
+        for (Geometry geom : globalGeomList){
+            geom.addLight(dl);
+            geom.addLight(dl2);
+            geom.updateGeometricState();
+        }
+        
+        globalGeoms = globalGeomList.toArray(new Geometry[0]);
+        fastRoot = tree.getFastRoot();
+        octBox = tree.getBound();
+
+        viewPort.addProcessor(this);
+    }
+
+    public void initialize(RenderManager rm, ViewPort vp) {
+    }
+
+    public void reshape(ViewPort vp, int w, int h) {
+    }
+
+    public boolean isInitialized() {
+        return true;
+    }
+
+    public void preFrame(float tpf) {
+    }
+
+    public void postQueue(RenderQueue rq) {
+        renderSet.clear();
+        //tree.generateRenderSet(renderSet, cam);
+        fastRoot.generateRenderSet(globalGeoms, renderSet, cam, octBox, true);
+//        System.out.println("Geoms: "+renderSet.size());
+        int tris = 0;
+
+        for (Geometry geom : renderSet){
+            tris += geom.getTriangleCount();
+//            geom.setMaterial(mat2);
+            rq.addToQueue(geom, geom.getQueueBucket());
+        }
+
+//        Matrix4f transform = new Matrix4f();
+//        transform.setScale(0.2f, 0.2f, 0.2f);
+//        System.out.println("Tris: "+tris);
+        
+//        tree.renderBounds(rq, transform, box, mat);
+
+//        renderManager.flushQueue(viewPort);
+    }
+
+    public void postFrame(FrameBuffer out) {
+    }
+
+    public void cleanup() {
+    }
+}
diff --git a/engine/src/test/jme3test/tools/TestSaveGame.java b/engine/src/test/jme3test/tools/TestSaveGame.java
new file mode 100644
index 0000000..fc7d1b4
--- /dev/null
+++ b/engine/src/test/jme3test/tools/TestSaveGame.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2010 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.tools;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import jme3tools.savegame.SaveGame;
+
+public class TestSaveGame extends SimpleApplication {
+
+    public static void main(String[] args) {
+
+        TestSaveGame app = new TestSaveGame();
+        app.start();
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+    }
+
+    public void simpleInitApp() {
+
+        //node that is used to store player data
+        Node myPlayer = new Node();
+        myPlayer.setName("PlayerNode");
+        myPlayer.setUserData("name", "Mario");
+        myPlayer.setUserData("health", 100.0f);
+        myPlayer.setUserData("points", 0);
+
+        //the actual model would be attached to this node
+        Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        myPlayer.attachChild(model);
+
+        //before saving the game, the model should be detached so its not saved along with the node
+        myPlayer.detachAllChildren();
+        SaveGame.saveGame("mycompany/mygame", "savegame_001", myPlayer);
+
+        //later the game is loaded again
+        Node player = (Node) SaveGame.loadGame("mycompany/mygame", "savegame_001");
+        player.attachChild(model);
+        rootNode.attachChild(player);
+
+        //and the data is available
+        System.out.println("Name: " + player.getUserData("name"));
+        System.out.println("Health: " + player.getUserData("health"));
+        System.out.println("Points: " + player.getUserData("points"));
+
+        AmbientLight al = new AmbientLight();
+        rootNode.addLight(al);
+        
+        //note you can also implement your own classes that implement the Savable interface.
+    }
+}
diff --git a/engine/src/test/jme3test/tools/TestTextureAtlas.java b/engine/src/test/jme3test/tools/TestTextureAtlas.java
new file mode 100644
index 0000000..d749e47
--- /dev/null
+++ b/engine/src/test/jme3test/tools/TestTextureAtlas.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 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.tools;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import jme3tools.optimize.TextureAtlas;
+
+public class TestTextureAtlas extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestTextureAtlas app = new TestTextureAtlas();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(50);
+        Node scene = new Node("Scene");
+        Spatial obj1 = assetManager.loadModel("Models/Ferrari/Car.scene");
+        obj1.setLocalTranslation(-4, 0, 0);
+        Spatial obj2 = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        obj2.setLocalTranslation(-2, 0, 0);
+        Spatial obj3 = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
+        obj3.setLocalTranslation(-0, 0, 0);
+        Spatial obj4 = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
+        obj4.setLocalTranslation(2, 0, 0);
+        Spatial obj5 = assetManager.loadModel("Models/Tree/Tree.mesh.j3o");
+        obj5.setLocalTranslation(4, 0, 0);
+        scene.attachChild(obj1);
+        scene.attachChild(obj2);
+        scene.attachChild(obj3);
+        scene.attachChild(obj4);
+        scene.attachChild(obj5);
+
+        Geometry geom = TextureAtlas.makeAtlasBatch(scene, assetManager, 2048);
+
+        AmbientLight al = new AmbientLight();
+        rootNode.addLight(al);
+
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(0.69077975f, -0.6277887f, -0.35875428f).normalizeLocal());
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        rootNode.addLight(sun);
+
+        rootNode.attachChild(geom);
+
+        //quad to display material
+        Geometry box = new Geometry("displayquad", new Quad(4, 4));
+        box.setMaterial(geom.getMaterial());
+        box.setLocalTranslation(0, 1, 3);
+        rootNode.attachChild(box);
+    }
+}
diff --git a/engine/src/test/jme3test/water/TestPostWater.java b/engine/src/test/jme3test/water/TestPostWater.java
new file mode 100644
index 0000000..9a89f98
--- /dev/null
+++ b/engine/src/test/jme3test/water/TestPostWater.java
@@ -0,0 +1,298 @@
+package jme3test.water;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.audio.LowPassFilter;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.DepthOfFieldFilter;
+import com.jme3.post.filters.LightScatteringFilter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.SkyFactory;
+import com.jme3.water.WaterFilter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * test
+ * @author normenhansen
+ */
+public class TestPostWater extends SimpleApplication {
+
+    private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f);
+    private WaterFilter water;
+    TerrainQuad terrain;
+    Material matRock;
+    AudioNode waves;
+    LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f);
+    LowPassFilter underWaterReverbFilter = new LowPassFilter(0.5f, 0.1f);
+    LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1);
+
+    public static void main(String[] args) {
+        TestPostWater app = new TestPostWater();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+      setDisplayFps(false);
+      setDisplayStatView(false);
+
+        Node mainScene = new Node("Main Scene");
+        rootNode.attachChild(mainScene);
+
+        createTerrain(mainScene);
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(1.7f));
+        mainScene.addLight(sun);
+
+        DirectionalLight l = new DirectionalLight();
+        l.setDirection(Vector3f.UNIT_Y.mult(-1));
+        l.setColor(ColorRGBA.White.clone().multLocal(0.3f));
+//        mainScene.addLight(l);
+
+        flyCam.setMoveSpeed(50);
+
+        //cam.setLocation(new Vector3f(-700, 100, 300));
+         //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z));
+        cam.setLocation(new Vector3f(-327.21957f, 61.6459f, 126.884346f));
+        cam.setRotation(new Quaternion(0.052168474f, 0.9443102f, -0.18395276f, 0.2678024f));
+
+          
+        cam.setRotation(new Quaternion().fromAngles(new float[]{FastMath.PI * 0.06f, FastMath.PI * 0.65f, 0}));
+
+
+        Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
+        sky.setLocalScale(350);
+      
+        mainScene.attachChild(sky);
+        cam.setFrustumFar(4000);
+        //cam.setFrustumNear(100);
+       
+        
+
+        //private FilterPostProcessor fpp;
+
+
+        water = new WaterFilter(rootNode, lightDir);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        
+        fpp.addFilter(water);
+        BloomFilter bloom=new BloomFilter();
+        //bloom.getE
+        bloom.setExposurePower(55);
+        bloom.setBloomIntensity(1.0f);
+        fpp.addFilter(bloom);
+        LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300));
+        lsf.setLightDensity(1.0f);
+        fpp.addFilter(lsf);
+        DepthOfFieldFilter dof=new DepthOfFieldFilter();
+        dof.setFocusDistance(0);
+        dof.setFocusRange(100);     
+        fpp.addFilter(dof);
+//        
+        
+     //   fpp.addFilter(new TranslucentBucketFilter());
+ //       
+        
+         // fpp.setNumSamples(4);
+
+
+        water.setWaveScale(0.003f);
+        water.setMaxAmplitude(2f);
+        water.setFoamExistence(new Vector3f(1f, 4, 0.5f));
+        water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
+        //water.setNormalScale(0.5f);
+
+        //water.setRefractionConstant(0.25f);
+        water.setRefractionStrength(0.2f);
+        //water.setFoamHardness(0.6f);
+
+        water.setWaterHeight(initialWaterHeight);
+      uw=cam.getLocation().y<waterHeight; 
+      
+        waves = new AudioNode(audioRenderer, assetManager, "Sound/Environment/Ocean Waves.ogg", false);
+        waves.setLooping(true);
+        waves.setReverbEnabled(true);
+        if(uw){
+            waves.setDryFilter(new LowPassFilter(0.5f, 0.1f));
+        }else{
+            waves.setDryFilter(aboveWaterAudioFilter);            
+        }
+        audioRenderer.playSource(waves);
+        //  
+        viewPort.addProcessor(fpp);
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) {
+                    if (name.equals("foam1")) {
+                        water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"));
+                    }
+                    if (name.equals("foam2")) {
+                        water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
+                    }
+                    if (name.equals("foam3")) {
+                        water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg"));
+                    }
+                }
+            }
+        }, "foam1", "foam2", "foam3");
+        inputManager.addMapping("foam1", new KeyTrigger(keyInput.KEY_1));
+        inputManager.addMapping("foam2", new KeyTrigger(keyInput.KEY_2));
+        inputManager.addMapping("foam3", new KeyTrigger(keyInput.KEY_3));
+//        createBox();
+      //  createFire();
+    }
+    Geometry box;
+
+    private void createBox() {
+        //creating a transluscent box
+        box = new Geometry("box", new Box(new Vector3f(0, 0, 0), 50, 50, 50));
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", new ColorRGBA(1.0f, 0, 0, 0.3f));
+        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+        //mat.getAdditionalRenderState().setDepthWrite(false);
+        //mat.getAdditionalRenderState().setDepthTest(false);
+        box.setMaterial(mat);
+        box.setQueueBucket(Bucket.Translucent);
+
+
+        //creating a post view port
+//        ViewPort post=renderManager.createPostView("transpPost", cam);
+//        post.setClearFlags(false, true, true);
+
+
+        box.setLocalTranslation(-600, 0, 300);
+
+        //attaching the box to the post viewport
+        //Don't forget to updateGeometricState() the box in the simpleUpdate
+        //  post.attachScene(box);
+
+        rootNode.attachChild(box);
+    }
+
+    private void createFire() {
+        /** Uses Texture from jme3-test-data library! */
+        ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
+        Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
+
+        fire.setMaterial(mat_red);
+        fire.setImagesX(2);
+        fire.setImagesY(2); // 2x2 texture animation
+        fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f));   // red
+        fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
+        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0));
+        fire.setStartSize(10f);
+        fire.setEndSize(1f);
+        fire.setGravity(0, 0, 0);
+        fire.setLowLife(0.5f);
+        fire.setHighLife(1.5f);
+        fire.getParticleInfluencer().setVelocityVariation(0.3f);
+        fire.setLocalTranslation(-350, 40, 430);
+
+        fire.setQueueBucket(Bucket.Transparent);
+        rootNode.attachChild(fire);
+    }
+
+    private void createTerrain(Node rootNode) {
+        matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+        matRock.setBoolean("useTriPlanarMapping", false);
+        matRock.setBoolean("WardIso", true);
+        matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+        Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
+        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+        grass.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap", grass);
+        matRock.setFloat("DiffuseMap_0_scale", 64);
+        Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+        dirt.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_1", dirt);
+        matRock.setFloat("DiffuseMap_1_scale", 16);
+        Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+        rock.setWrap(WrapMode.Repeat);
+        matRock.setTexture("DiffuseMap_2", rock);
+        matRock.setFloat("DiffuseMap_2_scale", 128);
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
+        normalMap0.setWrap(WrapMode.Repeat);
+        Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+        normalMap1.setWrap(WrapMode.Repeat);
+        Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+        normalMap2.setWrap(WrapMode.Repeat);
+        matRock.setTexture("NormalMap", normalMap0);
+        matRock.setTexture("NormalMap_1", normalMap2);
+        matRock.setTexture("NormalMap_2", normalMap2);
+
+        AbstractHeightMap heightmap = null;
+        try {
+            heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+            heightmap.load();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+        List<Camera> cameras = new ArrayList<Camera>();
+        cameras.add(getCamera());
+        terrain.setMaterial(matRock);
+        terrain.setLocalScale(new Vector3f(5, 5, 5));
+        terrain.setLocalTranslation(new Vector3f(0, -30, 0));
+        terrain.setLocked(false); // unlock it so we can edit the height
+
+        terrain.setShadowMode(ShadowMode.Receive);
+        rootNode.attachChild(terrain);
+
+    }
+    //This part is to emulate tides, slightly varrying the height of the water plane
+    private float time = 0.0f;
+    private float waterHeight = 0.0f;
+    private float initialWaterHeight = 0.8f;
+private boolean uw=false;
+    @Override
+    public void simpleUpdate(float tpf) {
+        super.simpleUpdate(tpf);
+        //     box.updateGeometricState();
+        time += tpf;
+        waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f;
+        water.setWaterHeight(initialWaterHeight + waterHeight);
+        if(water.isUnderWater() && !uw){
+           
+            waves.setDryFilter(new LowPassFilter(0.5f, 0.1f));
+            uw=true;
+        }
+        if(!water.isUnderWater() && uw){
+            uw=false;
+             //waves.setReverbEnabled(false);
+             waves.setDryFilter(new LowPassFilter(1, 1f));
+             //waves.setDryFilter(new LowPassFilter(1,1f));
+             
+        }     
+    }
+}
diff --git a/engine/src/test/jme3test/water/TestPostWaterLake.java b/engine/src/test/jme3test/water/TestPostWaterLake.java
new file mode 100644
index 0000000..c12501e
--- /dev/null
+++ b/engine/src/test/jme3test/water/TestPostWaterLake.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009-2010 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.water;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SkyFactory;
+import com.jme3.water.WaterFilter;
+import java.io.File;
+
+public class TestPostWaterLake extends SimpleApplication {
+
+    // set default for applets
+    private static boolean useHttp = true;
+
+    public static void main(String[] args) {
+     
+        TestPostWaterLake app = new TestPostWaterLake();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(10);
+        cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f));
+      //  cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f));
+
+        // load sky
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        File file = new File("wildhouse.zip");
+        
+        if (file.exists()) {
+            useHttp = false;
+        }
+        // create the geometry and attach it
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("wildhouse.zip", ZipLocator.class.getName());
+        }
+        Spatial scene = assetManager.loadModel("main.scene");
+        rootNode.attachChild(scene);
+
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f lightDir = new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f);
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);        
+        final WaterFilter water = new WaterFilter(rootNode, lightDir);
+        water.setWaterHeight(-20);
+        water.setUseFoam(false);
+        water.setUseRipples(false);
+        water.setDeepWaterColor(ColorRGBA.Brown);
+        water.setWaterColor(ColorRGBA.Brown.mult(2.0f));
+        water.setWaterTransparency(0.2f);
+        water.setMaxAmplitude(0.3f);
+        water.setWaveScale(0.008f);
+        water.setSpeed(0.7f);
+        water.setShoreHardness(1.0f);
+        water.setRefractionConstant(0.2f);
+        water.setShininess(0.3f);
+        water.setSunScale(1.0f);
+        water.setColorExtinction(new Vector3f(10.0f, 20.0f, 30.0f));
+        fpp.addFilter(water);
+        viewPort.addProcessor(fpp);
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+              if(isPressed){
+                  if(water.isUseHQShoreline()){
+                      water.setUseHQShoreline(false);
+                  }else{
+                      water.setUseHQShoreline(true);
+                  }
+              }
+            }
+        }, "HQ");
+
+        inputManager.addMapping("HQ", new KeyTrigger(keyInput.KEY_SPACE));
+    }
+}
diff --git a/engine/src/test/jme3test/water/TestSceneWater.java b/engine/src/test/jme3test/water/TestSceneWater.java
new file mode 100644
index 0000000..8a96877
--- /dev/null
+++ b/engine/src/test/jme3test/water/TestSceneWater.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2009-2010 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.water;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.SkyFactory;
+import com.jme3.water.SimpleWaterProcessor;
+import java.io.File;
+
+public class TestSceneWater extends SimpleApplication {
+
+    // set default for applets
+    private static boolean useHttp = true;
+
+    public static void main(String[] args) {
+      
+        TestSceneWater app = new TestSceneWater();
+        app.start();
+    }
+
+    public void simpleInitApp() {
+        this.flyCam.setMoveSpeed(10);
+        Node mainScene=new Node();
+        cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f));
+        cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f));
+
+        // load sky
+        mainScene.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+
+        
+        File file = new File("wildhouse.zip");
+        if (file.exists()) {
+            useHttp = false;
+        }
+        // create the geometry and attach it
+        // load the level from zip or http zip
+        if (useHttp) {
+            assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class.getName());
+        } else {
+            assetManager.registerLocator("wildhouse.zip", ZipLocator.class.getName());
+        }
+        Spatial scene = assetManager.loadModel("main.scene");
+
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f lightDir=new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f);
+        sun.setDirection(lightDir);
+        sun.setColor(ColorRGBA.White.clone().multLocal(2));
+        scene.addLight(sun);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+           //add lightPos Geometry
+        Sphere lite=new Sphere(8, 8, 3.0f);
+        Geometry lightSphere=new Geometry("lightsphere", lite);
+        lightSphere.setMaterial(mat);
+        Vector3f lightPos=lightDir.multLocal(-400);
+        lightSphere.setLocalTranslation(lightPos);
+        rootNode.attachChild(lightSphere);
+
+
+        SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
+        waterProcessor.setReflectionScene(mainScene);
+        waterProcessor.setDebug(false);
+        waterProcessor.setLightPosition(lightPos);
+        waterProcessor.setRefractionClippingOffset(1.0f);
+
+
+        //setting the water plane
+        Vector3f waterLocation=new Vector3f(0,-20,0);
+        waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
+        WaterUI waterUi=new WaterUI(inputManager, waterProcessor);
+        waterProcessor.setWaterColor(ColorRGBA.Brown);
+        waterProcessor.setDebug(true);
+        //lower render size for higher performance
+//        waterProcessor.setRenderSize(128,128);
+        //raise depth to see through water
+//        waterProcessor.setWaterDepth(20);
+        //lower the distortion scale if the waves appear too strong
+//        waterProcessor.setDistortionScale(0.1f);
+        //lower the speed of the waves if they are too fast
+//        waterProcessor.setWaveSpeed(0.01f);
+
+        Quad quad = new Quad(400,400);
+
+        //the texture coordinates define the general size of the waves
+        quad.scaleTextureCoordinates(new Vector2f(6f,6f));
+
+        Geometry water=new Geometry("water", quad);
+        water.setShadowMode(ShadowMode.Receive);
+        water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+        water.setMaterial(waterProcessor.getMaterial());
+        water.setLocalTranslation(-200, -20, 250);
+
+        rootNode.attachChild(water);
+
+        viewPort.addProcessor(waterProcessor);
+
+        mainScene.attachChild(scene);
+        rootNode.attachChild(mainScene);
+    }
+}
diff --git a/engine/src/test/jme3test/water/TestSimpleWater.java b/engine/src/test/jme3test/water/TestSimpleWater.java
new file mode 100644
index 0000000..de32923
--- /dev/null
+++ b/engine/src/test/jme3test/water/TestSimpleWater.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2009-2010 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.water;
+
+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.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.SkyFactory;
+import com.jme3.water.SimpleWaterProcessor;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class TestSimpleWater extends SimpleApplication implements ActionListener {
+
+    Material mat;
+    Spatial waterPlane;
+    Geometry lightSphere;
+    SimpleWaterProcessor waterProcessor;
+    Node sceneNode;
+    boolean useWater = true;
+    private Vector3f lightPos =  new Vector3f(33,12,-29);
+
+
+    public static void main(String[] args) {
+        TestSimpleWater app = new TestSimpleWater();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        initInput();
+        initScene();
+
+        //create processor
+        waterProcessor = new SimpleWaterProcessor(assetManager);
+        waterProcessor.setReflectionScene(sceneNode);
+        waterProcessor.setDebug(true);
+        viewPort.addProcessor(waterProcessor);
+
+        waterProcessor.setLightPosition(lightPos);
+
+        //create water quad
+        //waterPlane = waterProcessor.createWaterGeometry(100, 100);
+        waterPlane=(Spatial)  assetManager.loadAsset("Models/WaterTest/WaterTest.mesh.xml");
+        waterPlane.setMaterial(waterProcessor.getMaterial());
+        waterPlane.setLocalScale(40);
+        waterPlane.setLocalTranslation(-5, 0, 5);
+
+        rootNode.attachChild(waterPlane);
+    }
+
+    private void initScene() {
+        //init cam location
+        cam.setLocation(new Vector3f(0, 10, 10));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+        //init scene
+        sceneNode = new Node("Scene");
+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        Box b = new Box(1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        geom.setMaterial(mat);
+        sceneNode.attachChild(geom);
+
+        // load sky
+        sceneNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false));
+        rootNode.attachChild(sceneNode);
+
+        //add lightPos Geometry
+        Sphere lite=new Sphere(8, 8, 3.0f);
+        lightSphere=new Geometry("lightsphere", lite);
+        lightSphere.setMaterial(mat);
+        lightSphere.setLocalTranslation(lightPos);
+        rootNode.attachChild(lightSphere);
+    }
+
+    protected void initInput() {
+        flyCam.setMoveSpeed(3);
+        //init input
+        inputManager.addMapping("use_water", new KeyTrigger(KeyInput.KEY_O));
+        inputManager.addListener(this, "use_water");
+        inputManager.addMapping("lightup", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addListener(this, "lightup");
+        inputManager.addMapping("lightdown", new KeyTrigger(KeyInput.KEY_G));
+        inputManager.addListener(this, "lightdown");
+        inputManager.addMapping("lightleft", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addListener(this, "lightleft");
+        inputManager.addMapping("lightright", new KeyTrigger(KeyInput.KEY_K));
+        inputManager.addListener(this, "lightright");
+        inputManager.addMapping("lightforward", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addListener(this, "lightforward");
+        inputManager.addMapping("lightback", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addListener(this, "lightback");
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        fpsText.setText("Light Position: "+lightPos.toString()+" Change Light position with [U], [H], [J], [K] and [T], [G] Turn off water with [O]");
+        lightSphere.setLocalTranslation(lightPos);
+        waterProcessor.setLightPosition(lightPos);
+    }
+
+    public void onAction(String name, boolean value, float tpf) {
+        if (name.equals("use_water") && value) {
+            if (!useWater) {
+                useWater = true;
+                waterPlane.setMaterial(waterProcessor.getMaterial());
+            } else {
+                useWater = false;
+                waterPlane.setMaterial(mat);
+            }
+        } else if (name.equals("lightup") && value) {
+            lightPos.y++;
+        } else if (name.equals("lightdown") && value) {
+            lightPos.y--;
+        } else if (name.equals("lightleft") && value) {
+            lightPos.x--;
+        } else if (name.equals("lightright") && value) {
+            lightPos.x++;
+        } else if (name.equals("lightforward") && value) {
+            lightPos.z--;
+        } else if (name.equals("lightback") && value) {
+            lightPos.z++;
+        }
+    }
+}
diff --git a/engine/src/test/jme3test/water/WaterUI.java b/engine/src/test/jme3test/water/WaterUI.java
new file mode 100644
index 0000000..855b53f
--- /dev/null
+++ b/engine/src/test/jme3test/water/WaterUI.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009-2010 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.water;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.water.SimpleWaterProcessor;
+
+/**
+ *
+ * @author nehon
+ */
+public class WaterUI {
+    private SimpleWaterProcessor processor;
+    public WaterUI(InputManager inputManager, SimpleWaterProcessor proc) {
+        processor=proc;
+
+
+        System.out.println("----------------- SSAO UI Debugger --------------------");
+        System.out.println("-- Water transparency : press Y to increase, H to decrease");
+        System.out.println("-- Water depth : press U to increase, J to decrease");
+//        System.out.println("-- AO scale : press I to increase, K to decrease");
+//        System.out.println("-- AO bias : press O to increase, P to decrease");
+//        System.out.println("-- Toggle AO on/off : press space bar");
+//        System.out.println("-- Use only AO : press Num pad 0");
+//        System.out.println("-- Output config declaration : press P");
+        System.out.println("-------------------------------------------------------");
+    
+        inputManager.addMapping("transparencyUp", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("transparencyDown", new KeyTrigger(KeyInput.KEY_H));
+        inputManager.addMapping("depthUp", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("depthDown", new KeyTrigger(KeyInput.KEY_J));
+//        inputManager.addMapping("scaleUp", new KeyTrigger(KeyInput.KEY_I));
+//        inputManager.addMapping("scaleDown", new KeyTrigger(KeyInput.KEY_K));
+//        inputManager.addMapping("biasUp", new KeyTrigger(KeyInput.KEY_O));
+//        inputManager.addMapping("biasDown", new KeyTrigger(KeyInput.KEY_L));
+//        inputManager.addMapping("outputConfig", new KeyTrigger(KeyInput.KEY_P));
+//        inputManager.addMapping("toggleUseAO", new KeyTrigger(KeyInput.KEY_SPACE));
+//        inputManager.addMapping("toggleUseOnlyAo", new KeyTrigger(KeyInput.KEY_NUMPAD0));
+        
+//        ActionListener acl = new ActionListener() {
+//
+//            public void onAction(String name, boolean keyPressed, float tpf) {
+//
+//                if (name.equals("toggleUseAO") && keyPressed) {
+//                    ssaoConfig.setUseAo(!ssaoConfig.isUseAo());
+//                    System.out.println("use AO : "+ssaoConfig.isUseAo());
+//                }
+//                if (name.equals("toggleUseOnlyAo") && keyPressed) {
+//                    ssaoConfig.setUseOnlyAo(!ssaoConfig.isUseOnlyAo());
+//                    System.out.println("use Only AO : "+ssaoConfig.isUseOnlyAo());
+//
+//                }
+//                if (name.equals("outputConfig") && keyPressed) {
+//                    System.out.println("new SSAOConfig("+ssaoConfig.getSampleRadius()+"f,"+ssaoConfig.getIntensity()+"f,"+ssaoConfig.getScale()+"f,"+ssaoConfig.getBias()+"f,"+ssaoConfig.isUseOnlyAo()+","+ssaoConfig.isUseAo()+");");
+//                }
+//
+//            }
+//        };
+
+         AnalogListener anl = new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("transparencyUp")) {
+                    processor.setWaterTransparency(processor.getWaterTransparency()+0.001f);
+                    System.out.println("Water transparency : "+processor.getWaterTransparency());
+                }
+                if (name.equals("transparencyDown")) {
+                    processor.setWaterTransparency(processor.getWaterTransparency()-0.001f);
+                    System.out.println("Water transparency : "+processor.getWaterTransparency());
+                }
+                if (name.equals("depthUp")) {
+                    processor.setWaterDepth(processor.getWaterDepth()+0.001f);
+                    System.out.println("Water depth : "+processor.getWaterDepth());
+                }
+                if (name.equals("depthDown")) {
+                    processor.setWaterDepth(processor.getWaterDepth()-0.001f);
+                    System.out.println("Water depth : "+processor.getWaterDepth());
+                }
+
+            }
+        };
+    //    inputManager.addListener(acl,"toggleUseAO","toggleUseOnlyAo","outputConfig");
+        inputManager.addListener(anl, "transparencyUp","transparencyDown","depthUp","depthDown");
+     
+    }
+    
+    
+
+}
diff --git a/engine/src/tools/jme3tools/converters/Converter.java b/engine/src/tools/jme3tools/converters/Converter.java
new file mode 100644
index 0000000..8e8df56
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/Converter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters;
+
+import java.util.Map;
+
+public interface Converter<T> {
+    public T convert(T input, Map<String, String> params);
+}
diff --git a/engine/src/tools/jme3tools/converters/FolderConverter.java b/engine/src/tools/jme3tools/converters/FolderConverter.java
new file mode 100644
index 0000000..8dc9922
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/FolderConverter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.system.JmeSystem;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+public class FolderConverter {
+
+    private static AssetManager assetManager;
+    private static File sourceRoot;
+    private static JarOutputStream jarOut;
+    private static long time;
+
+    private static void process(File file) throws IOException{
+        String name = file.getName().replaceAll("[\\/\\.]", "_");
+        JarEntry entry = new JarEntry(name);
+        entry.setTime(time);
+
+        jarOut.putNextEntry(entry);
+    }
+
+    public static void main(String[] args) throws IOException{
+        if (args.length == 0){
+            System.out.println("Usage: java -jar FolderConverter <input folder>");
+            System.out.println();
+            System.out.println("  Converts files from input to output");
+            System.exit(1);
+        }
+
+        sourceRoot = new File(args[0]);
+        
+        File jarFile = new File(sourceRoot.getParent(), sourceRoot.getName()+".jar");
+        FileOutputStream out = new FileOutputStream(jarFile);
+        jarOut = new JarOutputStream(out);
+
+        assetManager = JmeSystem.newAssetManager();
+        assetManager.registerLocator(sourceRoot.toString(), 
+                                     "com.jme3.asset.plugins.FileSystemLocator");
+        for (File f : sourceRoot.listFiles()){
+             process(f);
+        }
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/RGB565.java b/engine/src/tools/jme3tools/converters/RGB565.java
new file mode 100644
index 0000000..c0e43f5
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/RGB565.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters;
+
+/**
+ *
+ * @author Kirill
+ */
+public class RGB565 {
+
+    public static short ARGB8_to_RGB565(int argb){
+        int a = (argb & 0xFF000000) >> 24;
+        int r = (argb & 0x00FF0000) >> 16;
+        int g = (argb & 0x0000FF00) >> 8;
+        int b = (argb & 0x000000FF);
+
+        r  = r >> 3;
+        g  = g >> 2;
+        b  = b >> 3;
+
+        return (short) (b | (g << 5) | (r << (5 + 6)));
+    }
+
+    public static int RGB565_to_ARGB8(short rgb565){
+        int a = 0xff;
+        int r = (rgb565 & 0xf800) >> 11;
+        int g = (rgb565 & 0x07e0) >> 5;
+        int b = (rgb565 & 0x001f);
+
+        r  = r << 3;
+        g  = g << 2;
+        b  = b << 3;
+
+        return (a << 24) | (r << 16) | (g << 8) | (b);
+    }
+
+    
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/FloatToFixed.java b/engine/src/tools/jme3tools/converters/model/FloatToFixed.java
new file mode 100644
index 0000000..0d0647a
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/FloatToFixed.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters.model;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import java.nio.*;
+
+public class FloatToFixed {
+
+    private static final float shortSize = Short.MAX_VALUE - Short.MIN_VALUE;
+    private static final float shortOff  = (Short.MAX_VALUE + Short.MIN_VALUE) * 0.5f;
+
+    private static final float byteSize = Byte.MAX_VALUE - Byte.MIN_VALUE;
+    private static final float byteOff  = (Byte.MAX_VALUE + Byte.MIN_VALUE) * 0.5f;
+
+    public static void convertToFixed(Geometry geom, Format posFmt, Format nmFmt, Format tcFmt){
+        geom.updateModelBound();
+        BoundingBox bbox = (BoundingBox) geom.getModelBound();
+        Mesh mesh = geom.getMesh();
+
+        VertexBuffer positions = mesh.getBuffer(Type.Position);
+        VertexBuffer normals   = mesh.getBuffer(Type.Normal);
+        VertexBuffer texcoords = mesh.getBuffer(Type.TexCoord);
+        VertexBuffer indices   = mesh.getBuffer(Type.Index);
+
+        // positions
+        FloatBuffer fb = (FloatBuffer) positions.getData();
+        if (posFmt != Format.Float){
+            Buffer newBuf = VertexBuffer.createBuffer(posFmt, positions.getNumComponents(),
+                                                      mesh.getVertexCount());
+            Transform t = convertPositions(fb, bbox, newBuf);
+            t.combineWithParent(geom.getLocalTransform());
+            geom.setLocalTransform(t);
+
+            VertexBuffer newPosVb = new VertexBuffer(Type.Position);
+            newPosVb.setupData(positions.getUsage(),
+                               positions.getNumComponents(),
+                               posFmt,
+                               newBuf);
+            mesh.clearBuffer(Type.Position);
+            mesh.setBuffer(newPosVb);
+        }
+
+        // normals, automatically convert to signed byte
+        fb = (FloatBuffer) normals.getData();
+
+        ByteBuffer bb = BufferUtils.createByteBuffer(fb.capacity());
+        convertNormals(fb, bb);
+
+        normals = new VertexBuffer(Type.Normal);
+        normals.setupData(Usage.Static, 3, Format.Byte, bb);
+        normals.setNormalized(true);
+        mesh.clearBuffer(Type.Normal);
+        mesh.setBuffer(normals);
+
+        // texcoords
+        fb = (FloatBuffer) texcoords.getData();
+        if (tcFmt != Format.Float){
+            Buffer newBuf = VertexBuffer.createBuffer(tcFmt,
+                                                      texcoords.getNumComponents(),
+                                                      mesh.getVertexCount());
+            convertTexCoords2D(fb, newBuf);
+
+            VertexBuffer newTcVb = new VertexBuffer(Type.TexCoord);
+            newTcVb.setupData(texcoords.getUsage(),
+                              texcoords.getNumComponents(),
+                              tcFmt,
+                              newBuf);
+            mesh.clearBuffer(Type.TexCoord);
+            mesh.setBuffer(newTcVb);
+        }
+    }
+
+    public static void compressIndexBuffer(Mesh mesh){
+        int vertCount = mesh.getVertexCount();
+        VertexBuffer vb = mesh.getBuffer(Type.Index);
+        Format targetFmt;
+        if (vb.getFormat() == Format.UnsignedInt && vertCount <= 0xffff){
+            if (vertCount <= 256)
+                targetFmt = Format.UnsignedByte;
+            else
+                targetFmt = Format.UnsignedShort;
+        }else if (vb.getFormat() == Format.UnsignedShort && vertCount <= 0xff){
+            targetFmt = Format.UnsignedByte;
+        }else{
+            return;
+        }
+
+        IndexBuffer src = mesh.getIndexBuffer();
+        Buffer newBuf = VertexBuffer.createBuffer(targetFmt, vb.getNumComponents(), src.size());
+
+        VertexBuffer newVb = new VertexBuffer(Type.Index);
+        newVb.setupData(vb.getUsage(), vb.getNumComponents(), targetFmt, newBuf);
+        mesh.clearBuffer(Type.Index);
+        mesh.setBuffer(newVb);
+
+        IndexBuffer dst = mesh.getIndexBuffer();
+        for (int i = 0; i < src.size(); i++){
+            dst.put(i, src.get(i));
+        }
+    }
+
+    private static void convertToFixed(FloatBuffer input, IntBuffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        input.clear();
+        output.clear();
+        for (int i = 0; i < input.capacity(); i++){
+            output.put( (int) (input.get() * (float)(1<<16)) );
+        }
+        output.flip();
+    }
+
+    private static void convertToFloat(IntBuffer input, FloatBuffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        input.clear();
+        output.clear();
+        for (int i = 0; i < input.capacity(); i++){
+            output.put( ((float)input.get() / (float)(1<<16)) );
+        }
+        output.flip();
+    }
+
+    private static void convertToUByte(FloatBuffer input, ByteBuffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        input.clear();
+        output.clear();
+        for (int i = 0; i < input.capacity(); i++){
+            output.put( (byte) (input.get() * 255f) );
+        }
+        output.flip();
+    }
+
+
+    public static VertexBuffer convertToUByte(VertexBuffer vb){
+        FloatBuffer fb = (FloatBuffer) vb.getData();
+        ByteBuffer bb = BufferUtils.createByteBuffer(fb.capacity());
+        convertToUByte(fb, bb);
+
+        VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+        newVb.setupData(vb.getUsage(),
+                        vb.getNumComponents(),
+                        Format.UnsignedByte,
+                        bb);
+        newVb.setNormalized(true);
+        return newVb;
+    }
+
+    public static VertexBuffer convertToFixed(VertexBuffer vb){
+        if (vb.getFormat() == Format.Int)
+            return vb;
+
+        FloatBuffer fb = (FloatBuffer) vb.getData();
+        IntBuffer ib = BufferUtils.createIntBuffer(fb.capacity());
+        convertToFixed(fb, ib);
+
+        VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+        newVb.setupData(vb.getUsage(),
+                        vb.getNumComponents(),
+                        Format.Int,
+                        ib);
+        return newVb;
+    }
+
+    public static VertexBuffer convertToFloat(VertexBuffer vb){
+        if (vb.getFormat() == Format.Float)
+            return vb;
+
+        IntBuffer ib = (IntBuffer) vb.getData();
+        FloatBuffer fb = BufferUtils.createFloatBuffer(ib.capacity());
+        convertToFloat(ib, fb);
+
+        VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+        newVb.setupData(vb.getUsage(),
+                        vb.getNumComponents(),
+                        Format.Float,
+                        fb);
+        return newVb;
+    }
+
+    private static void convertNormals(FloatBuffer input, ByteBuffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        input.clear();
+        output.clear();
+        Vector3f temp = new Vector3f();
+        int vertexCount = input.capacity() / 3;
+        for (int i = 0; i < vertexCount; i++){
+            BufferUtils.populateFromBuffer(temp, input, i);
+
+            // offset and scale vector into -128 ... 127
+            temp.multLocal(127).addLocal(0.5f, 0.5f, 0.5f);
+
+            // quantize
+            byte v1 = (byte) temp.getX();
+            byte v2 = (byte) temp.getY();
+            byte v3 = (byte) temp.getZ();
+
+            // store
+            output.put(v1).put(v2).put(v3);
+        }
+    }
+
+    private static void convertTexCoords2D(FloatBuffer input, Buffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        input.clear();
+        output.clear();
+        Vector2f temp = new Vector2f();
+        int vertexCount = input.capacity() / 2;
+
+        ShortBuffer sb = null;
+        IntBuffer ib = null;
+
+        if (output instanceof ShortBuffer)
+            sb = (ShortBuffer) output;
+        else if (output instanceof IntBuffer)
+            ib = (IntBuffer) output;
+        else
+            throw new UnsupportedOperationException();
+
+        for (int i = 0; i < vertexCount; i++){
+            BufferUtils.populateFromBuffer(temp, input, i);
+
+            if (sb != null){
+                sb.put( (short) (temp.getX()*Short.MAX_VALUE) );
+                sb.put( (short) (temp.getY()*Short.MAX_VALUE) );
+            }else{
+                int v1 = (int) (temp.getX() * ((float)(1 << 16)));
+                int v2 = (int) (temp.getY() * ((float)(1 << 16)));
+                ib.put(v1).put(v2);
+            }
+        }
+    }
+
+    private static Transform convertPositions(FloatBuffer input, BoundingBox bbox, Buffer output){
+        if (output.capacity() < input.capacity())
+            throw new RuntimeException("Output must be at least as large as input!");
+
+        Vector3f offset = bbox.getCenter().negate();
+        Vector3f size = new Vector3f(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
+        size.multLocal(2);
+
+        ShortBuffer sb = null;
+        ByteBuffer bb = null;
+        float dataTypeSize;
+        float dataTypeOffset;
+        if (output instanceof ShortBuffer){
+            sb = (ShortBuffer) output;
+            dataTypeOffset = shortOff;
+            dataTypeSize = shortSize;
+        }else{
+            bb = (ByteBuffer) output;
+            dataTypeOffset = byteOff;
+            dataTypeSize = byteSize;
+        }
+        Vector3f scale = new Vector3f();
+        scale.set(dataTypeSize, dataTypeSize, dataTypeSize).divideLocal(size);
+
+        Vector3f invScale = new Vector3f();
+        invScale.set(size).divideLocal(dataTypeSize);
+
+        offset.multLocal(scale);
+        offset.addLocal(dataTypeOffset, dataTypeOffset, dataTypeOffset);
+
+        // offset = (-modelOffset * shortSize)/modelSize + shortOff
+        // scale = shortSize / modelSize
+
+        input.clear();
+        output.clear();
+        Vector3f temp = new Vector3f();
+        int vertexCount = input.capacity() / 3;
+        for (int i = 0; i < vertexCount; i++){
+            BufferUtils.populateFromBuffer(temp, input, i);
+
+            // offset and scale vector into -32768 ... 32767
+            // or into -128 ... 127 if using bytes
+            temp.multLocal(scale);
+            temp.addLocal(offset);
+
+            // quantize and store
+            if (sb != null){
+                short v1 = (short) temp.getX();
+                short v2 = (short) temp.getY();
+                short v3 = (short) temp.getZ();
+                sb.put(v1).put(v2).put(v3);
+            }else{
+                byte v1 = (byte) temp.getX();
+                byte v2 = (byte) temp.getY();
+                byte v3 = (byte) temp.getZ();
+                bb.put(v1).put(v2).put(v3);
+            }
+        }
+
+        Transform transform = new Transform();
+        transform.setTranslation(offset.negate().multLocal(invScale));
+        transform.setScale(invScale);
+        return transform;
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/ModelConverter.java b/engine/src/tools/jme3tools/converters/model/ModelConverter.java
new file mode 100644
index 0000000..2539574
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/ModelConverter.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.converters.model;
+
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.nio.Buffer;
+import java.util.Arrays;
+import java.util.Comparator;
+import jme3tools.converters.model.strip.PrimitiveGroup;
+import jme3tools.converters.model.strip.TriStrip;
+
+public class ModelConverter {
+
+    private static final class PrimComparator
+            implements Comparator<PrimitiveGroup> {
+
+        public int compare(PrimitiveGroup g1, PrimitiveGroup g2) {
+            if (g1.type < g2.type)
+                return -1;
+            else if (g1.type > g2.type)
+                return 1;
+            else
+                return 0;
+        }
+    }
+
+    private static final PrimComparator primComp = new PrimComparator();
+
+    public static void generateStrips(Mesh mesh, boolean stitch, boolean listOnly, int cacheSize, int minStripSize){
+        TriStrip ts = new TriStrip();
+        ts.setStitchStrips(stitch);
+        ts.setCacheSize(cacheSize);
+        ts.setListsOnly(listOnly);
+        ts.setMinStripSize(minStripSize);
+
+        IndexBuffer ib = mesh.getIndexBuffer();
+        int[] indices = new int[ib.size()];
+        for (int i = 0; i < indices.length; i++)
+            indices[i] = ib.get(i);
+
+        PrimitiveGroup[] groups = ts.generateStrips(indices);
+        Arrays.sort(groups, primComp);
+
+        int numElements = 0;
+        for (PrimitiveGroup group : groups)
+            numElements += group.numIndices;
+
+        VertexBuffer original = mesh.getBuffer(Type.Index);
+        Buffer buf = VertexBuffer.createBuffer(original.getFormat(),
+                                               original.getNumComponents(),
+                                               numElements);
+        original.updateData(buf);
+        ib = mesh.getIndexBuffer();
+
+        int curIndex = 0;
+        int[] modeStart = new int[]{ -1, -1, -1 };
+        int[] elementLengths = new int[groups.length];
+        for (int i = 0; i < groups.length; i++){
+            PrimitiveGroup group = groups[i];
+            elementLengths[i] = group.numIndices;
+
+            if (modeStart[group.type] == -1){
+                modeStart[group.type] = i;
+            }
+
+            int[] trimmedIndices = group.getTrimmedIndices();
+            for (int j = 0; j < trimmedIndices.length; j++){
+                ib.put(curIndex + j, trimmedIndices[j]);
+            }
+
+            curIndex += group.numIndices;
+        }
+
+        if (modeStart[0] == -1 && modeStart[1] == 0 && modeStart[2] == -1 &&
+                elementLengths.length == 1){
+            original.compact(elementLengths[0]);
+            mesh.setMode(Mode.TriangleStrip);
+        }else{
+            mesh.setElementLengths(elementLengths);
+            mesh.setModeStart(modeStart);
+            mesh.setMode(Mode.Hybrid);
+        }
+
+        mesh.updateCounts();
+    }
+
+    public static void optimize(Mesh mesh, boolean toFixed){
+        // update any data that need updating
+        mesh.updateBound();
+        mesh.updateCounts();
+
+        // set all buffers into STATIC_DRAW mode
+        mesh.setStatic();
+
+        if (mesh.getBuffer(Type.Index) != null){
+            // compress index buffer from UShort to UByte (if possible)
+            FloatToFixed.compressIndexBuffer(mesh);
+
+            // generate triangle strips stitched with degenerate tris
+            generateStrips(mesh, false, false, 16, 0);
+        }
+
+        IntMap<VertexBuffer> bufs = mesh.getBuffers();
+        for (Entry<VertexBuffer> entry : bufs){
+            VertexBuffer vb = entry.getValue();
+            if (vb == null || vb.getBufferType() == Type.Index)
+                continue;
+
+             if (vb.getFormat() == Format.Float){
+                if (vb.getBufferType() == Type.Color){
+                    // convert the color buffer to UByte
+                    vb = FloatToFixed.convertToUByte(vb);
+                    vb.setNormalized(true);
+                }else if (toFixed){
+                    // convert normals, positions, and texcoords
+                    // to fixed-point (16.16)
+                    vb = FloatToFixed.convertToFixed(vb);
+//                    vb = FloatToFixed.convertToFloat(vb);
+                }
+                mesh.clearBuffer(vb.getBufferType());
+                mesh.setBuffer(vb);
+            }
+        }
+        mesh.setInterleaved();
+    }
+
+    private static void optimizeScene(Spatial source, boolean toFixed){
+        if (source instanceof Geometry){
+            Geometry geom = (Geometry) source;
+            Mesh mesh = geom.getMesh();
+            optimize(mesh, toFixed);
+        }else if (source instanceof Node){
+            Node node = (Node) source;
+            for (int i = node.getQuantity() - 1; i >= 0; i--){
+                Spatial child = node.getChild(i);
+                optimizeScene(child, toFixed);
+            }
+        }
+    }
+
+    public static void optimize(Spatial source, boolean toFixed){
+        optimizeScene(source, toFixed);
+        source.updateLogicalState(0);
+        source.updateGeometricState();
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java
new file mode 100644
index 0000000..02b7350
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java
@@ -0,0 +1,53 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+/**

+ *  

+ */

+class EdgeInfo {

+

+    FaceInfo m_face0, m_face1;

+    int m_v0, m_v1;

+    EdgeInfo m_nextV0, m_nextV1;

+

+    public EdgeInfo(int v0, int v1) {

+        m_v0 = v0;

+        m_v1 = v1;

+        m_face0 = null;

+        m_face1 = null;

+        m_nextV0 = null;

+        m_nextV1 = null;

+

+    }

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java
new file mode 100644
index 0000000..f289d78
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java
@@ -0,0 +1,50 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+import java.util.ArrayList;

+

+class EdgeInfoVec extends ArrayList<EdgeInfo> {

+

+    private static final long serialVersionUID = 1L;

+

+	public EdgeInfoVec() {

+        super();

+    }

+    

+    public EdgeInfo at(int index) {

+        return get(index);

+    }

+

+

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java b/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java
new file mode 100644
index 0000000..e917ec7
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java
@@ -0,0 +1,59 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+

+class FaceInfo {

+

+    int   m_v0, m_v1, m_v2;

+    int   m_stripId;      // real strip Id

+    int   m_testStripId;  // strip Id in an experiment

+    int   m_experimentId; // in what experiment was it given an experiment Id?

+    

+    public FaceInfo(int v0, int v1, int v2){

+        m_v0 = v0; m_v1 = v1; m_v2 = v2;

+        m_stripId      = -1;

+        m_testStripId  = -1;

+        m_experimentId = -1;

+    }

+    

+    public void set(FaceInfo o) {

+        m_v0 = o.m_v0;

+        m_v1 = o.m_v1;

+        m_v2 = o.m_v2;

+        

+        m_stripId = o.m_stripId;

+        m_testStripId = o.m_testStripId;

+        m_experimentId = o.m_experimentId;

+    }

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java
new file mode 100644
index 0000000..8319280
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java
@@ -0,0 +1,54 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+import java.util.ArrayList;

+

+class FaceInfoVec extends ArrayList<FaceInfo> {

+

+

+    private static final long serialVersionUID = 1L;

+

+	public FaceInfoVec() {

+        super();

+    }

+    

+    public FaceInfo at(int index) {

+        return get(index);

+    }

+

+    public void reserve(int i) {

+        super.ensureCapacity(i);

+    }

+

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/IntVec.java b/engine/src/tools/jme3tools/converters/model/strip/IntVec.java
new file mode 100644
index 0000000..7e7eecb
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/IntVec.java
@@ -0,0 +1,71 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+

+

+public class IntVec {

+

+    private int[] data;

+    private int count = 0;

+    

+    public IntVec() {

+        data = new int[16];

+    }

+    

+    public IntVec(int startSize) {

+        data = new int[startSize];

+    }

+    

+    public int size() {

+        return count;

+    }

+    

+    public int get(int i) {

+        return data[i];

+    }

+    

+    public void add(int val) {

+        if ( count == data.length ) {

+            int[] ndata = new int[count*2];

+            System.arraycopy(data,0,ndata,0,count);

+            data = ndata;

+        }

+        data[count] = val;

+        count++;

+    }

+    

+    public void clear() {

+        count = 0;

+    }

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java b/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java
new file mode 100644
index 0000000..51697ee
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java
@@ -0,0 +1,107 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+/**

+ * 

+ */

+public class PrimitiveGroup {

+

+    public static final int PT_LIST = 0;

+    public static final int PT_STRIP = 1;

+    public static final int PT_FAN = 2;

+

+    public int type;

+    public int[] indices;

+    public int numIndices;

+    

+    public PrimitiveGroup() {

+        type = PT_STRIP;

+    }

+    

+    public String getTypeString() {

+        switch(type) {

+            case PT_LIST : return "list";

+            case PT_STRIP: return "strip";

+            case PT_FAN: return "fan";

+            default: return "????";

+        }

+    }

+    

+    public String toString() {

+        return getTypeString() + " : " + numIndices;

+    }

+    

+    public String getFullInfo() {

+        if ( type != PT_STRIP )

+            return toString();

+        

+        int[] stripLengths = new int[numIndices];

+        

+        int prev = -1;

+        int length = -1;

+        for ( int i =0; i < numIndices; i++) {

+            if (indices[i] == prev) {

+                stripLengths[length]++;

+                length = -1;

+                prev = -1;

+            } else {

+                prev = indices[i];

+                length++;

+            }

+        }

+        stripLengths[length]++;

+        

+        StringBuffer sb = new StringBuffer();

+        sb.append("Strip:").append(numIndices).append("\n");

+        for ( int i =0; i < stripLengths.length; i++) {

+            if ( stripLengths[i] > 0) {

+                sb.append(i).append("->").append(stripLengths[i]).append("\n");

+            }

+        }

+        return sb.toString();

+    }

+

+    /**

+     * @return

+     */

+    public int[] getTrimmedIndices() {

+        if ( indices.length == numIndices )

+            return indices;

+        int[] nind = new int[numIndices];

+        System.arraycopy(indices,0,nind,0,numIndices);

+        return nind;

+    }

+

+}

+

diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java b/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java
new file mode 100644
index 0000000..a9b1098
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java
@@ -0,0 +1,355 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+/**

+ * 

+ */

+class StripInfo {

+

+    StripStartInfo m_startInfo;

+    FaceInfoVec    m_faces = new FaceInfoVec();

+    int              m_stripId;

+    int              m_experimentId;

+    

+    boolean visited;

+

+    int m_numDegenerates;

+    

+

+    public StripInfo(StripStartInfo startInfo,int stripId, int experimentId) {

+

+        m_startInfo = startInfo;

+        m_stripId      = stripId;

+        m_experimentId = experimentId;

+        visited = false;

+        m_numDegenerates = 0;

+    }

+

+    boolean isExperiment() {

+        return m_experimentId >= 0;

+    }

+    

+    boolean isInStrip(FaceInfo faceInfo) {

+        if(faceInfo == null)

+            return false;

+        

+        return (m_experimentId >= 0 ? faceInfo.m_testStripId == m_stripId : faceInfo.m_stripId == m_stripId);

+    }

+    

+

+///////////////////////////////////////////////////////////////////////////////////////////

+// IsMarked()

+//

+// If either the faceInfo has a real strip index because it is

+// already assign to a committed strip OR it is assigned in an

+// experiment and the experiment index is the one we are building

+// for, then it is marked and unavailable

+    boolean isMarked(FaceInfo faceInfo){

+        return (faceInfo.m_stripId >= 0) || (isExperiment() && faceInfo.m_experimentId == m_experimentId);

+    }

+

+

+///////////////////////////////////////////////////////////////////////////////////////////

+// MarkTriangle()

+//

+// Marks the face with the current strip ID

+//

+    void markTriangle(FaceInfo faceInfo){

+        if (isExperiment()){

+            faceInfo.m_experimentId = m_experimentId;

+            faceInfo.m_testStripId  = m_stripId;

+        }

+        else{

+            faceInfo.m_experimentId = -1;

+            faceInfo.m_stripId      = m_stripId;

+        }

+    }

+

+

+    boolean unique(FaceInfoVec faceVec, FaceInfo face)

+    {

+        boolean bv0, bv1, bv2; //bools to indicate whether a vertex is in the faceVec or not

+        bv0 = bv1 = bv2 = false;

+

+        for(int i = 0; i < faceVec.size(); i++)

+           {

+            if(!bv0)

+               {

+                if( (faceVec.at(i).m_v0 == face.m_v0) || 

+                        (faceVec.at(i).m_v1 == face.m_v0) ||

+                        (faceVec.at(i).m_v2 == face.m_v0) )

+                    bv0 = true;

+            }

+

+            if(!bv1)

+               {

+                if( (faceVec.at(i).m_v0 == face.m_v1) || 

+                        (faceVec.at(i).m_v1 == face.m_v1) ||

+                        (faceVec.at(i).m_v2 == face.m_v1) )

+                    bv1 = true;

+            }

+

+            if(!bv2)

+               {

+                if( (faceVec.at(i).m_v0 == face.m_v2) || 

+                        (faceVec.at(i).m_v1 == face.m_v2) ||

+                        (faceVec.at(i).m_v2 == face.m_v2) )

+                    bv2 = true;

+            }

+

+            //the face is not unique, all it's vertices exist in the face vector

+            if(bv0 && bv1 && bv2)

+                return false;

+        }

+        

+        //if we get out here, it's unique

+        return true;

+    }

+

+

+///////////////////////////////////////////////////////////////////////////////////////////

+// Build()

+//

+// Builds a strip forward as far as we can go, then builds backwards, and joins the two lists

+//

+    void build(EdgeInfoVec edgeInfos, FaceInfoVec faceInfos)

+    {

+        // used in building the strips forward and backward

+        IntVec scratchIndices = new IntVec();

+        

+        // build forward... start with the initial face

+        FaceInfoVec forwardFaces = new FaceInfoVec();

+        FaceInfoVec backwardFaces = new FaceInfoVec();

+        forwardFaces.add(m_startInfo.m_startFace);

+

+        markTriangle(m_startInfo.m_startFace);

+        

+        int v0 = (m_startInfo.m_toV1 ? m_startInfo.m_startEdge.m_v0 : m_startInfo.m_startEdge.m_v1);

+        int v1 = (m_startInfo.m_toV1 ? m_startInfo.m_startEdge.m_v1 : m_startInfo.m_startEdge.m_v0);

+        

+        // easiest way to get v2 is to use this function which requires the

+        // other indices to already be in the list.

+        scratchIndices.add(v0);

+        scratchIndices.add(v1);

+        int v2 = Stripifier.getNextIndex(scratchIndices, m_startInfo.m_startFace);

+        scratchIndices.add(v2);

+

+        //

+        // build the forward list

+        //

+        int nv0 = v1;

+        int nv1 = v2;

+

+        FaceInfo nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, m_startInfo.m_startFace);

+        while (nextFace != null && !isMarked(nextFace))

+           {

+            //check to see if this next face is going to cause us to die soon

+            int testnv0 = nv1;

+            int testnv1 = Stripifier.getNextIndex(scratchIndices, nextFace);

+            

+            FaceInfo nextNextFace = Stripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);

+

+            if( (nextNextFace == null) || (isMarked(nextNextFace)) )

+               {

+                //uh, oh, we're following a dead end, try swapping

+                FaceInfo testNextFace = Stripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);

+

+                if( ((testNextFace != null) && !isMarked(testNextFace)) )

+                   {

+                    //we only swap if it buys us something

+                    

+                    //add a "fake" degenerate face

+                    FaceInfo tempFace = new FaceInfo(nv0, nv1, nv0);

+                    

+                    forwardFaces.add(tempFace);

+                    markTriangle(tempFace);

+

+                    scratchIndices.add(nv0);

+                    testnv0 = nv0;

+

+                    ++m_numDegenerates;

+                }

+

+            }

+

+            // add this to the strip

+            forwardFaces.add(nextFace);

+

+            markTriangle(nextFace);

+            

+            // add the index

+            //nv0 = nv1;

+            //nv1 = NvStripifier::GetNextIndex(scratchIndices, nextFace);

+            scratchIndices.add(testnv1);

+            

+            // and get the next face

+            nv0 = testnv0;

+            nv1 = testnv1;

+

+            nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);

+            

+        }

+        

+        // tempAllFaces is going to be forwardFaces + backwardFaces

+        // it's used for Unique()

+        FaceInfoVec tempAllFaces = new FaceInfoVec();

+        for(int i = 0; i < forwardFaces.size(); i++)

+            tempAllFaces.add(forwardFaces.at(i));

+

+        //

+        // reset the indices for building the strip backwards and do so

+        //

+        scratchIndices.clear();

+        scratchIndices.add(v2);

+        scratchIndices.add(v1);

+        scratchIndices.add(v0);

+        nv0 = v1;

+        nv1 = v0;

+        nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, m_startInfo.m_startFace);

+        while (nextFace != null && !isMarked(nextFace))

+           {

+            //this tests to see if a face is "unique", meaning that its vertices aren't already in the list

+            // so, strips which "wrap-around" are not allowed

+            if(!unique(tempAllFaces, nextFace))

+                break;

+

+            //check to see if this next face is going to cause us to die soon

+            int testnv0 = nv1;

+            int testnv1 = Stripifier.getNextIndex(scratchIndices, nextFace);

+            

+            FaceInfo nextNextFace = Stripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);

+

+            if( (nextNextFace == null) || (isMarked(nextNextFace)) )

+               {

+                //uh, oh, we're following a dead end, try swapping

+                FaceInfo testNextFace = Stripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);

+                if( ((testNextFace != null) && !isMarked(testNextFace)) )

+                   {

+                    //we only swap if it buys us something

+                    

+                    //add a "fake" degenerate face

+                    FaceInfo tempFace = new FaceInfo(nv0, nv1, nv0);

+

+                    backwardFaces.add(tempFace);

+                    markTriangle(tempFace);

+                    scratchIndices.add(nv0);

+                    testnv0 = nv0;

+

+                    ++m_numDegenerates;

+                }

+                

+            }

+

+            // add this to the strip

+            backwardFaces.add(nextFace);

+            

+            //this is just so Unique() will work

+            tempAllFaces.add(nextFace);

+

+            markTriangle(nextFace);

+            

+            // add the index

+            //nv0 = nv1;

+            //nv1 = NvStripifier::GetNextIndex(scratchIndices, nextFace);

+            scratchIndices.add(testnv1);

+            

+            // and get the next face

+            nv0 = testnv0;

+            nv1 = testnv1;

+            nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);

+        }

+        

+        // Combine the forward and backwards stripification lists and put into our own face vector

+        combine(forwardFaces, backwardFaces);

+    }

+

+

+///////////////////////////////////////////////////////////////////////////////////////////

+// Combine()

+//

+// Combines the two input face vectors and puts the result into m_faces

+//

+    void combine(FaceInfoVec forward, FaceInfoVec backward){

+        

+        // add backward faces

+        int numFaces = backward.size();

+        for (int i = numFaces - 1; i >= 0; i--)

+            m_faces.add(backward.at(i));

+        

+        // add forward faces

+        numFaces = forward.size();

+        for (int i = 0; i < numFaces; i++)

+            m_faces.add(forward.at(i));

+    }

+

+

+///////////////////////////////////////////////////////////////////////////////////////////

+// SharesEdge()

+//

+// Returns true if the input face and the current strip share an edge

+//

+    boolean sharesEdge(FaceInfo faceInfo, EdgeInfoVec edgeInfos)

+    {

+        //check v0.v1 edge

+        EdgeInfo currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v0, faceInfo.m_v1);

+        

+        if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))

+            return true;

+        

+        //check v1.v2 edge

+        currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v1, faceInfo.m_v2);

+        

+        if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))

+            return true;

+        

+        //check v2.v0 edge

+        currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v2, faceInfo.m_v0);

+        

+        if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))

+            return true;

+        

+        return false;

+        

+    }

+

+    

+    

+    

+    

+

+    

+    

+    

+    

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java
new file mode 100644
index 0000000..58165f9
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java
@@ -0,0 +1,51 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+import java.util.ArrayList;

+

+

+class StripInfoVec extends ArrayList<StripInfo> {

+

+

+    private static final long serialVersionUID = 1L;

+

+	public StripInfoVec() {

+        super();

+    }

+    

+    public StripInfo at(int index) {

+        return get(index);

+    }

+    

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java b/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java
new file mode 100644
index 0000000..69f5157
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java
@@ -0,0 +1,49 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+class StripStartInfo {

+

+

+    FaceInfo    m_startFace;

+    EdgeInfo    m_startEdge;

+    boolean          m_toV1;   

+      

+

+    public StripStartInfo(FaceInfo startFace, EdgeInfo startEdge, boolean toV1){

+        m_startFace    = startFace;

+        m_startEdge    = startEdge;

+        m_toV1         = toV1;

+    }

+

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java b/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java
new file mode 100644
index 0000000..c8630fe
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java
@@ -0,0 +1,1365 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+import java.util.HashSet;

+import java.util.logging.Logger;

+

+/**

+ *  

+ */

+class Stripifier {

+    private static final Logger logger = Logger.getLogger(Stripifier.class

+            .getName());

+

+	public static int CACHE_INEFFICIENCY = 6;

+

+	IntVec indices = new IntVec();

+

+	int cacheSize;

+

+	int minStripLength;

+

+	float meshJump;

+

+	boolean bFirstTimeResetPoint;

+

+	Stripifier() {

+		super();

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindEdgeInfo()

+	//

+	// find the edge info for these two indices

+	//

+	static EdgeInfo findEdgeInfo(EdgeInfoVec edgeInfos, int v0, int v1) {

+

+		// we can get to it through either array

+		// because the edge infos have a v0 and v1

+		// and there is no order except how it was

+		// first created.

+		EdgeInfo infoIter = edgeInfos.at(v0);

+		while (infoIter != null) {

+			if (infoIter.m_v0 == v0) {

+				if (infoIter.m_v1 == v1)

+					return infoIter;

+				

+				infoIter = infoIter.m_nextV0;

+			} else {

+				if (infoIter.m_v0 == v1)

+					return infoIter;

+				

+				infoIter = infoIter.m_nextV1;

+			}

+		}

+		return null;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindOtherFace

+	//

+	// find the other face sharing these vertices

+	// exactly like the edge info above

+	//

+	static FaceInfo findOtherFace(EdgeInfoVec edgeInfos, int v0, int v1,

+			FaceInfo faceInfo) {

+		EdgeInfo edgeInfo = findEdgeInfo(edgeInfos, v0, v1);

+

+		if ((edgeInfo == null) || (v0 == v1)) {

+			//we've hit a degenerate

+			return null;

+		}

+

+		return (edgeInfo.m_face0 == faceInfo ? edgeInfo.m_face1

+				: edgeInfo.m_face0);

+	}

+

+	static boolean alreadyExists(FaceInfo faceInfo, FaceInfoVec faceInfos) {

+		for (int i = 0; i < faceInfos.size(); ++i) {

+			FaceInfo o = faceInfos.at(i);

+			if ((o.m_v0 == faceInfo.m_v0) && (o.m_v1 == faceInfo.m_v1)

+					&& (o.m_v2 == faceInfo.m_v2))

+				return true;

+		}

+		return false;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// BuildStripifyInfo()

+	//

+	// Builds the list of all face and edge infos

+	//

+	void buildStripifyInfo(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos,

+			int maxIndex) {

+		// reserve space for the face infos, but do not resize them.

+		int numIndices = indices.size();

+		faceInfos.reserve(numIndices / 3);

+

+		// we actually resize the edge infos, so we must initialize to null

+		for (int i = 0; i < maxIndex + 1; i++)

+			edgeInfos.add(null);

+

+		// iterate through the triangles of the triangle list

+		int numTriangles = numIndices / 3;

+		int index = 0;

+		boolean[] bFaceUpdated = new boolean[3];

+

+		for (int i = 0; i < numTriangles; i++) {

+			boolean bMightAlreadyExist = true;

+			bFaceUpdated[0] = false;

+			bFaceUpdated[1] = false;

+			bFaceUpdated[2] = false;

+

+			// grab the indices

+			int v0 = indices.get(index++);

+			int v1 = indices.get(index++);

+			int v2 = indices.get(index++);

+

+			//we disregard degenerates

+			if (isDegenerate(v0, v1, v2))

+				continue;

+

+			// create the face info and add it to the list of faces, but only

+			// if this exact face doesn't already

+			//  exist in the list

+			FaceInfo faceInfo = new FaceInfo(v0, v1, v2);

+

+			// grab the edge infos, creating them if they do not already exist

+			EdgeInfo edgeInfo01 = findEdgeInfo(edgeInfos, v0, v1);

+			if (edgeInfo01 == null) {

+				//since one of it's edges isn't in the edge data structure, it

+				// can't already exist in the face structure

+				bMightAlreadyExist = false;

+

+				// create the info

+				edgeInfo01 = new EdgeInfo(v0, v1);

+

+				// update the linked list on both

+				edgeInfo01.m_nextV0 = edgeInfos.at(v0);

+				edgeInfo01.m_nextV1 = edgeInfos.at(v1);

+				edgeInfos.set(v0, edgeInfo01);

+				edgeInfos.set(v1, edgeInfo01);

+

+				// set face 0

+				edgeInfo01.m_face0 = faceInfo;

+			} else {

+				if (edgeInfo01.m_face1 != null) {

+					logger.info("BuildStripifyInfo: > 2 triangles on an edge"

+                            + v0 + "," + v1 + "... uncertain consequences\n");

+				} else {

+					edgeInfo01.m_face1 = faceInfo;

+					bFaceUpdated[0] = true;

+				}

+			}

+

+			// grab the edge infos, creating them if they do not already exist

+			EdgeInfo edgeInfo12 = findEdgeInfo(edgeInfos, v1, v2);

+			if (edgeInfo12 == null) {

+				bMightAlreadyExist = false;

+

+				// create the info

+				edgeInfo12 = new EdgeInfo(v1, v2);

+

+				// update the linked list on both

+				edgeInfo12.m_nextV0 = edgeInfos.at(v1);

+				edgeInfo12.m_nextV1 = edgeInfos.at(v2);

+				edgeInfos.set(v1, edgeInfo12);

+				edgeInfos.set(v2, edgeInfo12);

+

+				// set face 0

+				edgeInfo12.m_face0 = faceInfo;

+			} else {

+				if (edgeInfo12.m_face1 != null) {

+					logger.info("BuildStripifyInfo: > 2 triangles on an edge"

+									+ v1

+									+ ","

+									+ v2

+									+ "... uncertain consequences\n");

+				} else {

+					edgeInfo12.m_face1 = faceInfo;

+					bFaceUpdated[1] = true;

+				}

+			}

+

+			// grab the edge infos, creating them if they do not already exist

+			EdgeInfo edgeInfo20 = findEdgeInfo(edgeInfos, v2, v0);

+			if (edgeInfo20 == null) {

+				bMightAlreadyExist = false;

+

+				// create the info

+				edgeInfo20 = new EdgeInfo(v2, v0);

+

+				// update the linked list on both

+				edgeInfo20.m_nextV0 = edgeInfos.at(v2);

+				edgeInfo20.m_nextV1 = edgeInfos.at(v0);

+				edgeInfos.set(v2, edgeInfo20);

+				edgeInfos.set(v0, edgeInfo20);

+

+				// set face 0

+				edgeInfo20.m_face0 = faceInfo;

+			} else {

+				if (edgeInfo20.m_face1 != null) {

+					logger.info("BuildStripifyInfo: > 2 triangles on an edge"

+									+ v2

+									+ ","

+									+ v0

+									+ "... uncertain consequences\n");

+				} else {

+					edgeInfo20.m_face1 = faceInfo;

+					bFaceUpdated[2] = true;

+				}

+			}

+

+			if (bMightAlreadyExist) {

+				if (!alreadyExists(faceInfo, faceInfos))

+					faceInfos.add(faceInfo);

+				else {

+

+					//cleanup pointers that point to this deleted face

+					if (bFaceUpdated[0])

+						edgeInfo01.m_face1 = null;

+					if (bFaceUpdated[1])

+						edgeInfo12.m_face1 = null;

+					if (bFaceUpdated[2])

+						edgeInfo20.m_face1 = null;

+				}

+			} else {

+				faceInfos.add(faceInfo);

+			}

+

+		}

+	}

+

+	static boolean isDegenerate(FaceInfo face) {

+		if (face.m_v0 == face.m_v1)

+			return true;

+		else if (face.m_v0 == face.m_v2)

+			return true;

+		else if (face.m_v1 == face.m_v2)

+			return true;

+		else

+			return false;

+	}

+

+	static boolean isDegenerate(int v0, int v1, int v2) {

+		if (v0 == v1)

+			return true;

+		else if (v0 == v2)

+			return true;

+		else if (v1 == v2)

+			return true;

+		else

+			return false;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// GetNextIndex()

+	//

+	// Returns vertex of the input face which is "next" in the input index list

+	//

+	static int getNextIndex(IntVec indices, FaceInfo face) {

+

+		int numIndices = indices.size();

+		

+		int v0 = indices.get(numIndices - 2);

+		int v1 = indices.get(numIndices - 1);

+

+		int fv0 = face.m_v0;

+		int fv1 = face.m_v1;

+		int fv2 = face.m_v2;

+

+		if (fv0 != v0 && fv0 != v1) {

+			if ((fv1 != v0 && fv1 != v1) || (fv2 != v0 && fv2 != v1)) {

+                logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");

+                logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");

+			}

+			return fv0;

+		}

+		if (fv1 != v0 && fv1 != v1) {

+			if ((fv0 != v0 && fv0 != v1) || (fv2 != v0 && fv2 != v1)) {

+                logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");

+                logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");

+			}

+			return fv1;

+		}

+		if (fv2 != v0 && fv2 != v1) {

+			if ((fv0 != v0 && fv0 != v1) || (fv1 != v0 && fv1 != v1)) {

+                logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");

+                logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");

+			}

+			return fv2;

+		}

+

+		// shouldn't get here, but let's try and fail gracefully

+		if ((fv0 == fv1) || (fv0 == fv2))

+			return fv0;

+		else if ((fv1 == fv0) || (fv1 == fv2))

+			return fv1;

+		else if ((fv2 == fv0) || (fv2 == fv1))

+			return fv2;

+		else

+			return -1;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindStartPoint()

+	//

+	// Finds a good starting point, namely one which has only one neighbor

+	//

+	static int findStartPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) {

+		int bestCtr = -1;

+		int bestIndex = -1;

+

+		for (int i = 0; i < faceInfos.size(); i++) {

+			int ctr = 0;

+

+			if (findOtherFace(edgeInfos, faceInfos.at(i).m_v0,

+					faceInfos.at(i).m_v1, faceInfos.at(i)) == null)

+				ctr++;

+			if (findOtherFace(edgeInfos, faceInfos.at(i).m_v1,

+					faceInfos.at(i).m_v2, faceInfos.at(i)) == null)

+				ctr++;

+			if (findOtherFace(edgeInfos, faceInfos.at(i).m_v2,

+					faceInfos.at(i).m_v0, faceInfos.at(i)) == null)

+				ctr++;

+			if (ctr > bestCtr) {

+				bestCtr = ctr;

+				bestIndex = i;

+				//return i;

+			}

+		}

+		//return -1;

+

+		if (bestCtr == 0)

+			return -1;

+		

+		return bestIndex;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindGoodResetPoint()

+	//  

+	// A good reset point is one near other commited areas so that

+	// we know that when we've made the longest strips its because

+	// we're stripifying in the same general orientation.

+	//

+	FaceInfo findGoodResetPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) {

+		// we hop into different areas of the mesh to try to get

+		// other large open spans done. Areas of small strips can

+		// just be left to triangle lists added at the end.

+		FaceInfo result = null;

+

+		if (result == null) {

+			int numFaces = faceInfos.size();

+			int startPoint;

+			if (bFirstTimeResetPoint) {

+				//first time, find a face with few neighbors (look for an edge

+				// of the mesh)

+				startPoint = findStartPoint(faceInfos, edgeInfos);

+				bFirstTimeResetPoint = false;

+			} else

+				startPoint = (int) (((float) numFaces - 1) * meshJump);

+

+			if (startPoint == -1) {

+				startPoint = (int) (((float) numFaces - 1) * meshJump);

+

+				//meshJump += 0.1f;

+				//if (meshJump > 1.0f)

+				//  meshJump = .05f;

+			}

+

+			int i = startPoint;

+			do {

+

+				// if this guy isn't visited, try him

+				if (faceInfos.at(i).m_stripId < 0) {

+					result = faceInfos.at(i);

+					break;

+				}

+

+				// update the index and clamp to 0-(numFaces-1)

+				if (++i >= numFaces)

+					i = 0;

+

+			} while (i != startPoint);

+

+			// update the meshJump

+			meshJump += 0.1f;

+			if (meshJump > 1.0f)

+				meshJump = .05f;

+		}

+

+		// return the best face we found

+		return result;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// GetUniqueVertexInB()

+	//

+	// Returns the vertex unique to faceB

+	//

+	static int getUniqueVertexInB(FaceInfo faceA, FaceInfo faceB) {

+

+		int facev0 = faceB.m_v0;

+		if (facev0 != faceA.m_v0 && facev0 != faceA.m_v1

+				&& facev0 != faceA.m_v2)

+			return facev0;

+

+		int facev1 = faceB.m_v1;

+		if (facev1 != faceA.m_v0 && facev1 != faceA.m_v1

+				&& facev1 != faceA.m_v2)

+			return facev1;

+

+		int facev2 = faceB.m_v2;

+		if (facev2 != faceA.m_v0 && facev2 != faceA.m_v1

+				&& facev2 != faceA.m_v2)

+			return facev2;

+

+		// nothing is different

+		return -1;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// GetSharedVertices()

+	//

+	// Returns the (at most) two vertices shared between the two faces

+	//

+	static void getSharedVertices(FaceInfo faceA, FaceInfo faceB, int[] vertex) {

+		vertex[0] = -1;

+		vertex[1] = -1;

+

+		int facev0 = faceB.m_v0;

+		if (facev0 == faceA.m_v0 || facev0 == faceA.m_v1

+				|| facev0 == faceA.m_v2) {

+			if (vertex[0] == -1)

+				vertex[0] = facev0;

+			else {

+				vertex[1] = facev0;

+				return;

+			}

+		}

+

+		int facev1 = faceB.m_v1;

+		if (facev1 == faceA.m_v0 || facev1 == faceA.m_v1

+				|| facev1 == faceA.m_v2) {

+			if (vertex[0] == -1)

+				vertex[0] = facev1;

+			else {

+				vertex[1] = facev1;

+				return;

+			}

+		}

+

+		int facev2 = faceB.m_v2;

+		if (facev2 == faceA.m_v0 || facev2 == faceA.m_v1

+				|| facev2 == faceA.m_v2) {

+			if (vertex[0] == -1)

+				vertex[0] = facev2;

+			else {

+				vertex[1] = facev2;

+				return;

+			}

+		}

+

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// CommitStrips()

+	//

+	// "Commits" the input strips by setting their m_experimentId to -1 and

+	// adding to the allStrips

+	//  vector

+	//

+	static void commitStrips(StripInfoVec allStrips, StripInfoVec strips) {

+		// Iterate through strips

+		int numStrips = strips.size();

+		for (int i = 0; i < numStrips; i++) {

+

+			// Tell the strip that it is now real

+			StripInfo strip = strips.at(i);

+			strip.m_experimentId = -1;

+

+			// add to the list of real strips

+			allStrips.add(strip);

+

+			// Iterate through the faces of the strip

+			// Tell the faces of the strip that they belong to a real strip now

+			FaceInfoVec faces = strips.at(i).m_faces;

+			int numFaces = faces.size();

+

+			for (int j = 0; j < numFaces; j++) {

+				strip.markTriangle(faces.at(j));

+			}

+		}

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// NextIsCW()

+	//

+	// Returns true if the next face should be ordered in CW fashion

+	//

+	static boolean nextIsCW(int numIndices) {

+		return ((numIndices % 2) == 0);

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// UpdateCacheFace()

+	//

+	// Updates the input vertex cache with this face's vertices

+	//

+	static void updateCacheFace(VertexCache vcache, FaceInfo face) {

+		if (!vcache.inCache(face.m_v0))

+			vcache.addEntry(face.m_v0);

+

+		if (!vcache.inCache(face.m_v1))

+			vcache.addEntry(face.m_v1);

+

+		if (!vcache.inCache(face.m_v2))

+			vcache.addEntry(face.m_v2);

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// UpdateCacheStrip()

+	//

+	// Updates the input vertex cache with this strip's vertices

+	//

+	static void updateCacheStrip(VertexCache vcache, StripInfo strip) {

+		for (int i = 0; i < strip.m_faces.size(); ++i) {

+			if (!vcache.inCache(strip.m_faces.at(i).m_v0))

+				vcache.addEntry(strip.m_faces.at(i).m_v0);

+

+			if (!vcache.inCache(strip.m_faces.at(i).m_v1))

+				vcache.addEntry(strip.m_faces.at(i).m_v1);

+

+			if (!vcache.inCache(strip.m_faces.at(i).m_v2))

+				vcache.addEntry(strip.m_faces.at(i).m_v2);

+		}

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// CalcNumHitsStrip()

+	//

+	// returns the number of cache hits per face in the strip

+	//

+	static float calcNumHitsStrip(VertexCache vcache, StripInfo strip) {

+		int numHits = 0;

+		int numFaces = 0;

+

+		for (int i = 0; i < strip.m_faces.size(); i++) {

+			if (vcache.inCache(strip.m_faces.at(i).m_v0))

+				++numHits;

+

+			if (vcache.inCache(strip.m_faces.at(i).m_v1))

+				++numHits;

+

+			if (vcache.inCache(strip.m_faces.at(i).m_v2))

+				++numHits;

+

+			numFaces++;

+		}

+

+		return ((float) numHits / (float) numFaces);

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// AvgStripSize()

+	//

+	// Finds the average strip size of the input vector of strips

+	//

+	static float avgStripSize(StripInfoVec strips) {

+		int sizeAccum = 0;

+		int numStrips = strips.size();

+		for (int i = 0; i < numStrips; i++) {

+			StripInfo strip = strips.at(i);

+			sizeAccum += strip.m_faces.size();

+			sizeAccum -= strip.m_numDegenerates;

+		}

+		return ((float) sizeAccum) / ((float) numStrips);

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// CalcNumHitsFace()

+	//

+	// returns the number of cache hits in the face

+	//

+	static int calcNumHitsFace(VertexCache vcache, FaceInfo face) {

+		int numHits = 0;

+

+		if (vcache.inCache(face.m_v0))

+			numHits++;

+

+		if (vcache.inCache(face.m_v1))

+			numHits++;

+

+		if (vcache.inCache(face.m_v2))

+			numHits++;

+

+		return numHits;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// NumNeighbors()

+	//

+	// Returns the number of neighbors that this face has

+	//

+	static int numNeighbors(FaceInfo face, EdgeInfoVec edgeInfoVec) {

+		int numNeighbors = 0;

+

+		if (findOtherFace(edgeInfoVec, face.m_v0, face.m_v1, face) != null) {

+			numNeighbors++;

+		}

+

+		if (findOtherFace(edgeInfoVec, face.m_v1, face.m_v2, face) != null) {

+			numNeighbors++;

+		}

+

+		if (findOtherFace(edgeInfoVec, face.m_v2, face.m_v0, face) != null) {

+			numNeighbors++;

+		}

+

+		return numNeighbors;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// IsCW()

+	//

+	// Returns true if the face is ordered in CW fashion

+	//

+	static boolean isCW(FaceInfo faceInfo, int v0, int v1) {

+		if (faceInfo.m_v0 == v0)

+			return (faceInfo.m_v1 == v1);

+		else if (faceInfo.m_v1 == v0)

+			return (faceInfo.m_v2 == v1);

+		else

+			return (faceInfo.m_v0 == v1);

+

+	}

+

+	static boolean faceContainsIndex(FaceInfo face, int index) {

+		return ((face.m_v0 == index) || (face.m_v1 == index) || (face.m_v2 == index));

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindTraversal()

+	//

+	// Finds the next face to start the next strip on.

+	//

+	static boolean findTraversal(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos,

+			StripInfo strip, StripStartInfo startInfo) {

+

+		// if the strip was v0.v1 on the edge, then v1 will be a vertex in the

+		// next edge.

+		int v = (strip.m_startInfo.m_toV1 ? strip.m_startInfo.m_startEdge.m_v1

+				: strip.m_startInfo.m_startEdge.m_v0);

+

+		FaceInfo untouchedFace = null;

+		EdgeInfo edgeIter = edgeInfos.at(v);

+		while (edgeIter != null) {

+			FaceInfo face0 = edgeIter.m_face0;

+			FaceInfo face1 = edgeIter.m_face1;

+			if ((face0 != null && !strip.isInStrip(face0)) && face1 != null

+					&& !strip.isMarked(face1)) {

+				untouchedFace = face1;

+				break;

+			}

+			if ((face1 != null && !strip.isInStrip(face1)) && face0 != null

+					&& !strip.isMarked(face0)) {

+				untouchedFace = face0;

+				break;

+			}

+

+			// find the next edgeIter

+			edgeIter = (edgeIter.m_v0 == v ? edgeIter.m_nextV0

+					: edgeIter.m_nextV1);

+		}

+

+		startInfo.m_startFace = untouchedFace;

+		startInfo.m_startEdge = edgeIter;

+		if (edgeIter != null) {

+			if (strip.sharesEdge(startInfo.m_startFace, edgeInfos))

+				startInfo.m_toV1 = (edgeIter.m_v0 == v); //note! used to be

+			// m_v1

+			else

+				startInfo.m_toV1 = (edgeIter.m_v1 == v);

+		}

+		return (startInfo.m_startFace != null);

+	}

+

+	////////////////////////////////////////////////////////////////////////////////////////

+	// RemoveSmallStrips()

+	//

+	// allStrips is the whole strip vector...all small strips will be deleted

+	// from this list, to avoid leaking mem

+	// allBigStrips is an out parameter which will contain all strips above

+	// minStripLength

+	// faceList is an out parameter which will contain all faces which were

+	// removed from the striplist

+	//

+	void removeSmallStrips(StripInfoVec allStrips, StripInfoVec allBigStrips,

+			FaceInfoVec faceList) {

+		faceList.clear();

+		allBigStrips.clear(); //make sure these are empty

+		FaceInfoVec tempFaceList = new FaceInfoVec();

+

+		for (int i = 0; i < allStrips.size(); i++) {

+			if (allStrips.at(i).m_faces.size() < minStripLength) {

+				//strip is too small, add faces to faceList

+				for (int j = 0; j < allStrips.at(i).m_faces.size(); j++)

+					tempFaceList.add(allStrips.at(i).m_faces.at(j));

+

+			} else {

+				allBigStrips.add(allStrips.at(i));

+			}

+		}

+

+		boolean[] bVisitedList = new boolean[tempFaceList.size()];

+

+		VertexCache vcache = new VertexCache(cacheSize);

+

+		int bestNumHits = -1;

+		int numHits;

+		int bestIndex = -9999;

+

+		while (true) {

+			bestNumHits = -1;

+

+			//find best face to add next, given the current cache

+			for (int i = 0; i < tempFaceList.size(); i++) {

+				if (bVisitedList[i])

+					continue;

+

+				numHits = calcNumHitsFace(vcache, tempFaceList.at(i));

+				if (numHits > bestNumHits) {

+					bestNumHits = numHits;

+					bestIndex = i;

+				}

+			}

+

+			if (bestNumHits == -1.0f)

+				break;

+			bVisitedList[bestIndex] = true;

+			updateCacheFace(vcache, tempFaceList.at(bestIndex));

+			faceList.add(tempFaceList.at(bestIndex));

+		}

+	}

+

+	////////////////////////////////////////////////////////////////////////////////////////

+	// CreateStrips()

+	//

+	// Generates actual strips from the list-in-strip-order.

+	//

+	int createStrips(StripInfoVec allStrips, IntVec stripIndices,

+			boolean bStitchStrips) {

+		int numSeparateStrips = 0;

+

+		FaceInfo tLastFace = new FaceInfo(0, 0, 0);

+		int nStripCount = allStrips.size();

+		

+		//we infer the cw/ccw ordering depending on the number of indices

+		//this is screwed up by the fact that we insert -1s to denote changing

+		// strips

+		//this is to account for that

+		int accountForNegatives = 0;

+

+		for (int i = 0; i < nStripCount; i++) {

+			StripInfo strip = allStrips.at(i);

+			int nStripFaceCount = strip.m_faces.size();

+			

+			// Handle the first face in the strip

+			{

+				FaceInfo tFirstFace = new FaceInfo(strip.m_faces.at(0).m_v0,

+						strip.m_faces.at(0).m_v1, strip.m_faces.at(0).m_v2);

+

+				// If there is a second face, reorder vertices such that the

+				// unique vertex is first

+				if (nStripFaceCount > 1) {

+					int nUnique = getUniqueVertexInB(strip.m_faces.at(1),

+							tFirstFace);

+					if (nUnique == tFirstFace.m_v1) {

+						int tmp = tFirstFace.m_v0;

+						tFirstFace.m_v0 = tFirstFace.m_v1;

+						tFirstFace.m_v1 = tmp;

+					} else if (nUnique == tFirstFace.m_v2) {

+						int tmp = tFirstFace.m_v0;

+						tFirstFace.m_v0 = tFirstFace.m_v2;

+						tFirstFace.m_v2 = tmp;

+					}

+

+					// If there is a third face, reorder vertices such that the

+					// shared vertex is last

+					if (nStripFaceCount > 2) {

+						if (isDegenerate(strip.m_faces.at(1))) {

+							int pivot = strip.m_faces.at(1).m_v1;

+							if (tFirstFace.m_v1 == pivot) {

+								int tmp = tFirstFace.m_v1;

+								tFirstFace.m_v1 = tFirstFace.m_v2;

+								tFirstFace.m_v2 = tmp;

+							}

+						} else {

+							int[] nShared = new int[2];

+							getSharedVertices(strip.m_faces.at(2), tFirstFace,

+									nShared);

+							if ((nShared[0] == tFirstFace.m_v1)

+									&& (nShared[1] == -1)) {

+								int tmp = tFirstFace.m_v1;

+								tFirstFace.m_v1 = tFirstFace.m_v2;

+								tFirstFace.m_v2 = tmp;

+							}

+						}

+					}

+				}

+

+				if ((i == 0) || !bStitchStrips) {

+					if (!isCW(strip.m_faces.at(0), tFirstFace.m_v0,

+							tFirstFace.m_v1))

+						stripIndices.add(tFirstFace.m_v0);

+				} else {

+					// Double tap the first in the new strip

+					stripIndices.add(tFirstFace.m_v0);

+

+					// Check CW/CCW ordering

+					if (nextIsCW(stripIndices.size() - accountForNegatives) != isCW(

+							strip.m_faces.at(0), tFirstFace.m_v0,

+							tFirstFace.m_v1)) {

+						stripIndices.add(tFirstFace.m_v0);

+					}

+				}

+

+				stripIndices.add(tFirstFace.m_v0);

+				stripIndices.add(tFirstFace.m_v1);

+				stripIndices.add(tFirstFace.m_v2);

+

+				// Update last face info

+				tLastFace.set(tFirstFace);

+			}

+

+			for (int j = 1; j < nStripFaceCount; j++) {

+				int nUnique = getUniqueVertexInB(tLastFace, strip.m_faces.at(j));

+				if (nUnique != -1) {

+					stripIndices.add(nUnique);

+

+					// Update last face info

+					tLastFace.m_v0 = tLastFace.m_v1;

+					tLastFace.m_v1 = tLastFace.m_v2;

+					tLastFace.m_v2 = nUnique;

+				} else {

+					//we've hit a degenerate

+					stripIndices.add(strip.m_faces.at(j).m_v2);

+					tLastFace.m_v0 = strip.m_faces.at(j).m_v0; //tLastFace.m_v1;

+					tLastFace.m_v1 = strip.m_faces.at(j).m_v1; //tLastFace.m_v2;

+					tLastFace.m_v2 = strip.m_faces.at(j).m_v2; //tLastFace.m_v1;

+

+				}

+			}

+

+			// Double tap between strips.

+			if (bStitchStrips) {

+				if (i != nStripCount - 1)

+					stripIndices.add(tLastFace.m_v2);

+			} else {

+				//-1 index indicates next strip

+				stripIndices.add(-1);

+				accountForNegatives++;

+				numSeparateStrips++;

+			}

+

+			// Update last face info

+			tLastFace.m_v0 = tLastFace.m_v1;

+			tLastFace.m_v1 = tLastFace.m_v2;

+			tLastFace.m_v2 = tLastFace.m_v2;

+		}

+

+		if (bStitchStrips)

+			numSeparateStrips = 1;

+		return numSeparateStrips;

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// FindAllStrips()

+	//

+	// Does the stripification, puts output strips into vector allStrips

+	//

+	// Works by setting runnning a number of experiments in different areas of

+	// the mesh, and

+	//  accepting the one which results in the longest strips. It then accepts

+	// this, and moves

+	//  on to a different area of the mesh. We try to jump around the mesh some,

+	// to ensure that

+	//  large open spans of strips get generated.

+	//

+	void findAllStrips(StripInfoVec allStrips, FaceInfoVec allFaceInfos,

+			EdgeInfoVec allEdgeInfos, int numSamples) {

+		// the experiments

+		int experimentId = 0;

+		int stripId = 0;

+		boolean done = false;

+

+		int loopCtr = 0;

+

+		while (!done) {

+			loopCtr++;

+

+			//

+			// PHASE 1: Set up numSamples * numEdges experiments

+			//

+			StripInfoVec[] experiments = new StripInfoVec[numSamples * 6];

+			for (int i = 0; i < experiments.length; i++)

+				experiments[i] = new StripInfoVec();

+

+			int experimentIndex = 0;

+			HashSet<FaceInfo> resetPoints = new HashSet<FaceInfo>(); /* NvFaceInfo */

+			for (int i = 0; i < numSamples; i++) {

+				// Try to find another good reset point.

+				// If there are none to be found, we are done

+				FaceInfo nextFace = findGoodResetPoint(allFaceInfos,

+						allEdgeInfos);

+				if (nextFace == null) {

+					done = true;

+					break;

+				}

+				// If we have already evaluated starting at this face in this

+				// slew of experiments, then skip going any further

+				else if (resetPoints.contains(nextFace)) {

+					continue;

+				}

+

+				// trying it now...

+				resetPoints.add(nextFace);

+

+				// otherwise, we shall now try experiments for starting on the

+				// 01,12, and 20 edges

+				

+				// build the strip off of this face's 0-1 edge

+				EdgeInfo edge01 = findEdgeInfo(allEdgeInfos, nextFace.m_v0,

+						nextFace.m_v1);

+				StripInfo strip01 = new StripInfo(new StripStartInfo(nextFace,

+						edge01, true), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip01);

+

+				// build the strip off of this face's 1-0 edge

+				EdgeInfo edge10 = findEdgeInfo(allEdgeInfos, nextFace.m_v0,

+						nextFace.m_v1);

+				StripInfo strip10 = new StripInfo(new StripStartInfo(nextFace,

+						edge10, false), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip10);

+

+				// build the strip off of this face's 1-2 edge

+				EdgeInfo edge12 = findEdgeInfo(allEdgeInfos, nextFace.m_v1,

+						nextFace.m_v2);

+				StripInfo strip12 = new StripInfo(new StripStartInfo(nextFace,

+						edge12, true), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip12);

+

+				// build the strip off of this face's 2-1 edge

+				EdgeInfo edge21 = findEdgeInfo(allEdgeInfos, nextFace.m_v1,

+						nextFace.m_v2);

+				StripInfo strip21 = new StripInfo(new StripStartInfo(nextFace,

+						edge21, false), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip21);

+

+				// build the strip off of this face's 2-0 edge

+				EdgeInfo edge20 = findEdgeInfo(allEdgeInfos, nextFace.m_v2,

+						nextFace.m_v0);

+				StripInfo strip20 = new StripInfo(new StripStartInfo(nextFace,

+						edge20, true), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip20);

+

+				// build the strip off of this face's 0-2 edge

+				EdgeInfo edge02 = findEdgeInfo(allEdgeInfos, nextFace.m_v2,

+						nextFace.m_v0);

+				StripInfo strip02 = new StripInfo(new StripStartInfo(nextFace,

+						edge02, false), stripId++, experimentId++);

+				experiments[experimentIndex++].add(strip02);

+			}

+

+			//

+			// PHASE 2: Iterate through that we setup in the last phase

+			// and really build each of the strips and strips that follow to

+			// see how

+			// far we get

+			//

+			int numExperiments = experimentIndex;

+			for (int i = 0; i < numExperiments; i++) {

+

+				// get the strip set

+

+				// build the first strip of the list

+				experiments[i].at(0).build(allEdgeInfos, allFaceInfos);

+				int experimentId2 = experiments[i].at(0).m_experimentId;

+

+				StripInfo stripIter = experiments[i].at(0);

+				StripStartInfo startInfo = new StripStartInfo(null, null, false);

+				while (findTraversal(allFaceInfos, allEdgeInfos, stripIter,

+						startInfo)) {

+

+					// create the new strip info

+					//TODO startInfo clone ?

+					stripIter = new StripInfo(startInfo, stripId++,

+							experimentId2);

+

+					// build the next strip

+					stripIter.build(allEdgeInfos, allFaceInfos);

+

+					// add it to the list

+					experiments[i].add(stripIter);

+				}

+			}

+

+			//

+			// Phase 3: Find the experiment that has the most promise

+			//

+			int bestIndex = 0;

+			double bestValue = 0;

+			for (int i = 0; i < numExperiments; i++) {

+				float avgStripSizeWeight = 1.0f;

+				//float numTrisWeight = 0.0f;

+				float numStripsWeight = 0.0f;

+				float avgStripSize = avgStripSize(experiments[i]);

+				float numStrips = experiments[i].size();

+				float value = avgStripSize * avgStripSizeWeight

+						+ (numStrips * numStripsWeight);

+				//float value = 1.f / numStrips;

+				//float value = numStrips * avgStripSize;

+

+				if (value > bestValue) {

+					bestValue = value;

+					bestIndex = i;

+				}

+			}

+

+			//

+			// Phase 4: commit the best experiment of the bunch

+			//

+			commitStrips(allStrips, experiments[bestIndex]);

+		}

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// SplitUpStripsAndOptimize()

+	//

+	// Splits the input vector of strips (allBigStrips) into smaller, cache

+	// friendly pieces, then

+	//  reorders these pieces to maximize cache hits

+	// The final strips are output through outStrips

+	//

+	void splitUpStripsAndOptimize(StripInfoVec allStrips,

+			StripInfoVec outStrips, EdgeInfoVec edgeInfos,

+			FaceInfoVec outFaceList) {

+		int threshold = cacheSize;

+		StripInfoVec tempStrips = new StripInfoVec();

+		int j;

+

+		//split up strips into threshold-sized pieces

+		for (int i = 0; i < allStrips.size(); i++) {

+			StripInfo currentStrip;

+			StripStartInfo startInfo = new StripStartInfo(null, null, false);

+

+			int actualStripSize = 0;

+			for (j = 0; j < allStrips.at(i).m_faces.size(); ++j) {

+				if (!isDegenerate(allStrips.at(i).m_faces.at(j)))

+					actualStripSize++;

+			}

+

+			if (actualStripSize /* allStrips.at(i).m_faces.size() */

+			> threshold) {

+

+				int numTimes = actualStripSize /* allStrips.at(i).m_faces.size() */

+						/ threshold;

+				int numLeftover = actualStripSize /* allStrips.at(i).m_faces.size() */

+						% threshold;

+

+				int degenerateCount = 0;

+				for (j = 0; j < numTimes; j++) {

+					currentStrip = new StripInfo(startInfo, 0, -1);

+

+					int faceCtr = j * threshold + degenerateCount;

+					boolean bFirstTime = true;

+					while (faceCtr < threshold + (j * threshold)

+							+ degenerateCount) {

+						if (isDegenerate(allStrips.at(i).m_faces.at(faceCtr))) {

+							degenerateCount++;

+

+							//last time or first time through, no need for a

+							// degenerate

+							if ((((faceCtr + 1) != threshold + (j * threshold)

+									+ degenerateCount) || ((j == numTimes - 1)

+									&& (numLeftover < 4) && (numLeftover > 0)))

+									&& !bFirstTime) {

+								currentStrip.m_faces

+										.add(allStrips.at(i).m_faces

+												.at(faceCtr++));

+							} else

+								++faceCtr;

+						} else {

+							currentStrip.m_faces.add(allStrips.at(i).m_faces

+									.at(faceCtr++));

+							bFirstTime = false;

+						}

+					}

+					/*

+					 * threshold; faceCtr < threshold+(j*threshold); faceCtr++) {

+					 * currentStrip.m_faces.add(allStrips.at(i).m_faces.at(faceCtr]); }

+					 */

+					///*

+					if (j == numTimes - 1) //last time through

+					{

+						if ((numLeftover < 4) && (numLeftover > 0)) //way too

+						// small

+						{

+							//just add to last strip

+							int ctr = 0;

+							while (ctr < numLeftover) {

+								if (!isDegenerate(allStrips.at(i).m_faces

+										.at(faceCtr))) {

+									currentStrip.m_faces

+											.add(allStrips.at(i).m_faces

+													.at(faceCtr++));

+									++ctr;

+								} else {

+									currentStrip.m_faces

+											.add(allStrips.at(i).m_faces

+													.at(faceCtr++));

+									++degenerateCount;

+								}

+							}

+							numLeftover = 0;

+						}

+					}

+					//*/

+					tempStrips.add(currentStrip);

+				}

+

+				int leftOff = j * threshold + degenerateCount;

+

+				if (numLeftover != 0) {

+					currentStrip = new StripInfo(startInfo, 0, -1);

+

+					int ctr = 0;

+					boolean bFirstTime = true;

+					while (ctr < numLeftover) {

+						if (!isDegenerate(allStrips.at(i).m_faces.at(leftOff))) {

+							ctr++;

+							bFirstTime = false;

+							currentStrip.m_faces.add(allStrips.at(i).m_faces

+									.at(leftOff++));

+						} else if (!bFirstTime)

+							currentStrip.m_faces.add(allStrips.at(i).m_faces

+									.at(leftOff++));

+						else

+							leftOff++;

+					}

+					/*

+					 * for(int k = 0; k < numLeftover; k++) {

+					 * currentStrip.m_faces.add(allStrips.at(i).m_faces[leftOff++]); }

+					 */

+

+					tempStrips.add(currentStrip);

+				}

+			} else {

+				//we're not just doing a tempStrips.add(allBigStrips[i])

+				// because

+				// this way we can delete allBigStrips later to free the memory

+				currentStrip = new StripInfo(startInfo, 0, -1);

+

+				for (j = 0; j < allStrips.at(i).m_faces.size(); j++)

+					currentStrip.m_faces.add(allStrips.at(i).m_faces.at(j));

+

+				tempStrips.add(currentStrip);

+			}

+		}

+

+		//add small strips to face list

+		StripInfoVec tempStrips2 = new StripInfoVec();

+		removeSmallStrips(tempStrips, tempStrips2, outFaceList);

+

+		outStrips.clear();

+		//screw optimization for now

+		//  for(i = 0; i < tempStrips.size(); ++i)

+		//    outStrips.add(tempStrips[i]);

+

+		if (tempStrips2.size() != 0) {

+			//Optimize for the vertex cache

+			VertexCache vcache = new VertexCache(cacheSize);

+

+			float bestNumHits = -1.0f;

+			float numHits;

+			int bestIndex = -99999;

+

+			int firstIndex = 0;

+			float minCost = 10000.0f;

+

+			for (int i = 0; i < tempStrips2.size(); i++) {

+				int numNeighbors = 0;

+

+				//find strip with least number of neighbors per face

+				for (j = 0; j < tempStrips2.at(i).m_faces.size(); j++) {

+					numNeighbors += numNeighbors(tempStrips2.at(i).m_faces

+							.at(j), edgeInfos);

+				}

+

+				float currCost = (float) numNeighbors

+						/ (float) tempStrips2.at(i).m_faces.size();

+				if (currCost < minCost) {

+					minCost = currCost;

+					firstIndex = i;

+				}

+			}

+

+			updateCacheStrip(vcache, tempStrips2.at(firstIndex));

+			outStrips.add(tempStrips2.at(firstIndex));

+

+			tempStrips2.at(firstIndex).visited = true;

+

+			boolean bWantsCW = (tempStrips2.at(firstIndex).m_faces.size() % 2) == 0;

+

+			//this n^2 algo is what slows down stripification so much....

+			// needs to be improved

+			while (true) {

+				bestNumHits = -1.0f;

+

+				//find best strip to add next, given the current cache

+				for (int i = 0; i < tempStrips2.size(); i++) {

+					if (tempStrips2.at(i).visited)

+						continue;

+

+					numHits = calcNumHitsStrip(vcache, tempStrips2.at(i));

+					if (numHits > bestNumHits) {

+						bestNumHits = numHits;

+						bestIndex = i;

+					} else if (numHits >= bestNumHits) {

+						//check previous strip to see if this one requires it

+						// to switch polarity

+						StripInfo strip = tempStrips2.at(i);

+						int nStripFaceCount = strip.m_faces.size();

+

+						FaceInfo tFirstFace = new FaceInfo(

+								strip.m_faces.at(0).m_v0,

+								strip.m_faces.at(0).m_v1,

+								strip.m_faces.at(0).m_v2);

+

+						// If there is a second face, reorder vertices such

+						// that the

+						// unique vertex is first

+						if (nStripFaceCount > 1) {

+							int nUnique = getUniqueVertexInB(strip.m_faces

+									.at(1), tFirstFace);

+							if (nUnique == tFirstFace.m_v1) {

+								int tmp = tFirstFace.m_v0;

+								tFirstFace.m_v0 = tFirstFace.m_v1;

+								tFirstFace.m_v1 = tmp;

+							} else if (nUnique == tFirstFace.m_v2) {

+								int tmp = tFirstFace.m_v0;

+								tFirstFace.m_v0 = tFirstFace.m_v2;

+								tFirstFace.m_v2 = tmp;

+							}

+

+							// If there is a third face, reorder vertices such

+							// that the

+							// shared vertex is last

+							if (nStripFaceCount > 2) {

+								int[] nShared = new int[2];

+								getSharedVertices(strip.m_faces.at(2),

+										tFirstFace, nShared);

+								if ((nShared[0] == tFirstFace.m_v1)

+										&& (nShared[1] == -1)) {

+									int tmp = tFirstFace.m_v2;

+									tFirstFace.m_v2 = tFirstFace.m_v1;

+									tFirstFace.m_v1 = tmp;

+								}

+							}

+						}

+

+						// Check CW/CCW ordering

+						if (bWantsCW == isCW(strip.m_faces.at(0),

+								tFirstFace.m_v0, tFirstFace.m_v1)) {

+							//I like this one!

+							bestIndex = i;

+						}

+					}

+				}

+

+				if (bestNumHits == -1.0f)

+					break;

+				tempStrips2.at(bestIndex).visited = true;

+				updateCacheStrip(vcache, tempStrips2.at(bestIndex));

+				outStrips.add(tempStrips2.at(bestIndex));

+				bWantsCW = (tempStrips2.at(bestIndex).m_faces.size() % 2 == 0) ? bWantsCW

+						: !bWantsCW;

+			}

+		}

+	}

+

+	///////////////////////////////////////////////////////////////////////////////////////////

+	// Stripify()

+	//

+	//

+	// in_indices are the input indices of the mesh to stripify

+	// in_cacheSize is the target cache size

+	//

+	void stripify(IntVec in_indices, int in_cacheSize, int in_minStripLength,

+			int maxIndex, StripInfoVec outStrips, FaceInfoVec outFaceList) {

+		meshJump = 0.0f;

+		bFirstTimeResetPoint = true; //used in FindGoodResetPoint()

+

+		//the number of times to run the experiments

+		int numSamples = 10;

+

+		//the cache size, clamped to one

+		cacheSize = Math.max(1, in_cacheSize - CACHE_INEFFICIENCY);

+

+		minStripLength = in_minStripLength;

+		//this is the strip size threshold below which we dump the strip into

+		// a list

+

+		indices = in_indices;

+

+		// build the stripification info

+		FaceInfoVec allFaceInfos = new FaceInfoVec();

+		EdgeInfoVec allEdgeInfos = new EdgeInfoVec();

+

+		buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex);

+

+		StripInfoVec allStrips = new StripInfoVec();

+

+		// stripify

+		findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples);

+

+		//split up the strips into cache friendly pieces, optimize them, then

+		// dump these into outStrips

+		splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos,

+				outFaceList);

+

+	}

+

+}
\ No newline at end of file
diff --git a/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java b/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java
new file mode 100644
index 0000000..aa84d5c
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java
@@ -0,0 +1,311 @@
+/*

+ * Copyright (c) 2003-2009 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 jme3tools.converters.model.strip;

+

+import java.util.Arrays;

+

+/**

+ * To use, call generateStrips method, passing your triangle index list and 

+ * then construct geometry/render resulting PrimitiveGroup objects.

+ * Features:

+ * <ul> 

+ * <li>generates strips from arbitrary geometry. 

+ * <li>flexibly optimizes for post TnL vertex caches (16 on GeForce1/2, 24 on GeForce3). 

+ * <li>can stitch together strips using degenerate triangles, or not. 

+ * <li>can output lists instead of strips. 

+ * <li>can optionally throw excessively small strips into a list instead. 

+ * <li>can remap indices to improve spatial locality in your vertex buffers.

+ * </ul>

+ * On cache sizes: Note that it's better to UNDERESTIMATE the cache size

+ * instead of OVERESTIMATING. So, if you're targetting GeForce1, 2, and 3, be

+ * conservative and use the GeForce1_2 cache size, NOT the GeForce3 cache size.

+ * This will make sure you don't "blow" the cache of the GeForce1 and 2. Also

+ * note that the cache size you specify is the "actual" cache size, not the

+ * "effective" cache size you may have heard about. This is 16 for GeForce1 and 2,

+ * and 24 for GeForce3.

+ * 

+ * Credit goes to Curtis Beeson and Joe Demers for the basis for this

+ * stripifier and to Jason Regier and Jon Stone at Blizzard for providing a

+ * much cleaner version of CreateStrips().

+ * 

+ * Ported to java by Artur Biesiadowski <abies@pg.gda.pl> 

+ */

+public class TriStrip {

+

+    public static final int CACHESIZE_GEFORCE1_2 = 16;

+    public static final int CACHESIZE_GEFORCE3 = 24;

+

+    int cacheSize = CACHESIZE_GEFORCE1_2;

+    boolean bStitchStrips = true;

+    int minStripSize = 0;

+    boolean bListsOnly = false;

+

+    /**

+	 *  

+	 */

+    public TriStrip() {

+        super();

+    }

+

+    /**

+	 * If set to true, will return an optimized list, with no strips at all.

+	 * Default value: false

+	 */

+    public void setListsOnly(boolean _bListsOnly) {

+        bListsOnly = _bListsOnly;

+    }

+

+    /**

+	 * Sets the cache size which the stripfier uses to optimize the data.

+	 * Controls the length of the generated individual strips. This is the

+	 * "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may

+	 * want to play around with this number to tweak performance. Default

+	 * value: 16

+	 */

+    public void setCacheSize(int _cacheSize) {

+        cacheSize = _cacheSize;

+    }

+

+    /**

+	 * bool to indicate whether to stitch together strips into one huge strip

+	 * or not. If set to true, you'll get back one huge strip stitched together

+	 * using degenerate triangles. If set to false, you'll get back a large

+	 * number of separate strips. Default value: true

+	 */

+    public void setStitchStrips(boolean _bStitchStrips) {

+        bStitchStrips = _bStitchStrips;

+    }

+

+    /**

+	 * Sets the minimum acceptable size for a strip, in triangles. All strips

+	 * generated which are shorter than this will be thrown into one big,

+	 * separate list. Default value: 0

+	 */

+    public void setMinStripSize(int _minStripSize) {

+        minStripSize = _minStripSize;

+    }

+

+    /**

+	 * @param in_indices

+	 *            input index list, the indices you would use to render

+	 * @return array of optimized/stripified PrimitiveGroups

+	 */

+    public PrimitiveGroup[] generateStrips(int[] in_indices) {

+        int numGroups = 0;

+        PrimitiveGroup[] primGroups;

+        //put data in format that the stripifier likes

+        IntVec tempIndices = new IntVec();

+        int maxIndex = 0;

+

+        for (int i = 0; i < in_indices.length; i++) {

+            tempIndices.add(in_indices[i]);

+            if (in_indices[i] > maxIndex)

+                maxIndex = in_indices[i];

+        }

+

+        StripInfoVec tempStrips = new StripInfoVec();

+        FaceInfoVec tempFaces = new FaceInfoVec();

+

+        Stripifier stripifier = new Stripifier();

+

+        //do actual stripification

+        stripifier.stripify(tempIndices, cacheSize, minStripSize, maxIndex, tempStrips, tempFaces);

+

+        //stitch strips together

+        IntVec stripIndices = new IntVec();

+        int numSeparateStrips = 0;

+

+        if (bListsOnly) {

+            //if we're outputting only lists, we're done

+            numGroups = 1;

+            primGroups = new PrimitiveGroup[numGroups];

+            primGroups[0] = new PrimitiveGroup();

+            PrimitiveGroup[] primGroupArray = primGroups;

+

+            //count the total number of indices

+            int numIndices = 0;

+            for (int i = 0; i < tempStrips.size(); i++) {

+                numIndices += tempStrips.at(i).m_faces.size() * 3;

+            }

+

+            //add in the list

+            numIndices += tempFaces.size() * 3;

+

+            primGroupArray[0].type = PrimitiveGroup.PT_LIST;

+            primGroupArray[0].indices = new int[numIndices];

+            primGroupArray[0].numIndices = numIndices;

+

+            //do strips

+            int indexCtr = 0;

+            for (int i = 0; i < tempStrips.size(); i++) {

+                for (int j = 0; j < tempStrips.at(i).m_faces.size(); j++) {

+                    //degenerates are of no use with lists

+                    if (!Stripifier.isDegenerate(tempStrips.at(i).m_faces.at(j))) {

+                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v0;

+                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v1;

+                        primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v2;

+                    } else {

+                        //we've removed a tri, reduce the number of indices

+                        primGroupArray[0].numIndices -= 3;

+                    }

+                }

+            }

+

+            //do lists

+            for (int i = 0; i < tempFaces.size(); i++) {

+                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v0;

+                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v1;

+                primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v2;

+            }

+        } else {

+            numSeparateStrips = stripifier.createStrips(tempStrips, stripIndices, bStitchStrips);

+

+            //if we're stitching strips together, we better get back only one

+            // strip from CreateStrips()

+            

+            //convert to output format

+            numGroups = numSeparateStrips; //for the strips

+            if (tempFaces.size() != 0)

+                numGroups++; //we've got a list as well, increment

+            primGroups = new PrimitiveGroup[numGroups];

+            for (int i = 0; i < primGroups.length; i++) {

+                primGroups[i] = new PrimitiveGroup();

+            }

+

+            PrimitiveGroup[] primGroupArray = primGroups;

+

+            //first, the strips

+            int startingLoc = 0;

+            for (int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++) {

+                int stripLength = 0;

+

+                if (!bStitchStrips) {

+                    int i;

+                    //if we've got multiple strips, we need to figure out the

+                    // correct length

+                    for (i = startingLoc; i < stripIndices.size(); i++) {

+                        if (stripIndices.get(i) == -1)

+                            break;

+                    }

+

+                    stripLength = i - startingLoc;

+                } else

+                    stripLength = stripIndices.size();

+

+                primGroupArray[stripCtr].type = PrimitiveGroup.PT_STRIP;

+                primGroupArray[stripCtr].indices = new int[stripLength];

+                primGroupArray[stripCtr].numIndices = stripLength;

+

+                int indexCtr = 0;

+                for (int i = startingLoc; i < stripLength + startingLoc; i++)

+                    primGroupArray[stripCtr].indices[indexCtr++] = stripIndices.get(i);

+

+                //we add 1 to account for the -1 separating strips

+                //this doesn't break the stitched case since we'll exit the

+                // loop

+                startingLoc += stripLength + 1;

+            }

+

+            //next, the list

+            if (tempFaces.size() != 0) {

+                int faceGroupLoc = numGroups - 1; //the face group is the last

+                // one

+                primGroupArray[faceGroupLoc].type = PrimitiveGroup.PT_LIST;

+                primGroupArray[faceGroupLoc].indices = new int[tempFaces.size() * 3];

+                primGroupArray[faceGroupLoc].numIndices = tempFaces.size() * 3;

+                int indexCtr = 0;

+                for (int i = 0; i < tempFaces.size(); i++) {

+                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v0;

+                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v1;

+                    primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v2;

+                }

+            }

+        }

+        return primGroups;

+    }

+

+    /**

+	 * Function to remap your indices to improve spatial locality in your

+	 * vertex buffer.

+	 * 

+	 * in_primGroups: array of PrimitiveGroups you want remapped numGroups:

+	 * number of entries in in_primGroups numVerts: number of vertices in your

+	 * vertex buffer, also can be thought of as the range of acceptable values

+	 * for indices in your primitive groups. remappedGroups: array of remapped

+	 * PrimitiveGroups

+	 * 

+	 * Note that, according to the remapping handed back to you, you must

+	 * reorder your vertex buffer.

+	 *  

+	 */

+

+    public static int[] remapIndices(int[] indices, int numVerts) {

+        int[] indexCache = new int[numVerts];

+        Arrays.fill(indexCache, -1);

+

+        int numIndices = indices.length;

+        int[] remappedIndices = new int[numIndices];

+        int indexCtr = 0;

+        for (int j = 0; j < numIndices; j++) {

+            int cachedIndex = indexCache[indices[j]];

+            if (cachedIndex == -1) //we haven't seen this index before

+                {

+                //point to "last" vertex in VB

+                remappedIndices[j] = indexCtr;

+

+                //add to index cache, increment

+                indexCache[indices[j]] = indexCtr++;

+            } else {

+                //we've seen this index before

+                remappedIndices[j] = cachedIndex;

+            }

+        }

+

+        return remappedIndices;

+    }

+

+    public static void remapArrays(float[] vertexBuffer, int vertexSize, int[] indices) {

+        int[] remapped = remapIndices(indices, vertexBuffer.length / vertexSize);

+        float[] bufferCopy = vertexBuffer.clone();

+        for (int i = 0; i < remapped.length; i++) {

+            int from = indices[i] * vertexSize;

+            int to = remapped[i] * vertexSize;

+            for (int j = 0; j < vertexSize; j++) {

+                vertexBuffer[to + j] = bufferCopy[from + j];

+            }

+        }

+

+        System.arraycopy(remapped, 0, indices, 0, indices.length);

+    }

+

+}

diff --git a/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java b/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java
new file mode 100644
index 0000000..b60e9e5
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java
@@ -0,0 +1,100 @@
+/*

+ * Copyright (c) 2009-2010 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 jme3tools.converters.model.strip;

+

+import java.util.Arrays;

+

+

+class VertexCache {

+

+    int[] entries;

+    int numEntries;

+    

+    public VertexCache() {

+        this(16);

+    }

+    

+    public VertexCache(int size) {

+        numEntries = size;

+        entries = new int[numEntries];

+        clear();

+    }

+    

+    public boolean inCache(int entry) {

+        for(int i = 0; i < numEntries; i++)

+        {

+            if(entries[i] == entry)

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+    

+    public int addEntry(int entry) {

+        int removed;

+        

+        removed = entries[numEntries - 1];

+        

+        //push everything right one

+        for(int i = numEntries - 2; i >= 0; i--)

+           {

+            entries[i + 1] = entries[i];

+        }

+        

+        entries[0] = entry;

+        

+        return removed;

+    }

+

+    public void clear() {

+        Arrays.fill(entries,-1);

+    }

+    

+    public int at(int index) {

+        return entries[index];

+    }

+    

+    public void set(int index, int value) {

+        entries[index] = value;

+    }

+        

+    public void copy(VertexCache inVcache)

+    {

+        for(int i = 0; i < numEntries; i++)

+           {

+            inVcache.set(i, entries[i]);

+        }

+    }

+

+}

diff --git a/engine/src/tools/jme3tools/optimize/FastOctnode.java b/engine/src/tools/jme3tools/optimize/FastOctnode.java
new file mode 100644
index 0000000..d9e37da
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/FastOctnode.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.optimize;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import java.util.Set;
+
+public class FastOctnode {
+
+    int offset;
+    int length;
+    FastOctnode child;
+    FastOctnode next;
+
+    private static final BoundingBox tempBox = new BoundingBox();
+
+    public int getSide(){
+        return ((offset & 0xE0000000) >> 29) & 0x7;
+    }
+
+    public void setSide(int side){
+        offset &= 0x1FFFFFFF;
+        offset |= (side << 29);
+    }
+
+    public void setOffset(int offset){
+        if (offset < 0 || offset > 20000000){
+            throw new IllegalArgumentException();
+        }
+
+        this.offset &= 0xE0000000;
+        this.offset |= offset;
+    }
+
+    public int getOffset(){
+        return this.offset & 0x1FFFFFFF;
+    }
+
+    private void generateRenderSetNoCheck(Geometry[] globalGeomList, Set<Geometry> renderSet, Camera cam){
+        if (length != 0){
+            int start = getOffset();
+            int end   = start + length;
+            for (int i = start; i < end; i++){
+                renderSet.add(globalGeomList[i]);
+            }
+        }
+
+        if (child == null)
+            return;
+
+        FastOctnode node = child;
+        while (node != null){
+            node.generateRenderSetNoCheck(globalGeomList, renderSet, cam);
+            node = node.next;
+        }
+    }
+
+    private static void findChildBound(BoundingBox bbox, int side){
+        float extent = bbox.getXExtent() * 0.5f;
+        bbox.getCenter().set(bbox.getCenter().x + extent * Octnode.extentMult[side].x,
+                             bbox.getCenter().y + extent * Octnode.extentMult[side].y,
+                             bbox.getCenter().z + extent * Octnode.extentMult[side].z);
+        bbox.setXExtent(extent);
+        bbox.setYExtent(extent);
+        bbox.setZExtent(extent);
+    }
+
+    public void generateRenderSet(Geometry[] globalGeomList, Set<Geometry> renderSet, Camera cam, BoundingBox parentBox, boolean isRoot){
+        tempBox.setCenter(parentBox.getCenter());
+        tempBox.setXExtent(parentBox.getXExtent());
+        tempBox.setYExtent(parentBox.getYExtent());
+        tempBox.setZExtent(parentBox.getZExtent());
+
+        if (!isRoot){
+            findChildBound(tempBox, getSide());
+        }
+        
+        tempBox.setCheckPlane(0);
+        cam.setPlaneState(0);
+        Camera.FrustumIntersect result = cam.contains(tempBox);
+        if (result != Camera.FrustumIntersect.Outside){
+            if (length != 0){
+                int start = getOffset();
+                int end   = start + length;
+                for (int i = start; i < end; i++){
+                    renderSet.add(globalGeomList[i]);
+                }
+            }
+
+            if (child == null)
+                return;
+
+            FastOctnode node = child;
+
+            float x = tempBox.getCenter().x;
+            float y = tempBox.getCenter().y;
+            float z = tempBox.getCenter().z;
+            float ext = tempBox.getXExtent();
+
+            while (node != null){
+                if (result == Camera.FrustumIntersect.Inside){
+                    node.generateRenderSetNoCheck(globalGeomList, renderSet, cam);
+                }else{
+                    node.generateRenderSet(globalGeomList, renderSet, cam, tempBox, false);
+                }
+
+                tempBox.getCenter().set(x,y,z);
+                tempBox.setXExtent(ext);
+                tempBox.setYExtent(ext);
+                tempBox.setZExtent(ext);
+
+                node = node.next;
+            }
+        }
+    }
+
+    @Override
+    public String toString(){
+        return "OCTNode[O=" + getOffset() + ", L=" + length +
+                ", S=" + getSide() + "]";
+    }
+
+    public String toStringVerbose(int indent){
+        String str = "------------------".substring(0,indent) + toString() + "\n";
+        if (child == null)
+            return str;
+
+        FastOctnode children = child;
+        while (children != null){
+            str += children.toStringVerbose(indent+1);
+            children = children.next;
+        }
+
+        return str;
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java
new file mode 100644
index 0000000..ae6ad8c
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java
@@ -0,0 +1,418 @@
+package jme3tools.optimize;
+
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap.Entry;
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.*;
+import java.util.logging.Logger;
+
+public class GeometryBatchFactory {
+
+    private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName());
+
+    private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
+        Vector3f pos = new Vector3f();
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 3;
+
+        for (int i = 0; i < inBuf.capacity() / 3; i++) {
+            pos.x = inBuf.get(i * 3 + 0);
+            pos.y = inBuf.get(i * 3 + 1);
+            pos.z = inBuf.get(i * 3 + 2);
+
+            transform.mult(pos, pos);
+
+            outBuf.put(offset + i * 3 + 0, pos.x);
+            outBuf.put(offset + i * 3 + 1, pos.y);
+            outBuf.put(offset + i * 3 + 2, pos.z);
+        }
+    }
+
+    private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
+        Vector3f norm = new Vector3f();
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 3;
+
+        for (int i = 0; i < inBuf.capacity() / 3; i++) {
+            norm.x = inBuf.get(i * 3 + 0);
+            norm.y = inBuf.get(i * 3 + 1);
+            norm.z = inBuf.get(i * 3 + 2);
+
+            transform.multNormal(norm, norm);
+
+            outBuf.put(offset + i * 3 + 0, norm.x);
+            outBuf.put(offset + i * 3 + 1, norm.y);
+            outBuf.put(offset + i * 3 + 2, norm.z);
+        }
+    }
+    
+    private static void doTransformTangents(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) {
+        Vector3f tan = new Vector3f();
+        float handedness = 0;
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 4;
+
+        for (int i = 0; i < inBuf.capacity() / 4; i++) {
+            tan.x = inBuf.get(i * 4 + 0);
+            tan.y = inBuf.get(i * 4 + 1);
+            tan.z = inBuf.get(i * 4 + 2);
+            handedness = inBuf.get(i * 4 + 3);
+
+            transform.multNormal(tan, tan);
+
+            outBuf.put(offset + i * 4 + 0, tan.x);
+            outBuf.put(offset + i * 4 + 1, tan.y);
+            outBuf.put(offset + i * 4 + 2, tan.z);
+            outBuf.put(offset + i * 4 + 3, handedness);
+             
+        }
+    }
+
+    /**
+     * Merges all geometries in the collection into
+     * the output mesh. Creates a new material using the TextureAtlas.
+     * 
+     * @param geometries
+     * @param outMesh
+     */
+    public static void mergeGeometries(Collection<Geometry> geometries, Mesh outMesh) {
+        int[] compsForBuf = new int[VertexBuffer.Type.values().length];
+        Format[] formatForBuf = new Format[compsForBuf.length];
+
+        int totalVerts = 0;
+        int totalTris = 0;
+        int totalLodLevels = 0;
+
+        Mode mode = null;
+        for (Geometry geom : geometries) {
+            totalVerts += geom.getVertexCount();
+            totalTris += geom.getTriangleCount();
+            totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
+
+            Mode listMode;
+            int components;
+            switch (geom.getMesh().getMode()) {
+                case Points:
+                    listMode = Mode.Points;
+                    components = 1;
+                    break;
+                case LineLoop:
+                case LineStrip:
+                case Lines:
+                    listMode = Mode.Lines;
+                    components = 2;
+                    break;
+                case TriangleFan:
+                case TriangleStrip:
+                case Triangles:
+                    listMode = Mode.Triangles;
+                    components = 3;
+                    break;
+                default:
+                    throw new UnsupportedOperationException();
+            }
+
+            for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
+                compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
+                formatForBuf[entry.getKey()] = entry.getValue().getFormat();
+            }
+
+            if (mode != null && mode != listMode) {
+                throw new UnsupportedOperationException("Cannot combine different"
+                        + " primitive types: " + mode + " != " + listMode);
+            }
+            mode = listMode;
+            compsForBuf[Type.Index.ordinal()] = components;
+        }
+
+        outMesh.setMode(mode);
+        if (totalVerts >= 65536) {
+            // make sure we create an UnsignedInt buffer so
+            // we can fit all of the meshes
+            formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt;
+        } else {
+            formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort;
+        }
+
+        // generate output buffers based on retrieved info
+        for (int i = 0; i < compsForBuf.length; i++) {
+            if (compsForBuf[i] == 0) {
+                continue;
+            }
+
+            Buffer data;
+            if (i == Type.Index.ordinal()) {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
+            } else {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
+            }
+
+            VertexBuffer vb = new VertexBuffer(Type.values()[i]);
+            vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data);
+            outMesh.setBuffer(vb);
+        }
+
+        int globalVertIndex = 0;
+        int globalTriIndex = 0;
+
+        for (Geometry geom : geometries) {
+            Mesh inMesh = geom.getMesh();
+            geom.computeWorldMatrix();
+            Matrix4f worldMatrix = geom.getWorldMatrix();
+
+            int geomVertCount = inMesh.getVertexCount();
+            int geomTriCount = inMesh.getTriangleCount();
+
+            for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
+                VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]);
+                VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]);
+
+                if (inBuf == null || outBuf == null) {
+                    continue;
+                }
+
+                if (Type.Index.ordinal() == bufType) {
+                    int components = compsForBuf[bufType];
+
+                    IndexBuffer inIdx = inMesh.getIndicesAsList();
+                    IndexBuffer outIdx = outMesh.getIndexBuffer();
+
+                    for (int tri = 0; tri < geomTriCount; tri++) {
+                        for (int comp = 0; comp < components; comp++) {
+                            int idx = inIdx.get(tri * components + comp) + globalVertIndex;
+                            outIdx.put((globalTriIndex + tri) * components + comp, idx);
+                        }
+                    }
+                } else if (Type.Position.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                    doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix);
+                } else if (Type.Normal.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                    doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix);
+                }else if(Type.Tangent.ordinal() == bufType){                    
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                    doTransformTangents(inPos, globalVertIndex, outPos, worldMatrix);
+                } else {
+                    inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
+                }
+            }
+
+            globalVertIndex += geomVertCount;
+            globalTriIndex += geomTriCount;
+        }
+    }
+
+    public static void makeLods(Collection<Geometry> geometries, Mesh outMesh) {
+        int lodLevels = 0;
+        int[] lodSize = null;
+        int index = 0;
+        for (Geometry g : geometries) {
+            if (lodLevels == 0) {
+                lodLevels = g.getMesh().getNumLodLevels();
+            }
+            if (lodSize == null) {
+                lodSize = new int[lodLevels];
+            }
+            for (int i = 0; i < lodLevels; i++) {
+                lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity();
+                //if( i == 0) System.out.println(index + " " +lodSize[i]);
+            }
+            index++;
+        }
+        int[][] lodData = new int[lodLevels][];
+        for (int i = 0; i < lodLevels; i++) {
+            lodData[i] = new int[lodSize[i]];
+        }
+        VertexBuffer[] lods = new VertexBuffer[lodLevels];
+        int bufferPos[] = new int[lodLevels];
+        //int index = 0;
+        int numOfVertices = 0;
+        int curGeom = 0;
+        for (Geometry g : geometries) {
+            if (numOfVertices == 0) {
+                numOfVertices = g.getVertexCount();
+            }
+            for (int i = 0; i < lodLevels; i++) {
+                ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getDataReadOnly();
+                //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index);
+                for (int j = 0; j < buffer.capacity(); j++) {
+                    lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom;
+                    //bufferPos[i]++;
+                }
+                bufferPos[i] += buffer.capacity();
+            }
+            curGeom++;
+        }
+        for (int i = 0; i < lodLevels; i++) {
+            lods[i] = new VertexBuffer(Type.Index);
+            lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i]));
+        }
+        System.out.println(lods.length);
+        outMesh.setLodLevels(lods);
+    }
+
+    public static List<Geometry> makeBatches(Collection<Geometry> geometries) {
+        return makeBatches(geometries, false);
+    }
+
+    /**
+     * Batches a collection of Geometries so that all with the same material get combined.
+     * @param geometries The Geometries to combine
+     * @return A List of newly created Geometries, each with a  distinct material
+     */
+    public static List<Geometry> makeBatches(Collection<Geometry> geometries, boolean useLods) {
+        ArrayList<Geometry> retVal = new ArrayList<Geometry>();
+        HashMap<Material, List<Geometry>> matToGeom = new HashMap<Material, List<Geometry>>();
+
+        for (Geometry geom : geometries) {
+            List<Geometry> outList = matToGeom.get(geom.getMaterial());
+            if (outList == null) {
+                outList = new ArrayList<Geometry>();
+                matToGeom.put(geom.getMaterial(), outList);
+            }
+            outList.add(geom);
+        }
+
+        int batchNum = 0;
+        for (Map.Entry<Material, List<Geometry>> entry : matToGeom.entrySet()) {
+            Material mat = entry.getKey();
+            List<Geometry> geomsForMat = entry.getValue();
+            Mesh mesh = new Mesh();
+            mergeGeometries(geomsForMat, mesh);
+            // lods
+            if (useLods) {
+                makeLods(geomsForMat, mesh);
+            }
+            mesh.updateCounts();
+            mesh.updateBound();
+
+            Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh);
+            out.setMaterial(mat);
+            retVal.add(out);
+        }
+
+        return retVal;
+    }
+
+    public static void gatherGeoms(Spatial scene, List<Geometry> geoms) {
+        if (scene instanceof Node) {
+            Node node = (Node) scene;
+            for (Spatial child : node.getChildren()) {
+                gatherGeoms(child, geoms);
+            }
+        } else if (scene instanceof Geometry) {
+            geoms.add((Geometry) scene);
+        }
+    }
+
+    /**
+     * Optimizes a scene by combining Geometry with the same material.
+     * All Geometries found in the scene are detached from their parent and
+     * a new Node containing the optimized Geometries is attached.
+     * @param scene The scene to optimize
+     * @return The newly created optimized geometries attached to a node
+     */
+    public static Spatial optimize(Node scene) {
+        return optimize(scene, false);
+    }
+
+    /**
+     * Optimizes a scene by combining Geometry with the same material.
+     * All Geometries found in the scene are detached from their parent and
+     * a new Node containing the optimized Geometries is attached.
+     * @param scene The scene to optimize
+     * @param useLods true if you want the resulting geometry to keep lod information
+     * @return The newly created optimized geometries attached to a node
+     */
+    public static Node optimize(Node scene, boolean useLods) {
+        ArrayList<Geometry> geoms = new ArrayList<Geometry>();
+
+        gatherGeoms(scene, geoms);
+
+        List<Geometry> batchedGeoms = makeBatches(geoms, useLods);
+        for (Geometry geom : batchedGeoms) {
+            scene.attachChild(geom);
+        }
+
+        for (Iterator<Geometry> it = geoms.iterator(); it.hasNext();) {
+            Geometry geometry = it.next();
+            geometry.removeFromParent();
+        }
+
+        // Since the scene is returned unaltered the transform must be reset
+        scene.setLocalTransform(Transform.IDENTITY);
+
+        return scene;
+    }
+
+    public static void printMesh(Mesh mesh) {
+        for (int bufType = 0; bufType < Type.values().length; bufType++) {
+            VertexBuffer outBuf = mesh.getBuffer(Type.values()[bufType]);
+            if (outBuf == null) {
+                continue;
+            }
+
+            System.out.println(outBuf.getBufferType() + ": ");
+            for (int vert = 0; vert < outBuf.getNumElements(); vert++) {
+                String str = "[";
+                for (int comp = 0; comp < outBuf.getNumComponents(); comp++) {
+                    Object val = outBuf.getElementComponent(vert, comp);
+                    outBuf.setElementComponent(vert, comp, val);
+                    val = outBuf.getElementComponent(vert, comp);
+                    str += val;
+                    if (comp != outBuf.getNumComponents() - 1) {
+                        str += ", ";
+                    }
+                }
+                str += "]";
+                System.out.println(str);
+            }
+            System.out.println("------");
+        }
+    }
+
+    public static void main(String[] args) {
+        Mesh mesh = new Mesh();
+        mesh.setBuffer(Type.Position, 3, new float[]{
+                    0, 0, 0,
+                    1, 0, 0,
+                    1, 1, 0,
+                    0, 1, 0
+                });
+        mesh.setBuffer(Type.Index, 2, new short[]{
+                    0, 1,
+                    1, 2,
+                    2, 3,
+                    3, 0
+                });
+
+        Geometry g1 = new Geometry("g1", mesh);
+
+        ArrayList<Geometry> geoms = new ArrayList<Geometry>();
+        geoms.add(g1);
+
+        Mesh outMesh = new Mesh();
+        mergeGeometries(geoms, outMesh);
+        printMesh(outMesh);
+    }
+}
diff --git a/engine/src/tools/jme3tools/optimize/OCTTriangle.java b/engine/src/tools/jme3tools/optimize/OCTTriangle.java
new file mode 100644
index 0000000..b31a8a3
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/OCTTriangle.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.optimize;
+
+import com.jme3.math.Vector3f;
+
+public final class OCTTriangle {
+
+    private final Vector3f pointa = new Vector3f();
+    private final Vector3f pointb = new Vector3f();
+    private final Vector3f pointc = new Vector3f();
+    private final int index;
+    private final int geomIndex;
+
+    public OCTTriangle(Vector3f p1, Vector3f p2, Vector3f p3, int index, int geomIndex) {
+        pointa.set(p1);
+        pointb.set(p2);
+        pointc.set(p3);
+        this.index = index;
+        this.geomIndex = geomIndex;
+    }
+
+    public int getGeometryIndex() {
+        return geomIndex;
+    }
+
+    public int getTriangleIndex() {
+        return index;
+    }
+    
+    public Vector3f get1(){
+        return pointa;
+    }
+
+    public Vector3f get2(){
+        return pointb;
+    }
+
+    public Vector3f get3(){
+        return pointc;
+    }
+
+    public Vector3f getNormal(){
+        Vector3f normal = new Vector3f(pointb);
+        normal.subtractLocal(pointa).crossLocal(pointc.x-pointa.x, pointc.y-pointa.y, pointc.z-pointa.z);
+        normal.normalizeLocal();
+        return normal;
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/optimize/Octnode.java b/engine/src/tools/jme3tools/optimize/Octnode.java
new file mode 100644
index 0000000..e9a0080
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/Octnode.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.optimize;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.debug.WireBox;
+import java.util.*;
+
+public class Octnode {
+
+    static final Vector3f[] extentMult = new Vector3f[]
+    {
+        new Vector3f( 1, 1, 1), // right top forw
+        new Vector3f(-1, 1, 1), // left top forw
+        new Vector3f( 1,-1, 1), // right bot forw
+        new Vector3f(-1,-1, 1), // left bot forw
+        new Vector3f( 1, 1,-1), // right top back
+        new Vector3f(-1, 1,-1), // left top back
+        new Vector3f( 1,-1,-1), // right bot back
+        new Vector3f(-1,-1,-1)  // left bot back
+    };
+
+    final BoundingBox bbox;
+    final ArrayList<OCTTriangle> tris;
+    Geometry[] geoms;
+    final Octnode[] children = new Octnode[8];
+    boolean leaf = false;
+    FastOctnode fastNode;
+
+    public Octnode(BoundingBox bbox, ArrayList<OCTTriangle> tris){
+        this.bbox = bbox;
+        this.tris = tris;
+    }
+    
+    private BoundingBox getChildBound(int side){
+        float extent = bbox.getXExtent() * 0.5f;
+        Vector3f center = new Vector3f(bbox.getCenter().x + extent * extentMult[side].x,
+                                       bbox.getCenter().y + extent * extentMult[side].y,
+                                       bbox.getCenter().z + extent * extentMult[side].z);
+        return new BoundingBox(center, extent, extent, extent);
+    }
+
+    private float getAdditionCost(BoundingBox bbox, OCTTriangle t){
+        if (bbox.intersects(t.get1(), t.get2(), t.get3())){
+            float d1 = bbox.distanceToEdge(t.get1());
+            float d2 = bbox.distanceToEdge(t.get2());
+            float d3 = bbox.distanceToEdge(t.get3());
+            return d1 + d2 + d3;
+        }
+        return Float.POSITIVE_INFINITY;
+    }
+
+    private void expandBoxToContainTri(BoundingBox bbox, OCTTriangle t){
+        Vector3f min = bbox.getMin(null);
+        Vector3f max = bbox.getMax(null);
+        BoundingBox.checkMinMax(min, max, t.get1());
+        BoundingBox.checkMinMax(min, max, t.get2());
+        BoundingBox.checkMinMax(min, max, t.get3());
+        bbox.setMinMax(min, max);
+    }
+
+    private boolean contains(BoundingBox bbox, OCTTriangle t){
+        if (bbox.contains(t.get1()) &&
+            bbox.contains(t.get2()) &&
+            bbox.contains(t.get3())){
+            return true;
+        }
+        return false;
+    }
+
+    public void subdivide(int depth, int minTrisPerNode){
+        if (tris == null || depth > 50 || bbox.getVolume() < 0.01f || tris.size() < minTrisPerNode){
+            // no need to subdivide anymore
+            leaf = true;
+            return;
+        }
+
+        ArrayList<OCTTriangle> keepTris = new ArrayList<OCTTriangle>();
+        ArrayList[] trisForChild = new ArrayList[8];
+        BoundingBox[] boxForChild = new BoundingBox[8];
+        // create boxes for children
+        for (int i = 0; i < 8; i++){
+            boxForChild[i] = getChildBound(i);
+            trisForChild[i] = new ArrayList<Triangle>();
+        }
+
+        for (OCTTriangle t : tris){
+            float lowestCost = Float.POSITIVE_INFINITY;
+            int lowestIndex = -1;
+            int numIntersecting = 0;
+            for (int i = 0; i < 8; i++){
+                BoundingBox childBox = boxForChild[i];
+                float cost = getAdditionCost(childBox, t);
+                if (cost < lowestCost){
+                    lowestCost = cost;
+                    lowestIndex = i;
+                    numIntersecting++;
+                }
+            }
+            if (numIntersecting < 8 && lowestIndex > -1){
+                trisForChild[lowestIndex].add(t);
+                expandBoxToContainTri(boxForChild[lowestIndex], t);
+            }else{
+                keepTris.add(t);
+            }
+//            boolean wasAdded = false;
+//            for (int i = 0; i < 8; i++){
+//                BoundingBox childBox = boxForChild[i];
+//                if (contains(childBox, t)){
+//                    trisForChild[i].add(t);
+//                    wasAdded = true;
+//                    break;
+//                }
+//            }
+//            if (!wasAdded){
+//                keepTris.add(t);
+//            }
+        }
+        tris.retainAll(keepTris);
+        for (int i = 0; i < 8; i++){
+            if (trisForChild[i].size() > 0){
+                children[i] = new Octnode(boxForChild[i], trisForChild[i]);
+                children[i].subdivide(depth + 1, minTrisPerNode);
+            }
+        }
+    }
+
+    public void subdivide(int minTrisPerNode){
+        subdivide(0, minTrisPerNode);
+    }
+
+    public void createFastOctnode(List<Geometry> globalGeomList){
+        fastNode = new FastOctnode();
+
+        if (geoms != null){
+            Collection<Geometry> geomsColl = Arrays.asList(geoms);
+            List<Geometry> myOptimizedList = GeometryBatchFactory.makeBatches(geomsColl);
+
+            int startIndex = globalGeomList.size();
+            globalGeomList.addAll(myOptimizedList);
+
+            fastNode.setOffset(startIndex);
+            fastNode.length = myOptimizedList.size();
+        }else{
+            fastNode.setOffset(0);
+            fastNode.length = 0;
+        }
+
+        for (int i = 0; i < 8; i++){
+            if (children[i] != null){
+                children[i].createFastOctnode(globalGeomList);
+            }
+        }
+    }
+
+    public void generateFastOctnodeLinks(Octnode parent, Octnode nextSibling, int side){
+        fastNode.setSide(side);
+        fastNode.next = nextSibling != null ? nextSibling.fastNode : null;
+
+        // We set the next sibling property by going in reverse order
+        Octnode prev = null;
+        for (int i = 7; i >= 0; i--){
+            if (children[i] != null){
+                children[i].generateFastOctnodeLinks(this, prev, i);
+                prev = children[i];
+            }
+        }
+        fastNode.child = prev != null ? prev.fastNode : null;
+    }
+
+    private void generateRenderSetNoCheck(Set<Geometry> renderSet, Camera cam){
+        if (geoms != null){
+            renderSet.addAll(Arrays.asList(geoms));
+        }
+        for (int i = 0; i < 8; i++){
+            if (children[i] != null){
+                children[i].generateRenderSetNoCheck(renderSet, cam);
+            }
+        }
+    }
+
+    public void generateRenderSet(Set<Geometry> renderSet, Camera cam){
+//        generateRenderSetNoCheck(renderSet, cam);
+
+        bbox.setCheckPlane(0);
+        cam.setPlaneState(0);
+        Camera.FrustumIntersect result = cam.contains(bbox);
+        if (result != Camera.FrustumIntersect.Outside){
+            if (geoms != null){
+                renderSet.addAll(Arrays.asList(geoms));
+            }
+            for (int i = 0; i < 8; i++){
+                if (children[i] != null){
+                    if (result == Camera.FrustumIntersect.Inside){
+                        children[i].generateRenderSetNoCheck(renderSet, cam);
+                    }else{
+                        children[i].generateRenderSet(renderSet, cam);
+                    }
+                }
+            }
+        }
+    }
+
+    public void collectTriangles(Geometry[] inGeoms){
+        if (tris.size() > 0){
+            List<Geometry> geomsList = TriangleCollector.gatherTris(inGeoms, tris);
+            geoms = new Geometry[geomsList.size()];
+            geomsList.toArray(geoms);
+        }else{
+            geoms = null;
+        }
+        for (int i = 0; i < 8; i++){
+            if (children[i] != null){
+                children[i].collectTriangles(inGeoms);
+            }
+        }
+    }
+
+    public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){
+        int numChilds = 0;
+        for (int i = 0; i < 8; i++){
+            if (children[i] != null){
+                numChilds ++;
+                break;
+            }
+        }
+        if (geoms != null && numChilds == 0){
+            BoundingBox bbox2 = new BoundingBox(bbox);
+            bbox.transform(transform, bbox2);
+//            WireBox box = new WireBox(bbox2.getXExtent(), bbox2.getYExtent(),
+//                                      bbox2.getZExtent());
+//            WireBox box = new WireBox(1,1,1);
+
+            Geometry geom = new Geometry("bound", box);
+            geom.setLocalTranslation(bbox2.getCenter());
+            geom.setLocalScale(bbox2.getXExtent(), bbox2.getYExtent(),
+                               bbox2.getZExtent());
+            geom.updateGeometricState();
+            geom.setMaterial(mat);
+            rq.addToQueue(geom, Bucket.Opaque);
+            box = null;
+            geom = null;
+        }
+        for (int i = 0; i < 8; i++){
+            if (children[i] != null){
+                children[i].renderBounds(rq, transform, box, mat);
+            }
+        }
+    }
+
+    public final void intersectWhere(Ray r, Geometry[] geoms, float sceneMin, float sceneMax,
+                                            CollisionResults results){
+        for (OCTTriangle t : tris){
+            float d = r.intersects(t.get1(), t.get2(), t.get3());
+            if (Float.isInfinite(d))
+                continue;
+
+            Vector3f contactPoint = new Vector3f(r.getDirection()).multLocal(d).addLocal(r.getOrigin());
+            CollisionResult result = new CollisionResult(geoms[t.getGeometryIndex()],
+                                                         contactPoint,
+                                                         d,
+                                                         t.getTriangleIndex());
+            results.addCollision(result);
+        }
+        for (int i = 0; i < 8; i++){
+            Octnode child = children[i];
+            if (child == null)
+                continue;
+
+            if (child.bbox.intersects(r)){
+                child.intersectWhere(r, geoms, sceneMin, sceneMax, results);
+            }
+        }
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/optimize/Octree.java b/engine/src/tools/jme3tools/optimize/Octree.java
new file mode 100644
index 0000000..880f984
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/Octree.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.optimize;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.CollisionResults;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Triangle;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireBox;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class Octree {
+
+    private final ArrayList<OCTTriangle> allTris = new ArrayList<OCTTriangle>();
+    private final Geometry[] geoms;
+    private final BoundingBox bbox;
+    private final int minTrisPerNode;
+    private Octnode root;
+
+    private CollisionResults boundResults = new CollisionResults();
+
+    private static List<Geometry> getGeometries(Spatial scene){
+        if (scene instanceof Geometry){
+            List<Geometry> geomList = new ArrayList<Geometry>(1);
+            geomList.add((Geometry) scene);
+            return geomList;
+        }else if (scene instanceof Node){
+            Node n = (Node) scene;
+            List<Geometry> geoms = new ArrayList<Geometry>();
+            for (Spatial child : n.getChildren()){
+                geoms.addAll(getGeometries(child));
+            }
+            return geoms;
+        }else{
+            throw new UnsupportedOperationException("Unsupported scene element class");
+        }
+    }
+
+    public Octree(Spatial scene, int minTrisPerNode){
+        scene.updateGeometricState();
+
+        List<Geometry> geomsList = getGeometries(scene);
+        geoms = new Geometry[geomsList.size()];
+        geomsList.toArray(geoms);
+        // generate bound box for all geom
+        bbox = new BoundingBox();
+        for (Geometry geom : geoms){
+            BoundingVolume bv = geom.getWorldBound();
+            bbox.mergeLocal(bv);
+        }
+
+        // set largest extent
+        float extent = Math.max(bbox.getXExtent(), Math.max(bbox.getYExtent(), bbox.getZExtent()));
+        bbox.setXExtent(extent);
+        bbox.setYExtent(extent);
+        bbox.setZExtent(extent);
+
+        this.minTrisPerNode = minTrisPerNode;
+
+        Triangle t = new Triangle();
+        for (int g = 0; g < geoms.length; g++){
+            Mesh m = geoms[g].getMesh();
+            for (int i = 0; i < m.getTriangleCount(); i++){
+                m.getTriangle(i, t);
+                OCTTriangle ot = new OCTTriangle(t.get1(), t.get2(), t.get3(), i, g);
+                allTris.add(ot);
+                // convert triangle to world space
+//                geom.getWorldTransform().transformVector(t.get1(), t.get1());
+//                geom.getWorldTransform().transformVector(t.get2(), t.get2());
+//                geom.getWorldTransform().transformVector(t.get3(), t.get3());
+            }
+        }
+    }
+
+    public Octree(Spatial scene){
+        this(scene,11);
+    }
+
+    public void construct(){
+        root = new Octnode(bbox, allTris);
+        root.subdivide(minTrisPerNode);
+        root.collectTriangles(geoms);
+    }
+
+    public void createFastOctnodes(List<Geometry> globalGeomList){
+        root.createFastOctnode(globalGeomList);
+    }
+
+    public BoundingBox getBound(){
+        return bbox;
+    }
+
+    public FastOctnode getFastRoot(){
+        return root.fastNode;
+    }
+
+    public void generateFastOctnodeLinks(){
+        root.generateFastOctnodeLinks(null, null, 0);
+    }
+    
+    public void generateRenderSet(Set<Geometry> renderSet, Camera cam){
+        root.generateRenderSet(renderSet, cam);
+    }
+
+    public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){
+        root.renderBounds(rq, transform, box, mat);
+    }
+
+    public void intersect(Ray r, float farPlane, Geometry[] geoms, CollisionResults results){
+        boundResults.clear();
+        bbox.collideWith(r, boundResults);
+        if (boundResults.size() > 0){
+            float tMin = boundResults.getClosestCollision().getDistance();
+            float tMax = boundResults.getFarthestCollision().getDistance();
+
+            tMin = Math.max(tMin, 0);
+            tMax = Math.min(tMax, farPlane);
+
+            root.intersectWhere(r, geoms, tMin, tMax, results);
+        }
+    }
+}
diff --git a/engine/src/tools/jme3tools/optimize/TestCollector.java b/engine/src/tools/jme3tools/optimize/TestCollector.java
new file mode 100644
index 0000000..cc451aa
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/TestCollector.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 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 jme3tools.optimize;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestCollector {
+
+    public static void main(String[] args){
+        Vector3f z = Vector3f.ZERO;
+        Geometry g  = new Geometry("quad", new Quad(2,2));
+        Geometry g2 = new Geometry("quad", new Quad(2,2));
+        List<OCTTriangle> tris = new ArrayList<OCTTriangle>();
+        tris.add(new OCTTriangle(z, z, z, 1, 0));
+        tris.add(new OCTTriangle(z, z, z, 0, 1));
+        List<Geometry> firstOne = TriangleCollector.gatherTris(new Geometry[]{ g, g2 }, tris);
+        System.out.println(firstOne.get(0).getMesh());
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/optimize/TextureAtlas.java b/engine/src/tools/jme3tools/optimize/TextureAtlas.java
new file mode 100644
index 0000000..13c8b69
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/TextureAtlas.java
@@ -0,0 +1,686 @@
+/*
+ *  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 jme3tools.optimize;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.MatParamTexture;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <b><code>TextureAtlas</code></b> allows combining multiple textures to one texture atlas.
+ * 
+ * <p>After the TextureAtlas has been created with a certain size, textures can be added for
+ * freely chosen "map names". The textures are automatically placed on the atlas map and the
+ * image data is stored in a byte array for each map name. Later each map can be retrieved as
+ * a Texture to be used further in materials.</p>
+ * 
+ * <p>The first map name used is the "master map" that defines new locations on the atlas. Secondary
+ * textures (other map names) have to reference a texture of the master map to position the texture
+ * on the secondary map. This is necessary as the maps share texture coordinates and thus need to be
+ * placed at the same location on both maps.</p>
+ * 
+ * <p>The helper methods that work with <code>Geometry</code> objects handle the <em>DiffuseMap</em> or <em>ColorMap</em> as the master map and
+ * additionally handle <em>NormalMap</em> and <em>SpecularMap</em> as secondary maps.</p>
+ * 
+ * <p>The textures are referenced by their <b>asset key name</b> and for each texture the location
+ * inside the atlas is stored. A texture with an existing key name is never added more than once
+ * to the atlas. You can access the information for each texture or geometry texture via helper methods.</p>
+ * 
+ * <p>The TextureAtlas also allows you to change the texture coordinates of a mesh or geometry
+ * to point at the new locations of its texture inside the atlas (if the texture exists inside the atlas).</p>
+ * 
+ * <p>Note that models that use texture coordinates outside the 0-1 range (repeating/wrapping textures)
+ * will not work correctly as their new coordinates leak into other parts of the atlas and thus display
+ * other textures instead of repeating the texture.</p>
+ * 
+ * <p>Also note that textures are not scaled and the atlas needs to be large enough to hold all textures.
+ * All methods that allow adding textures return false if the texture could not be added due to the
+ * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size
+ * as the main (e.g. DiffuseMap) texture.</p>
+ * 
+ * <p><b>Usage examples</b></p>
+ * Create one geometry out of several geometries that are loaded from a j3o file:
+ * <pre>
+ * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
+ * Geometry geom = TextureAtlas.makeAtlasBatch(scene);
+ * rootNode.attachChild(geom);
+ * </pre>
+ * Create a texture atlas and change the texture coordinates of one geometry:
+ * <pre>
+ * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
+ * //either auto-create from node:
+ * TextureAtlas atlas = TextureAtlas.createAtlas(scene);
+ * //or create manually by adding textures or geometries with textures
+ * TextureAtlas atlas = new TextureAtlas(1024,1024);
+ * atlas.addTexture(myTexture, "DiffuseMap");
+ * atlas.addGeometry(myGeometry);
+ * //create material and set texture
+ * Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
+ * mat.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap"));
+ * //change one geometry to use atlas, apply texture coordinates and replace material.
+ * Geometry geom = scene.getChild("MyGeometry");
+ * atlas.applyCoords(geom);
+ * geom.setMaterial(mat);
+ * </pre>
+ * 
+ * @author normenhansen, Lukasz Bruun - lukasz.dk
+ */
+public class TextureAtlas {
+
+    private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName());
+    private Map<String, byte[]> images;
+    private int atlasWidth, atlasHeight;
+    private Format format = Format.ABGR8;
+    private Node root;
+    private Map<String, TextureAtlasTile> locationMap;
+    private Map<String, String> mapNameMap;
+    private String rootMapName;
+
+    public TextureAtlas(int width, int height) {
+        this.atlasWidth = width;
+        this.atlasHeight = height;
+        root = new Node(0, 0, width, height);
+        locationMap = new TreeMap<String, TextureAtlasTile>();
+        mapNameMap = new HashMap<String, String>();
+    }
+
+    /**
+     * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas.
+     * @param geometry
+     * @return false if the atlas is full.
+     */
+    public boolean addGeometry(Geometry geometry) {
+        Texture diffuse = getMaterialTexture(geometry, "DiffuseMap");
+        Texture normal = getMaterialTexture(geometry, "NormalMap");
+        Texture specular = getMaterialTexture(geometry, "SpecularMap");
+        if (diffuse == null) {
+            diffuse = getMaterialTexture(geometry, "ColorMap");
+
+        }
+        if (diffuse != null && diffuse.getKey() != null) {
+            String keyName = diffuse.getKey().toString();
+            if (!addTexture(diffuse, "DiffuseMap")) {
+                return false;
+            } else {
+                if (normal != null && normal.getKey() != null) {
+                    addTexture(diffuse, "NormalMap", keyName);
+                }
+                if (specular != null && specular.getKey() != null) {
+                    addTexture(specular, "SpecularMap", keyName);
+                }
+            }
+            return true;
+        }
+        return true;
+    }
+
+    /**
+     * Add a texture for a specific map name
+     * @param texture A texture to add to the atlas.
+     * @param mapName A freely chosen map name that can be later retrieved as a Texture. The first map name supplied will be the master map.
+     * @return false if the atlas is full.
+     */
+    public boolean addTexture(Texture texture, String mapName) {
+        if (texture == null) {
+            throw new IllegalStateException("Texture cannot be null!");
+        }
+        String name = textureName(texture);
+        if (texture.getImage() != null && name != null) {
+            return addImage(texture.getImage(), name, mapName, null);
+        } else {
+            throw new IllegalStateException("Texture has no asset key name!");
+        }
+    }
+
+    /**
+     * Add a texture for a specific map name at the location of another existing texture on the master map.
+     * @param texture A texture to add to the atlas.
+     * @param mapName A freely chosen map name that can be later retrieved as a Texture.
+     * @param masterTexture The master texture for determining the location, it has to exist in tha master map.
+     */
+    public void addTexture(Texture texture, String mapName, Texture masterTexture) {
+        String sourceTextureName = textureName(masterTexture);
+        if (sourceTextureName == null) {
+            throw new IllegalStateException("Supplied master map texture has no asset key name!");
+        } else {
+            addTexture(texture, mapName, sourceTextureName);
+        }
+    }
+
+    /**
+     * Add a texture for a specific map name at the location of another existing texture (on the master map).
+     * @param texture A texture to add to the atlas.
+     * @param mapName A freely chosen map name that can be later retrieved as a Texture.
+     * @param sourceTextureName Name of the master map used for the location.
+     */
+    public void addTexture(Texture texture, String mapName, String sourceTextureName) {
+        if (texture == null) {
+            throw new IllegalStateException("Texture cannot be null!");
+        }
+        String name = textureName(texture);
+        if (texture.getImage() != null && name != null) {
+            addImage(texture.getImage(), name, mapName, sourceTextureName);
+        } else {
+            throw new IllegalStateException("Texture has no asset key name!");
+        }
+    }
+
+    private String textureName(Texture texture) {
+        if (texture == null) {
+            return null;
+        }
+        AssetKey key = texture.getKey();
+        if (key != null) {
+            return key.toString();
+        } else {
+            return null;
+        }
+    }
+
+    private boolean addImage(Image image, String name, String mapName, String sourceTextureName) {
+        if (rootMapName == null) {
+            rootMapName = mapName;
+        }
+        if (sourceTextureName == null && !rootMapName.equals(mapName)) {
+            throw new IllegalStateException("Atlas already has a master map called " + rootMapName + "."
+                    + " Textures for new maps have to use a texture from the master map for their location.");
+        }
+        TextureAtlasTile location = locationMap.get(name);
+        if (location != null) {
+            //have location for texture
+            if (!mapName.equals(mapNameMap.get(name))) {
+                logger.log(Level.WARNING, "Same texture " + name + " is used in different maps! (" + mapName + " and " + mapNameMap.get(name) + "). Location will be based on location in " + mapNameMap.get(name) + "!");
+                drawImage(image, location.getX(), location.getY(), mapName);
+                return true;
+            } else {
+                return true;
+            }
+        } else if (sourceTextureName == null) {
+            //need to make new tile
+            Node node = root.insert(image);
+            if (node == null) {
+                return false;
+            }
+            location = node.location;
+        } else {
+            //got old tile to align to
+            location = locationMap.get(sourceTextureName);
+            if (location == null) {
+                throw new IllegalStateException("Cannot find master map texture for " + name + ".");
+            } else if (location.width != image.getWidth() || location.height != image.getHeight()) {
+                throw new IllegalStateException(mapName + " " + name + " does not fit " + rootMapName + " tile size. Make sure all textures (diffuse, normal, specular) for one model are the same size.");
+            }
+        }
+        mapNameMap.put(name, mapName);
+        locationMap.put(name, location);
+        drawImage(image, location.getX(), location.getY(), mapName);
+        return true;
+    }
+
+    private void drawImage(Image source, int x, int y, String mapName) {
+        if (images == null) {
+            images = new HashMap<String, byte[]>();
+        }
+        byte[] image = images.get(mapName);
+        if (image == null) {
+            image = new byte[atlasWidth * atlasHeight * 4];
+            images.put(mapName, image);
+        }
+        //TODO: all buffers?
+        ByteBuffer sourceData = source.getData(0);
+        int height = source.getHeight();
+        int width = source.getWidth();
+        Image newImage = null;
+        for (int yPos = 0; yPos < height; yPos++) {
+            for (int xPos = 0; xPos < width; xPos++) {
+                int i = ((xPos + x) + (yPos + y) * atlasWidth) * 4;
+                if (source.getFormat() == Format.ABGR8) {
+                    int j = (xPos + yPos * width) * 4;
+                    image[i] = sourceData.get(j); //a
+                    image[i + 1] = sourceData.get(j + 1); //b
+                    image[i + 2] = sourceData.get(j + 2); //g
+                    image[i + 3] = sourceData.get(j + 3); //r
+                } else if (source.getFormat() == Format.BGR8) {
+                    int j = (xPos + yPos * width) * 3;
+                    image[i] = 1; //a
+                    image[i + 1] = sourceData.get(j); //b
+                    image[i + 2] = sourceData.get(j + 1); //g
+                    image[i + 3] = sourceData.get(j + 2); //r
+                } else if (source.getFormat() == Format.RGB8) {
+                    int j = (xPos + yPos * width) * 3;
+                    image[i] = 1; //a
+                    image[i + 1] = sourceData.get(j + 2); //b
+                    image[i + 2] = sourceData.get(j + 1); //g
+                    image[i + 3] = sourceData.get(j); //r
+                } else if (source.getFormat() == Format.RGBA8) {
+                    int j = (xPos + yPos * width) * 4;
+                    image[i] = sourceData.get(j + 3); //a
+                    image[i + 1] = sourceData.get(j + 2); //b
+                    image[i + 2] = sourceData.get(j + 1); //g
+                    image[i + 3] = sourceData.get(j); //r
+                } else if (source.getFormat() == Format.Luminance8) {
+                    int j = (xPos + yPos * width) * 1;
+                    image[i] = 1; //a
+                    image[i + 1] = sourceData.get(j); //b
+                    image[i + 2] = sourceData.get(j); //g
+                    image[i + 3] = sourceData.get(j); //r
+                } else if (source.getFormat() == Format.Luminance8Alpha8) {
+                    int j = (xPos + yPos * width) * 2;
+                    image[i] = sourceData.get(j + 1); //a
+                    image[i + 1] = sourceData.get(j); //b
+                    image[i + 2] = sourceData.get(j); //g
+                    image[i + 3] = sourceData.get(j); //r
+                } else {
+                    //ImageToAwt conversion
+                    if (newImage == null) {
+                        newImage = convertImageToAwt(source);
+                        if (newImage != null) {
+                            source = newImage;
+                            sourceData = source.getData(0);
+                            int j = (xPos + yPos * width) * 4;
+                            image[i] = sourceData.get(j); //a
+                            image[i + 1] = sourceData.get(j + 1); //b
+                            image[i + 2] = sourceData.get(j + 2); //g
+                            image[i + 3] = sourceData.get(j + 3); //r
+                        }else{
+                            throw new UnsupportedOperationException("Cannot draw or convert textures with format " + source.getFormat());
+                        }
+                    } else {
+                        throw new UnsupportedOperationException("Cannot draw textures with format " + source.getFormat());
+                    }
+                }
+            }
+        }
+    }
+
+    private Image convertImageToAwt(Image source) {
+        //use awt dependent classes without actual dependency via reflection
+        try {
+            Class clazz = Class.forName("jme3tools.converters.ImageToAwt");
+            if (clazz == null) {
+                return null;
+            }
+            Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4));
+            clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage);
+            return newImage;
+        } catch (InstantiationException ex) {
+        } catch (IllegalAccessException ex) {
+        } catch (IllegalArgumentException ex) {
+        } catch (InvocationTargetException ex) {
+        } catch (NoSuchMethodException ex) {
+        } catch (SecurityException ex) {
+        } catch (ClassNotFoundException ex) {
+        }
+        return null;
+    }
+
+    /**
+     * Get the <code>TextureAtlasTile</code> for the given Texture
+     * @param texture The texture to retrieve the <code>TextureAtlasTile</code> for.
+     * @return 
+     */
+    public TextureAtlasTile getAtlasTile(Texture texture) {
+        String sourceTextureName = textureName(texture);
+        if (sourceTextureName != null) {
+            return getAtlasTile(sourceTextureName);
+        }
+        return null;
+    }
+
+    /**
+     * Get the <code>TextureAtlasTile</code> for the given Texture
+     * @param assetName The texture to retrieve the <code>TextureAtlasTile</code> for.
+     * @return 
+     */
+    private TextureAtlasTile getAtlasTile(String assetName) {
+        return locationMap.get(assetName);
+    }
+
+    /**
+     * Creates a new atlas texture for the given map name.
+     * @param mapName
+     * @return 
+     */
+    public Texture getAtlasTexture(String mapName) {
+        if (images == null) {
+            return null;
+        }
+        byte[] image = images.get(mapName);
+        if (image != null) {
+            Texture2D tex = new Texture2D(new Image(format, atlasWidth, atlasHeight, BufferUtils.createByteBuffer(image)));
+            tex.setMagFilter(Texture.MagFilter.Bilinear);
+            tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap);
+            tex.setWrap(Texture.WrapMode.Clamp);
+            return tex;
+        }
+        return null;
+    }
+
+    /**
+     * Applies the texture coordinates to the given geometry
+     * if its DiffuseMap or ColorMap exists in the atlas.
+     * @param geom The geometry to change the texture coordinate buffer on.
+     * @return true if texture has been found and coords have been changed, false otherwise.
+     */
+    public boolean applyCoords(Geometry geom) {
+        return applyCoords(geom, 0, geom.getMesh());
+    }
+
+    /**
+     * Applies the texture coordinates to the given output mesh
+     * if the DiffuseMap or ColorMap of the input geometry exist in the atlas.
+     * @param geom The geometry to change the texture coordinate buffer on.
+     * @param offset Target buffer offset.
+     * @param outMesh The mesh to set the coords in (can be same as input).
+     * @return true if texture has been found and coords have been changed, false otherwise.
+     */
+    public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) {
+        Mesh inMesh = geom.getMesh();
+        geom.computeWorldMatrix();
+
+        VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord);
+        VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord);
+
+        if (inBuf == null || outBuf == null) {
+            throw new IllegalStateException("Geometry mesh has no texture coordinate buffer.");
+        }
+
+        Texture tex = getMaterialTexture(geom, "DiffuseMap");
+        if (tex == null) {
+            tex = getMaterialTexture(geom, "ColorMap");
+
+        }
+        if (tex != null) {
+            TextureAtlasTile tile = getAtlasTile(tex);
+            if (tile != null) {
+                FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+                FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                tile.transformTextureCoords(inPos, offset, outPos);
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            throw new IllegalStateException("Geometry has no proper texture.");
+        }
+    }
+
+    /**
+     * Create a texture atlas for the given root node, containing DiffuseMap, NormalMap and SpecularMap.
+     * @param root The rootNode to create the atlas for.
+     * @param atlasSize The size of the atlas (width and height).
+     * @return Null if the atlas cannot be created because not all textures fit.
+     */
+    public static TextureAtlas createAtlas(Spatial root, int atlasSize) {
+        List<Geometry> geometries = new ArrayList<Geometry>();
+        GeometryBatchFactory.gatherGeoms(root, geometries);
+        TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize);
+        for (Geometry geometry : geometries) {
+            if (!atlas.addGeometry(geometry)) {
+                logger.log(Level.WARNING, "Texture atlas size too small, cannot add all textures");
+                return null;
+            }
+        }
+        return atlas;
+    }
+
+    /**
+     * Creates one geometry out of the given root spatial and merges all single
+     * textures into one texture of the given size.
+     * @param spat The root spatial of the scene to batch
+     * @param mgr An assetmanager that can be used to create the material.
+     * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures.
+     * @return A new geometry that uses the generated texture atlas and merges all meshes of the root spatial, null if the atlas cannot be created because not all textures fit.
+     */
+    public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) {
+        List<Geometry> geometries = new ArrayList<Geometry>();
+        GeometryBatchFactory.gatherGeoms(spat, geometries);
+        TextureAtlas atlas = createAtlas(spat, atlasSize);
+        if (atlas == null) {
+            return null;
+        }
+        Geometry geom = new Geometry();
+        Mesh mesh = new Mesh();
+        GeometryBatchFactory.mergeGeometries(geometries, mesh);
+        applyAtlasCoords(geometries, mesh, atlas);
+        mesh.updateCounts();
+        mesh.updateBound();
+        geom.setMesh(mesh);
+
+        Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
+        mat.getAdditionalRenderState().setAlphaTest(true);
+        Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap");
+        Texture normalMap = atlas.getAtlasTexture("NormalMap");
+        Texture specularMap = atlas.getAtlasTexture("SpecularMap");
+        if (diffuseMap != null) {
+            mat.setTexture("DiffuseMap", diffuseMap);
+        }
+        if (normalMap != null) {
+            mat.setTexture("NormalMap", normalMap);
+        }
+        if (specularMap != null) {
+            mat.setTexture("SpecularMap", specularMap);
+        }
+        mat.setFloat("Shininess", 16.0f);
+
+        geom.setMaterial(mat);
+        return geom;
+    }
+
+    private static void applyAtlasCoords(List<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) {
+        int globalVertIndex = 0;
+
+        for (Geometry geom : geometries) {
+            Mesh inMesh = geom.getMesh();
+            geom.computeWorldMatrix();
+
+            int geomVertCount = inMesh.getVertexCount();
+
+            VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord);
+            VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord);
+
+            if (inBuf == null || outBuf == null) {
+                continue;
+            }
+
+            atlas.applyCoords(geom, globalVertIndex, outMesh);
+
+            globalVertIndex += geomVertCount;
+        }
+    }
+
+    private static Texture getMaterialTexture(Geometry geometry, String mapName) {
+        Material mat = geometry.getMaterial();
+        if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) {
+            return null;
+        }
+        MatParamTexture param = (MatParamTexture) mat.getParam(mapName);
+        Texture texture = param.getTextureValue();
+        if (texture == null) {
+            return null;
+        }
+        return texture;
+
+
+    }
+
+    private class Node {
+
+        public TextureAtlasTile location;
+        public Node child[];
+        public boolean occupied;
+
+        public Node(int x, int y, int width, int height) {
+            location = new TextureAtlasTile(x, y, width, height);
+            child = new Node[2];
+            child[0] = null;
+            child[1] = null;
+            occupied = false;
+        }
+
+        public boolean isLeaf() {
+            return child[0] == null && child[1] == null;
+        }
+
+        // Algorithm from http://www.blackpawn.com/texts/lightmaps/
+        public Node insert(Image image) {
+            if (!isLeaf()) {
+                Node newNode = child[0].insert(image);
+
+                if (newNode != null) {
+                    return newNode;
+                }
+
+                return child[1].insert(image);
+            } else {
+                if (occupied) {
+                    return null; // occupied
+                }
+
+                if (image.getWidth() > location.getWidth() || image.getHeight() > location.getHeight()) {
+                    return null; // does not fit
+                }
+
+                if (image.getWidth() == location.getWidth() && image.getHeight() == location.getHeight()) {
+                    occupied = true; // perfect fit
+                    return this;
+                }
+
+                int dw = location.getWidth() - image.getWidth();
+                int dh = location.getHeight() - image.getHeight();
+
+                if (dw > dh) {
+                    child[0] = new Node(location.getX(), location.getY(), image.getWidth(), location.getHeight());
+                    child[1] = new Node(location.getX() + image.getWidth(), location.getY(), location.getWidth() - image.getWidth(), location.getHeight());
+                } else {
+                    child[0] = new Node(location.getX(), location.getY(), location.getWidth(), image.getHeight());
+                    child[1] = new Node(location.getX(), location.getY() + image.getHeight(), location.getWidth(), location.getHeight() - image.getHeight());
+                }
+
+                return child[0].insert(image);
+            }
+        }
+    }
+
+    public class TextureAtlasTile {
+
+        private int x;
+        private int y;
+        private int width;
+        private int height;
+
+        public TextureAtlasTile(int x, int y, int width, int height) {
+            this.x = x;
+            this.y = y;
+            this.width = width;
+            this.height = height;
+        }
+
+        /**
+         * Get the transformed texture coordinate for a given input location.
+         * @param previousLocation The old texture coordinate.
+         * @return The new texture coordinate inside the atlas.
+         */
+        public Vector2f getLocation(Vector2f previousLocation) {
+            float x = (float) getX() / (float) atlasWidth;
+            float y = (float) getY() / (float) atlasHeight;
+            float w = (float) getWidth() / (float) atlasWidth;
+            float h = (float) getHeight() / (float) atlasHeight;
+            Vector2f location = new Vector2f(x, y);
+            float prevX = previousLocation.x;
+            float prevY = previousLocation.y;
+            location.addLocal(prevX * w, prevY * h);
+            return location;
+        }
+
+        /**
+         * Transforms a whole texture coordinates buffer.
+         * @param inBuf The input texture buffer.
+         * @param offset The offset in the output buffer
+         * @param outBuf The output buffer.
+         */
+        public void transformTextureCoords(FloatBuffer inBuf, int offset, FloatBuffer outBuf) {
+            Vector2f tex = new Vector2f();
+
+            // offset is given in element units
+            // convert to be in component units
+            offset *= 2;
+
+            for (int i = 0; i < inBuf.capacity() / 2; i++) {
+                tex.x = inBuf.get(i * 2 + 0);
+                tex.y = inBuf.get(i * 2 + 1);
+                Vector2f location = getLocation(tex);
+                //TODO: add proper texture wrapping for atlases..
+                outBuf.put(offset + i * 2 + 0, location.x);
+                outBuf.put(offset + i * 2 + 1, location.y);
+            }
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public int getY() {
+            return y;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+    }
+}
diff --git a/engine/src/tools/jme3tools/optimize/TriangleCollector.java b/engine/src/tools/jme3tools/optimize/TriangleCollector.java
new file mode 100644
index 0000000..9ce4ffa
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/TriangleCollector.java
@@ -0,0 +1,248 @@
+/*
+ * 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 jme3tools.optimize;
+
+import com.jme3.light.Light;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+import java.util.*;
+
+public class TriangleCollector {
+
+    private static final GeomTriComparator comparator = new GeomTriComparator();
+
+    private static class GeomTriComparator implements Comparator<OCTTriangle> {
+        public int compare(OCTTriangle a, OCTTriangle b) {
+            if (a.getGeometryIndex() < b.getGeometryIndex()){
+                return -1;
+            }else if (a.getGeometryIndex() > b.getGeometryIndex()){
+                return 1;
+            }else{
+                return 0;
+            }
+        }
+    }
+
+    private static class Range {
+        
+        private int start, length;
+
+        public Range(int start, int length) {
+            this.start = start;
+            this.length = length;
+        }
+
+        public int getLength() {
+            return length;
+        }
+
+        public void setLength(int length) {
+            this.length = length;
+        }
+
+        public int getStart() {
+            return start;
+        }
+
+        public void setStart(int start) {
+            this.start = start;
+        }
+
+    }
+
+    /**
+     * Grabs all the triangles specified in <code>tris</code> from the input array
+     * (using the indices OCTTriangle.getGeometryIndex() & OCTTriangle.getTriangleIndex())
+     * then organizes them into output geometry.
+     *
+     * @param inGeoms
+     * @param tris
+     * @return
+     */
+    public static final List<Geometry> gatherTris(Geometry[] inGeoms, List<OCTTriangle> tris){
+        Collections.sort(tris, comparator);
+        HashMap<Integer, Range> ranges = new HashMap<Integer, Range>();
+
+        for (int i = 0; i < tris.size(); i++){
+            Range r = ranges.get(tris.get(i).getGeometryIndex());
+            if (r != null){
+                // incremenet length
+                r.setLength(r.getLength()+1);
+            }else{
+                // set offset, length is 1
+                ranges.put(tris.get(i).getGeometryIndex(), new Range(i, 1));
+            }
+        }
+        
+        List<Geometry> newGeoms = new ArrayList<Geometry>();
+        int[] vertIndicies = new int[3];
+        int[] newIndices = new int[3];
+        boolean[] vertexCreated = new boolean[3];
+        HashMap<Integer, Integer> indexCache = new HashMap<Integer, Integer>();
+        for (Map.Entry<Integer, Range> entry : ranges.entrySet()){
+            int inGeomIndex = entry.getKey().intValue();
+            int outOffset = entry.getValue().start;
+            int outLength = entry.getValue().length;
+
+            Geometry inGeom = inGeoms[inGeomIndex];
+            Mesh in = inGeom.getMesh();
+            Mesh out = new Mesh();
+
+            int outElementCount = outLength * 3;
+            ShortBuffer ib = BufferUtils.createShortBuffer(outElementCount);
+            out.setBuffer(Type.Index, 3, ib);
+
+            // generate output buffers based on input buffers
+            IntMap<VertexBuffer> bufs = in.getBuffers();
+            for (Entry<VertexBuffer> ent : bufs){
+                VertexBuffer vb = ent.getValue();
+                if (vb.getBufferType() == Type.Index)
+                    continue;
+
+                // NOTE: we are not actually sure
+                // how many elements will be in this buffer.
+                // It will be compacted later.
+                Buffer b = VertexBuffer.createBuffer(vb.getFormat(), 
+                                                     vb.getNumComponents(),
+                                                     outElementCount);
+
+                VertexBuffer outVb = new VertexBuffer(vb.getBufferType());
+                outVb.setNormalized(vb.isNormalized());
+                outVb.setupData(vb.getUsage(), vb.getNumComponents(), vb.getFormat(), b);
+                out.setBuffer(outVb);
+            }
+
+            int currentVertex = 0;
+            for (int i = outOffset; i < outOffset + outLength; i++){
+                OCTTriangle t = tris.get(i);
+
+                // find vertex indices for triangle t
+                in.getTriangle(t.getTriangleIndex(), vertIndicies);
+
+                // find indices in new buf
+                Integer i0 = indexCache.get(vertIndicies[0]);
+                Integer i1 = indexCache.get(vertIndicies[1]);
+                Integer i2 = indexCache.get(vertIndicies[2]);
+
+                // check which ones were not created
+                // if not created in new IB, create them
+                if (i0 == null){
+                    vertexCreated[0] = true;
+                    newIndices[0] = currentVertex++;
+                    indexCache.put(vertIndicies[0], newIndices[0]);
+                }else{
+                    newIndices[0] = i0.intValue();
+                    vertexCreated[0] = false;
+                }
+                if (i1 == null){
+                    vertexCreated[1] = true;
+                    newIndices[1] = currentVertex++;
+                    indexCache.put(vertIndicies[1], newIndices[1]);
+                }else{
+                    newIndices[1] = i1.intValue();
+                    vertexCreated[1] = false;
+                }
+                if (i2 == null){
+                    vertexCreated[2] = true;
+                    newIndices[2] = currentVertex++;
+                    indexCache.put(vertIndicies[2], newIndices[2]);
+                }else{
+                    newIndices[2] = i2.intValue();
+                    vertexCreated[2] = false;
+                }
+
+                // if any verticies were created for this triangle
+                // copy them to the output mesh
+                IntMap<VertexBuffer> inbufs = in.getBuffers();
+                for (Entry<VertexBuffer> ent : inbufs){
+                    VertexBuffer vb = ent.getValue();
+                    if (vb.getBufferType() == Type.Index)
+                        continue;
+                    
+                    VertexBuffer outVb = out.getBuffer(vb.getBufferType());
+                    // copy verticies that were created for this triangle
+                    for (int v = 0; v < 3; v++){
+                        if (!vertexCreated[v])
+                            continue;
+
+                        // copy triangle's attribute from one
+                        // buffer to another
+                        vb.copyElement(vertIndicies[v], outVb, newIndices[v]);
+                    }
+                }
+
+                // write the indices onto the output index buffer
+                ib.put((short)newIndices[0])
+                  .put((short)newIndices[1])
+                  .put((short)newIndices[2]);
+            }
+            ib.clear();
+            indexCache.clear();
+
+            // since some verticies were cached, it means there's
+            // extra data in some buffers
+            IntMap<VertexBuffer> outbufs = out.getBuffers();
+            for (Entry<VertexBuffer> ent : outbufs){
+                VertexBuffer vb = ent.getValue();
+                if (vb.getBufferType() == Type.Index)
+                    continue;
+
+                vb.compact(currentVertex);
+            }
+
+            out.updateBound();
+            out.updateCounts();
+            out.setStatic();
+            //out.setInterleaved();
+            Geometry outGeom = new Geometry("Geom"+entry.getKey(), out);
+            outGeom.setLocalTransform(inGeom.getWorldTransform());
+            outGeom.setMaterial(inGeom.getMaterial());
+            for (Light light : inGeom.getWorldLightList()){
+                outGeom.addLight(light);
+            }
+
+            outGeom.updateGeometricState();
+            newGeoms.add(outGeom);
+        }
+
+        return newGeoms;
+    }
+
+}
diff --git a/engine/src/tools/jme3tools/optimize/pvsnotes b/engine/src/tools/jme3tools/optimize/pvsnotes
new file mode 100644
index 0000000..61d83a5
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/pvsnotes
@@ -0,0 +1,40 @@
+convert all leafs in octree to PvsNode, add to list pvsNodes

+

+for (every nodeX in pvsNodes):

+    for (every nodeY in pvsNodes):

+        if (nodeX == nodeY or nodeX adjecent or intersecting nodeY):

+            continue

+

+        setup camera for (nodeX, nodeY)

+        draw every node except nodeX & nodeY

+

+        turn on occlusion query

+        draw nodeY as bounding box

+        turn off occlusion query

+

+        if (numSamples > 0): // node is visible

+            add nodeY to nodeX's potentially visible set

+

+

+setup camera for node, sideI:

+

+    float width, height, near;

+

+    switch (sideI):

+        case X+

+        case X-

+            width  = x extent

+            height = y extent

+            near   = z extent / 2

+        case Y+

+        case Y-

+            width  = x extent

+            height = z extent

+            near   = y extent / 2

+        case Z+

+        case Z-

+            width  = z extent

+            height = y extent

+            near   = x extent / 2

+

+

diff --git a/engine/src/tools/jme3tools/savegame/SaveGame.java b/engine/src/tools/jme3tools/savegame/SaveGame.java
new file mode 100644
index 0000000..56a693e
--- /dev/null
+++ b/engine/src/tools/jme3tools/savegame/SaveGame.java
@@ -0,0 +1,118 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.savegame;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.Savable;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.system.JmeSystem;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Tool for saving Savables as SaveGame entries in a system-dependent way.
+ * @author normenhansen
+ */
+public class SaveGame {
+
+    /**
+     * Saves a savable in a system-dependent way.
+     * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+     * @param dataName A unique name for this savegame, e.g. "save_001"
+     * @param data The Savable to save
+     */
+    public static void saveGame(String gamePath, String dataName, Savable data) {
+        BinaryExporter ex = BinaryExporter.getInstance();
+        OutputStream os = null;
+        try {
+            File daveFolder = new File(JmeSystem.getStorageFolder().getAbsolutePath() + File.separator + gamePath.replaceAll("/", File.separator));
+            if (!daveFolder.exists() && !daveFolder.mkdirs()) {
+                Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
+                throw new IllegalStateException("SaveGame dataset cannot be created");
+            }
+            File saveFile = new File(daveFolder.getAbsolutePath() + File.separator + dataName);
+            if (!saveFile.exists()) {
+                if (!saveFile.createNewFile()) {
+                    Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
+                    throw new IllegalStateException("SaveGame dataset cannot be created");
+                }
+            }
+            os = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(saveFile)));
+            ex.save(data, os);
+        } catch (IOException ex1) {
+            Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
+            ex1.printStackTrace();
+            throw new IllegalStateException("SaveGame dataset cannot be saved");
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                }
+            } catch (IOException ex1) {
+                Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
+                ex1.printStackTrace();
+                throw new IllegalStateException("SaveGame dataset cannot be saved");
+            }
+        }
+    }
+
+    /**
+     * Loads a savable that has been saved on this system with saveGame() before.
+     * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+     * @param dataName A unique name for this savegame, e.g. "save_001"
+     * @return The savable that was saved
+     */
+    public static Savable loadGame(String gamePath, String dataName) {
+        return loadGame(gamePath, dataName, null);
+    }
+
+    /**
+     * Loads a savable that has been saved on this system with saveGame() before.
+     * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+     * @param dataName A unique name for this savegame, e.g. "save_001"
+     * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures)
+     * @return The savable that was saved or null if none was found
+     */
+    public static Savable loadGame(String gamePath, String dataName, AssetManager manager) {
+        InputStream is = null;
+        Savable sav = null;
+        try {
+            File file = new File(JmeSystem.getStorageFolder().getAbsolutePath() + File.separator + gamePath.replaceAll("/", File.separator) + File.separator + dataName);
+            if(!file.exists()){
+                return null;
+            }
+            is = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file)));
+            BinaryImporter imp = BinaryImporter.getInstance();
+            if (manager != null) {
+                imp.setAssetManager(manager);
+            }
+            sav = imp.load(is);
+        } catch (IOException ex) {
+            Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
+            ex.printStackTrace();
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException ex) {
+                    Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
+                    ex.printStackTrace();
+                }
+            }
+        }
+        return sav;
+    }
+}
diff --git a/engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java b/engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java
new file mode 100644
index 0000000..c3be20a
--- /dev/null
+++ b/engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java
@@ -0,0 +1,1511 @@
+/*
+ * Copyright (c) 2009-2010 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.export.xml;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.export.SavableClassUtil;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.*;
+import java.util.logging.Logger;
+import org.w3c.dom.*;
+
+/**
+ * Part of the jME XML IO system as introduced in the google code jmexml project.
+ *
+ * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project
+ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5
+ * @author blaine
+ */
+public class DOMInputCapsule implements InputCapsule {
+    private static final Logger logger =
+        Logger.getLogger(DOMInputCapsule.class .getName());
+
+    private Document doc;
+    private Element currentElem;
+    private XMLImporter importer;
+    private boolean isAtRoot = true;
+    private Map<String, Savable> referencedSavables = new HashMap<String, Savable>();
+    
+    private int[] classHierarchyVersions;
+    private Savable savable;
+
+    public DOMInputCapsule(Document doc, XMLImporter importer) {
+        this.doc = doc;
+        this.importer = importer;
+        currentElem = doc.getDocumentElement();
+        
+        String version = currentElem.getAttribute("format_version");
+        importer.formatVersion = version.equals("") ? 0 : Integer.parseInt(version);
+    }
+
+    public int getSavableVersion(Class<? extends Savable> desiredClass) {
+        if (classHierarchyVersions != null){
+            return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, 
+                                                        classHierarchyVersions, importer.getFormatVersion());
+        }else{
+            return 0;
+        }
+    }
+    
+    private static String decodeString(String s) {
+        if (s == null) {
+            return null;
+        }
+        s = s.replaceAll("\\&quot;", "\"").replaceAll("\\&lt;", "<").replaceAll("\\&amp;", "&");
+        return s;
+    }
+
+    private Element findFirstChildElement(Element parent) {
+        Node ret = parent.getFirstChild();
+        while (ret != null && (!(ret instanceof Element))) {
+            ret = ret.getNextSibling();
+        }
+        return (Element) ret;
+    }
+
+    private Element findChildElement(Element parent, String name) {
+        if (parent == null) {
+            return null;
+        }
+        Node ret = parent.getFirstChild();
+        while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) {
+            ret = ret.getNextSibling();
+        }
+        return (Element) ret;
+    }
+
+    private Element findNextSiblingElement(Element current) {
+        Node ret = current.getNextSibling();
+        while (ret != null) {
+            if (ret instanceof Element) {
+                return (Element) ret;
+            }
+            ret = ret.getNextSibling();
+        }
+        return null;
+    }
+
+    public byte readByte(String name, byte defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Byte.parseByte(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public byte[] readByteArray(String name, byte[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of bytes for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains "
+                            + strings.length);
+            }
+            byte[] tmp = new byte[strings.length];
+            for (int i = 0; i < strings.length; i++) {
+                tmp[i] = Byte.parseByte(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<byte[]> byteArrays = new ArrayList<byte[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    byteArrays.add(readByteArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (byteArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + byteArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return byteArrays.toArray(new byte[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public int readInt(String name, int defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Integer.parseInt(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public int[] readIntArray(String name, int[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of ints for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            int[] tmp = new int[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Integer.parseInt(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public int[][] readIntArray2D(String name, int[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+
+
+
+
+            NodeList nodes = currentElem.getChildNodes();
+            List<int[]> intArrays = new ArrayList<int[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    intArrays.add(readIntArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (intArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + intArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return intArrays.toArray(new int[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public float readFloat(String name, float defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Float.parseFloat(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public float[] readFloatArray(String name, float[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of floats for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            float[] tmp = new float[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Float.parseFloat(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException {
+        /* Why does this one method ignore the 'size attr.? */
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+            int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+
+            float[][] tmp = new float[size_outer][size_inner];
+
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            for (int i = 0; i < size_outer; i++) {
+                tmp[i] = new float[size_inner];
+                for (int k = 0; k < size_inner; k++) {
+                    tmp[i][k] = Float.parseFloat(strings[i]);
+                }
+            }
+            return tmp;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public double readDouble(String name, double defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Double.parseDouble(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public double[] readDoubleArray(String name, double[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of doubles for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            double[] tmp = new double[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Double.parseDouble(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<double[]> doubleArrays = new ArrayList<double[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    doubleArrays.add(readDoubleArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (doubleArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + doubleArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return doubleArrays.toArray(new double[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public long readLong(String name, long defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Long.parseLong(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public long[] readLongArray(String name, long[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of longs for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            long[] tmp = new long[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Long.parseLong(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public long[][] readLongArray2D(String name, long[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<long[]> longArrays = new ArrayList<long[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    longArrays.add(readLongArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (longArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + longArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return longArrays.toArray(new long[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public short readShort(String name, short defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Short.parseShort(tmpString);
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public short[] readShortArray(String name, short[] defVal) throws IOException {
+         try {
+             Element tmpEl;
+             if (name != null) {
+                 tmpEl = findChildElement(currentElem, name);
+             } else {
+                 tmpEl = currentElem;
+             }
+             if (tmpEl == null) {
+                 return defVal;
+             }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of shorts for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            short[] tmp = new short[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Short.parseShort(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public short[][] readShortArray2D(String name, short[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<short[]> shortArrays = new ArrayList<short[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    shortArrays.add(readShortArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (shortArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + shortArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return shortArrays.toArray(new short[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public boolean readBoolean(String name, boolean defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return Boolean.parseBoolean(tmpString);
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of bools for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            boolean[] tmp = new boolean[strings.length];
+            for (int i = 0; i < tmp.length; i++) {
+                tmp[i] = Boolean.parseBoolean(strings[i]);
+            }
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<boolean[]> booleanArrays = new ArrayList<boolean[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    booleanArrays.add(readBooleanArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (booleanArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + booleanArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return booleanArrays.toArray(new boolean[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public String readString(String name, String defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            return decodeString(tmpString);
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public String[] readStringArray(String name, String[] defVal) throws IOException {
+         try {
+             Element tmpEl;
+             if (name != null) {
+                 tmpEl = findChildElement(currentElem, name);
+             } else {
+                 tmpEl = currentElem;
+             }
+             if (tmpEl == null) {
+                 return defVal;
+             }
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = tmpEl.getChildNodes();
+            List<String> strings = new ArrayList<String>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("String")) {
+                // Very unsafe assumption
+                    strings.add(((Element) n).getAttributeNode("value").getValue());
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + strings.size());
+            }
+            return strings.toArray(new String[0]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public String[][] readStringArray2D(String name, String[][] defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            NodeList nodes = currentElem.getChildNodes();
+            List<String[]> stringArrays = new ArrayList<String[]>();
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                        Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().contains("array")) {
+                // Very unsafe assumption
+                    stringArrays.add(readStringArray(n.getNodeName(), null));
+                                }
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (stringArrays.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + stringArrays.size());
+            }
+            currentElem = (Element) currentElem.getParentNode();
+            return stringArrays.toArray(new String[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public BitSet readBitSet(String name, BitSet defVal) throws IOException {
+        String tmpString = currentElem.getAttribute(name);
+        if (tmpString == null || tmpString.length() < 1) return defVal;
+        try {
+            BitSet set = new BitSet();
+            String[] strings = parseTokens(tmpString);
+            for (int i = 0; i < strings.length; i++) {
+                int isSet = Integer.parseInt(strings[i]);
+                if (isSet == 1) {
+                        set.set(i);
+                }
+            }
+            return set;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public Savable readSavable(String name, Savable defVal) throws IOException {
+        Savable ret = defVal;
+        if (name != null && name.equals(""))
+            logger.warning("Reading Savable String with name \"\"?");
+        try {
+            Element tmpEl = null;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+                if (tmpEl == null) {
+                    return defVal;
+                }
+            } else if (isAtRoot) {
+                tmpEl = doc.getDocumentElement();
+                isAtRoot = false;
+            } else {
+                tmpEl = findFirstChildElement(currentElem);
+            }
+            currentElem = tmpEl;
+            ret = readSavableFromCurrentElem(defVal);
+            if (currentElem.getParentNode() instanceof Element) {
+                currentElem = (Element) currentElem.getParentNode();
+            } else {
+                currentElem = null;
+            }
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+        return ret;
+    }
+
+    private Savable readSavableFromCurrentElem(Savable defVal) throws
+            InstantiationException, ClassNotFoundException,
+            IOException, IllegalAccessException {
+        Savable ret = defVal;
+        Savable tmp = null;
+
+        if (currentElem == null || currentElem.getNodeName().equals("null")) {
+            return null;
+        }
+        String reference = currentElem.getAttribute("ref");
+        if (reference.length() > 0) {
+            ret = referencedSavables.get(reference);
+        } else {
+            String className = currentElem.getNodeName();
+            if (defVal != null) {
+                className = defVal.getClass().getName();
+            } else if (currentElem.hasAttribute("class")) {
+                className = currentElem.getAttribute("class");
+            }
+            tmp = SavableClassUtil.fromName(className, null);
+            
+            
+            String versionsStr = currentElem.getAttribute("savable_versions");
+            if (versionsStr != null && !versionsStr.equals("")){
+                String[] versionStr = versionsStr.split(",");
+                classHierarchyVersions = new int[versionStr.length];
+                for (int i = 0; i < classHierarchyVersions.length; i++){
+                    classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim());
+                }
+            }else{
+                classHierarchyVersions = null;
+            }
+            
+            String refID = currentElem.getAttribute("reference_ID");
+            if (refID.length() < 1) refID = currentElem.getAttribute("id");
+            if (refID.length() > 0) referencedSavables.put(refID, tmp);
+            if (tmp != null) {
+                // Allows reading versions from this savable
+                savable = tmp;
+                tmp.read(importer);
+                ret = tmp;
+            }
+        }
+        return ret;
+    }
+
+    public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException {
+        Savable[] ret = defVal;
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            List<Savable> savables = new ArrayList<Savable>();
+            for (currentElem = findFirstChildElement(tmpEl);
+                    currentElem != null;
+                    currentElem = findNextSiblingElement(currentElem)) {
+                savables.add(readSavableFromCurrentElem(null));
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (savables.size() != requiredSize)
+                    throw new IOException("Wrong number of Savables for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + savables.size());
+            }
+            ret = savables.toArray(new Savable[0]);
+            currentElem = (Element) tmpEl.getParentNode();
+            return ret;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+    }
+
+    public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException {
+        Savable[][] ret = defVal;
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+            int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+
+            Savable[][] tmp = new Savable[size_outer][size_inner];
+            currentElem = findFirstChildElement(tmpEl);
+            for (int i = 0; i < size_outer; i++) {
+                for (int j = 0; j < size_inner; j++) {
+                    tmp[i][j] = (readSavableFromCurrentElem(null));
+                    if (i == size_outer - 1 && j == size_inner - 1) {
+                        break;
+                    }
+                    currentElem = findNextSiblingElement(currentElem);
+                }
+            }
+            ret = tmp;
+            currentElem = (Element) tmpEl.getParentNode();
+            return ret;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+    }
+
+    public ArrayList<Savable> readSavableArrayList(String name, ArrayList defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            ArrayList<Savable> savables = new ArrayList<Savable>();
+            for (currentElem = findFirstChildElement(tmpEl);
+                    currentElem != null;
+                    currentElem = findNextSiblingElement(currentElem)) {
+                savables.add(readSavableFromCurrentElem(null));
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (savables.size() != requiredSize)
+                    throw new IOException(
+                            "Wrong number of Savable arrays for '" + name
+                            + "'.  size says " + requiredSize
+                            + ", data contains " + savables.size());
+            }
+            currentElem = (Element) tmpEl.getParentNode();
+            return savables;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+    }
+
+    public ArrayList<Savable>[] readSavableArrayListArray(
+            String name, ArrayList[] defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+            currentElem = tmpEl;
+
+            String sizeString = tmpEl.getAttribute("size");
+            int requiredSize = (sizeString.length() > 0)
+                             ? Integer.parseInt(sizeString)
+                             : -1;
+
+            ArrayList<Savable> sal;
+            List<ArrayList<Savable>> savableArrayLists =
+                    new ArrayList<ArrayList<Savable>>();
+            int i = -1;
+            while (true) {
+                sal = readSavableArrayList("SavableArrayList_" + ++i, null);
+                if (sal == null && savableArrayLists.size() >= requiredSize)
+                    break;
+                savableArrayLists.add(sal);
+            }
+
+            if (requiredSize > -1 && savableArrayLists.size() != requiredSize)
+                throw new IOException(
+                        "String array contains wrong element count.  "
+                        + "Specified size " + requiredSize
+                        + ", data contains " + savableArrayLists.size());
+            currentElem = (Element) tmpEl.getParentNode();
+            return savableArrayLists.toArray(new ArrayList[0]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public ArrayList<Savable>[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+            currentElem = tmpEl;
+            String sizeString = tmpEl.getAttribute("size");
+
+            ArrayList<Savable>[] arr;
+            List<ArrayList<Savable>[]> sall = new ArrayList<ArrayList<Savable>[]>();
+            int i = -1;
+            while ((arr = readSavableArrayListArray(
+                    "SavableArrayListArray_" + ++i, null)) != null) sall.add(arr);
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (sall.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + sall.size());
+            }
+            currentElem = (Element) tmpEl.getParentNode();
+            return sall.toArray(new ArrayList[0][]);
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+    }
+
+    public ArrayList<FloatBuffer> readFloatBufferArrayList(
+            String name, ArrayList<FloatBuffer> defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            ArrayList<FloatBuffer> tmp = new ArrayList<FloatBuffer>();
+            for (currentElem = findFirstChildElement(tmpEl);
+                    currentElem != null;
+                    currentElem = findNextSiblingElement(currentElem)) {
+                tmp.add(readFloatBuffer(null, null));
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (tmp.size() != requiredSize)
+                    throw new IOException(
+                            "String array contains wrong element count.  "
+                            + "Specified size " + requiredSize
+                            + ", data contains " + tmp.size());
+            }
+            currentElem = (Element) tmpEl.getParentNode();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public Map<? extends Savable, ? extends Savable> readSavableMap(String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+        Map<Savable, Savable> ret;
+        Element tempEl;
+
+        if (name != null) {
+                tempEl = findChildElement(currentElem, name);
+        } else {
+                tempEl = currentElem;
+        }
+        ret = new HashMap<Savable, Savable>();
+
+        NodeList nodes = tempEl.getChildNodes();
+        for (int i = 0; i < nodes.getLength(); i++) {
+                Node n = nodes.item(i);
+            if (n instanceof Element && n.getNodeName().equals("MapEntry")) {
+                Element elem = (Element) n;
+                        currentElem = elem;
+                        Savable key = readSavable(XMLExporter.ELEMENT_KEY, null);
+                        Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null);
+                        ret.put(key, val);
+                }
+        }
+        currentElem = (Element) tempEl.getParentNode();
+        return ret;
+    }
+
+    public Map<String, ? extends Savable> readStringSavableMap(String name, Map<String, ? extends Savable> defVal) throws IOException {
+        Map<String, Savable> ret = null;
+        Element tempEl;
+
+        if (name != null) {
+                tempEl = findChildElement(currentElem, name);
+        } else {
+                tempEl = currentElem;
+        }
+        if (tempEl != null) {
+                ret = new HashMap<String, Savable>();
+
+                NodeList nodes = tempEl.getChildNodes();
+                    for (int i = 0; i < nodes.getLength(); i++) {
+                                Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().equals("MapEntry")) {
+                                        Element elem = (Element) n;
+                                        currentElem = elem;
+                                        String key = currentElem.getAttribute("key");
+                                        Savable val = readSavable("Savable", null);
+                                        ret.put(key, val);
+                                }
+                        }
+        } else {
+                return defVal;
+            }
+        currentElem = (Element) tempEl.getParentNode();
+        return ret;
+    }
+
+    public IntMap<? extends Savable> readIntSavableMap(String name, IntMap<? extends Savable> defVal) throws IOException {
+        IntMap<Savable> ret = null;
+        Element tempEl;
+
+        if (name != null) {
+                tempEl = findChildElement(currentElem, name);
+        } else {
+                tempEl = currentElem;
+        }
+        if (tempEl != null) {
+                ret = new IntMap<Savable>();
+
+                NodeList nodes = tempEl.getChildNodes();
+                    for (int i = 0; i < nodes.getLength(); i++) {
+                                Node n = nodes.item(i);
+                                if (n instanceof Element && n.getNodeName().equals("MapEntry")) {
+                                        Element elem = (Element) n;
+                                        currentElem = elem;
+                                        int key = Integer.parseInt(currentElem.getAttribute("key"));
+                                        Savable val = readSavable("Savable", null);
+                                        ret.put(key, val);
+                                }
+                        }
+        } else {
+                return defVal;
+            }
+        currentElem = (Element) tempEl.getParentNode();
+        return ret;
+    }
+
+    /**
+     * reads from currentElem if name is null
+     */
+    public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException {
+        try {
+            Element tmpEl;
+            if (name != null) {
+                tmpEl = findChildElement(currentElem, name);
+            } else {
+                tmpEl = currentElem;
+            }
+            if (tmpEl == null) {
+                return defVal;
+            }
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of float buffers for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            FloatBuffer tmp = BufferUtils.createFloatBuffer(strings.length);
+            for (String s : strings) tmp.put(Float.parseFloat(s));
+            tmp.flip();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of int buffers for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            IntBuffer tmp = BufferUtils.createIntBuffer(strings.length);
+            for (String s : strings) tmp.put(Integer.parseInt(s));
+            tmp.flip();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of byte buffers for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            ByteBuffer tmp = BufferUtils.createByteBuffer(strings.length);
+            for (String s : strings) tmp.put(Byte.valueOf(s));
+            tmp.flip();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+    public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            String[] strings = parseTokens(tmpEl.getAttribute("data"));
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (strings.length != requiredSize)
+                    throw new IOException("Wrong number of short buffers for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + strings.length);
+            }
+            ShortBuffer tmp = BufferUtils.createShortBuffer(strings.length);
+            for (String s : strings) tmp.put(Short.valueOf(s));
+            tmp.flip();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+    }
+
+        public ArrayList<ByteBuffer> readByteBufferArrayList(String name, ArrayList<ByteBuffer> defVal) throws IOException {
+        try {
+            Element tmpEl = findChildElement(currentElem, name);
+            if (tmpEl == null) {
+                return defVal;
+            }
+
+            String sizeString = tmpEl.getAttribute("size");
+            ArrayList<ByteBuffer> tmp = new ArrayList<ByteBuffer>();
+            for (currentElem = findFirstChildElement(tmpEl);
+                    currentElem != null;
+                    currentElem = findNextSiblingElement(currentElem)) {
+                tmp.add(readByteBuffer(null, null));
+            }
+            if (sizeString.length() > 0) {
+                int requiredSize = Integer.parseInt(sizeString);
+                if (tmp.size() != requiredSize)
+                    throw new IOException("Wrong number of short buffers for '"
+                            + name + "'.  size says " + requiredSize
+                            + ", data contains " + tmp.size());
+            }
+            currentElem = (Element) tmpEl.getParentNode();
+            return tmp;
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (NumberFormatException nfe) {
+            IOException io = new IOException(nfe.toString());
+            io.initCause(nfe);
+            throw io;
+        } catch (DOMException de) {
+            IOException io = new IOException(de.toString());
+            io.initCause(de);
+            throw io;
+        }
+        }
+
+        public <T extends Enum<T>> T readEnum(String name, Class<T> enumType,
+                        T defVal) throws IOException {
+        T ret = defVal;
+        try {
+            String eVal = currentElem.getAttribute(name);
+            if (eVal != null && eVal.length() > 0) {
+                ret = Enum.valueOf(enumType, eVal);
+            }
+        } catch (Exception e) {
+            IOException io = new IOException(e.toString());
+            io.initCause(e);
+            throw io;
+        }
+        return ret;
+        }
+
+    private static final String[] zeroStrings = new String[0];
+
+    protected String[] parseTokens(String inString) {
+        String[] outStrings = inString.split("\\s+");
+        return (outStrings.length == 1 && outStrings[0].length() == 0)
+               ? zeroStrings
+               : outStrings;
+    }
+}
\ No newline at end of file
diff --git a/engine/src/xml/com/jme3/export/xml/DOMOutputCapsule.java b/engine/src/xml/com/jme3/export/xml/DOMOutputCapsule.java
new file mode 100644
index 0000000..2baef1f
--- /dev/null
+++ b/engine/src/xml/com/jme3/export/xml/DOMOutputCapsule.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (c) 2009-2010 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.export.xml;
+
+import com.jme3.export.FormatVersion;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.export.SavableClassUtil;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.*;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Part of the jME XML IO system as introduced in the google code jmexml project.
+ *
+ * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project
+ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5
+ */
+public class DOMOutputCapsule implements OutputCapsule {
+
+    private static final String dataAttributeName = "data";
+    private Document doc;
+    private Element currentElement;
+    private JmeExporter exporter;
+    private Map<Savable, Element> writtenSavables = new IdentityHashMap<Savable, Element>();
+
+    public DOMOutputCapsule(Document doc, JmeExporter exporter) {
+        this.doc = doc;
+        this.exporter = exporter;
+        currentElement = null;
+    }
+
+    public Document getDoc() {
+        return doc;
+    }
+
+    /**
+     * appends a new Element with the given name to currentElement, sets
+     * currentElement to be new Element, and returns the new Element as well
+     */
+    private Element appendElement(String name) {
+        Element ret = doc.createElement(name);
+        if (currentElement == null) {
+            ret.setAttribute("format_version", Integer.toString(FormatVersion.VERSION));
+            doc.appendChild(ret);
+        } else {
+            currentElement.appendChild(ret);
+        }
+        currentElement = ret;
+        return ret;
+    }
+
+    private static String encodeString(String s) {
+        if (s == null) {
+            return null;
+        }
+        s =     s.replaceAll("\\&", "&amp;")
+                 .replaceAll("\\\"", "&quot;")
+                 .replaceAll("\\<", "&lt;");
+        return s;
+    }
+
+    public void write(byte value, String name, byte defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(byte[] value, String name, byte[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (byte b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(buf.length() - 1);
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(byte[][] value, String name, byte[][] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (byte[] bs : value) {
+            for (byte b : bs) {
+                buf.append(b);
+                buf.append(" ");
+            }
+            buf.append(" ");
+        }
+        //remove last spaces
+        buf.setLength(buf.length() - 2);
+
+        Element el = appendElement(name);
+        el.setAttribute("size_outer", String.valueOf(value.length));
+        el.setAttribute("size_inner", String.valueOf(value[0].length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(int value, String name, int defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(int[] value, String name, int[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) { return; }
+        if (Arrays.equals(value, defVal)) { return; }
+
+        for (int b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(Math.max(0, buf.length() - 1));
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(int[][] value, String name, int[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                int[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(float value, String name, float defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(float[] value, String name, float[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        if (value != null) {
+            for (float b : value) {
+                buf.append(b);
+                buf.append(" ");
+            }
+            //remove last space
+            buf.setLength(buf.length() - 1);
+        }
+
+        Element el = appendElement(name);
+        el.setAttribute("size", value == null ? "0" : String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(float[][] value, String name, float[][] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        for (float[] bs : value) {
+            for(float b : bs){
+                buf.append(b);
+                buf.append(" ");
+            }
+        }
+        //remove last space
+        buf.setLength(buf.length() - 1);
+
+        Element el = appendElement(name);
+        el.setAttribute("size_outer", String.valueOf(value.length));
+        el.setAttribute("size_inner", String.valueOf(value[0].length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(double value, String name, double defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(double[] value, String name, double[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (double b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(buf.length() - 1);
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(double[][] value, String name, double[][] defVal) throws IOException {
+            if (value == null) return;
+            if(Arrays.deepEquals(value, defVal)) return;
+
+            Element el = appendElement(name);
+            el.setAttribute("size", String.valueOf(value.length));
+
+            for (int i=0; i<value.length; i++) {
+                double[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+            }
+            currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(long value, String name, long defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(long[] value, String name, long[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (long b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(buf.length() - 1);
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(long[][] value, String name, long[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                long[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(short value, String name, short defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(short[] value, String name, short[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (short b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(buf.length() - 1);
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(short[][] value, String name, short[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                short[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(boolean value, String name, boolean defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+    }
+
+    public void write(boolean[] value, String name, boolean[] defVal) throws IOException {
+        StringBuilder buf = new StringBuilder();
+        if (value == null) {
+            value = defVal;
+        }
+        for (boolean b : value) {
+            buf.append(b);
+            buf.append(" ");
+        }
+        //remove last space
+        buf.setLength(Math.max(0, buf.length() - 1));
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                boolean[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(String value, String name, String defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
+            return;
+        }
+        currentElement.setAttribute(name, encodeString(value));
+    }
+
+    public void write(String[] value, String name, String[] defVal) throws IOException {
+        Element el = appendElement(name);
+
+        if (value == null) {
+            value = defVal;
+        }
+
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                String b = value[i];
+                appendElement("String_"+i);
+            String val = encodeString(b);
+            currentElement.setAttribute("value", val);
+            currentElement = el;
+        }
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void write(String[][] value, String name, String[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.length));
+
+        for (int i=0; i<value.length; i++) {
+                String[] array = value[i];
+                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(BitSet value, String name, BitSet defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
+            return;
+        }
+        StringBuilder buf = new StringBuilder();
+        for (int i = value.nextSetBit(0); i >= 0; i = value.nextSetBit(i + 1)) {
+            buf.append(i);
+            buf.append(" ");
+        }
+        buf.setLength(Math.max(0, buf.length() - 1));
+        currentElement.setAttribute(name, buf.toString());
+
+    }
+
+    public void write(Savable object, String name, Savable defVal) throws IOException {
+        if (object == null) {
+            return;
+        }
+        if (object.equals(defVal)) {
+            return;
+        }
+
+        Element old = currentElement;
+        Element el = writtenSavables.get(object);
+
+        String className = null;
+        if(!object.getClass().getName().equals(name)){
+            className = object.getClass().getName();
+        }
+        try {
+            doc.createElement(name);
+        } catch (DOMException e) {
+            // Ridiculous fallback behavior.
+            // Would be far better to throw than to totally disregard the
+            // specified "name" and write a class name instead!
+            // (Besides the fact we are clobbering the managed .getClassTag()).
+            name = "Object";
+            className = object.getClass().getName();
+        }
+
+        if (el != null) {
+            String refID = el.getAttribute("reference_ID");
+            if (refID.length() == 0) {
+                refID = object.getClass().getName() + "@" + object.hashCode();
+                el.setAttribute("reference_ID", refID);
+            }
+            el = appendElement(name);
+            el.setAttribute("ref", refID);
+        } else {
+            el = appendElement(name);
+            
+            // jME3 NEW: Append version number(s)
+            int[] versions = SavableClassUtil.getSavableVersions(object.getClass());
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < versions.length; i++){
+                sb.append(versions[i]);
+                if (i != versions.length - 1){
+                    sb.append(", ");
+                }
+            }
+            el.setAttribute("savable_versions", sb.toString());
+            
+            writtenSavables.put(object, el);
+            object.write(exporter);
+        }
+        if(className != null){
+            el.setAttribute("class", className);
+        }
+
+        currentElement = old;
+    }
+
+    public void write(Savable[] objects, String name, Savable[] defVal) throws IOException {
+        if (objects == null) {
+            return;
+        }
+        if (Arrays.equals(objects, defVal)) {
+            return;
+        }
+
+        Element old = currentElement;
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(objects.length));
+        for (int i = 0; i < objects.length; i++) {
+            Savable o = objects[i];
+            if(o == null){
+                //renderStateList has special loading code, so we can leave out the null values
+                if(!name.equals("renderStateList")){
+                Element before = currentElement;
+                appendElement("null");
+                currentElement = before;
+                }
+            }else{
+                write(o, o.getClass().getName(), null);
+            }
+        }
+        currentElement = old;
+    }
+
+    public void write(Savable[][] value, String name, Savable[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size_outer", String.valueOf(value.length));
+        el.setAttribute("size_inner", String.valueOf(value[0].length));
+        for (Savable[] bs : value) {
+            for(Savable b : bs){
+                write(b, b.getClass().getSimpleName(), null);
+            }
+        }
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
+        if (array == null) {
+            return;
+        }
+        if (array.equals(defVal)) {
+            return;
+        }
+        Element old = currentElement;
+        Element el = appendElement(name);
+        currentElement = el;
+        el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
+        for (Object o : array) {
+                if(o == null) {
+                        continue;
+                }
+                else if (o instanceof Savable) {
+                Savable s = (Savable) o;
+                write(s, s.getClass().getName(), null);
+            } else {
+                throw new ClassCastException("Not a Savable instance: " + o);
+            }
+        }
+        currentElement = old;
+    }
+
+    public void writeSavableArrayListArray(ArrayList[] objects, String name, ArrayList[] defVal) throws IOException {
+        if (objects == null) {return;}
+        if (Arrays.equals(objects, defVal)) {return;}
+
+        Element old = currentElement;
+        Element el = appendElement(name);
+        el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length));
+        for (int i = 0; i < objects.length; i++) {
+            ArrayList o = objects[i];
+            if(o == null){
+                Element before = currentElement;
+                appendElement("null");
+                currentElement = before;
+            }else{
+                StringBuilder buf = new StringBuilder("SavableArrayList_");
+                buf.append(i);
+                writeSavableArrayList(o, buf.toString(), null);
+            }
+        }
+        currentElement = old;
+    }
+
+    public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException {
+        if (value == null) return;
+        if(Arrays.deepEquals(value, defVal)) return;
+
+        Element el = appendElement(name);
+        int size = value.length;
+        el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size));
+
+        for (int i=0; i< size; i++) {
+            ArrayList[] vi = value[i];
+            writeSavableArrayListArray(vi, "SavableArrayListArray_"+i, null);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array, String name, ArrayList<FloatBuffer> defVal) throws IOException {
+        if (array == null) {
+            return;
+        }
+        if (array.equals(defVal)) {
+            return;
+        }
+        Element el = appendElement(name);
+        el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
+        for (FloatBuffer o : array) {
+            write(o, XMLExporter.ELEMENT_FLOATBUFFER, null);
+        }
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+        if (map == null) {
+            return;
+        }
+        if (map.equals(defVal)) {
+            return;
+        }
+                Element stringMap = appendElement(name);
+
+                Iterator<? extends Savable> keyIterator = map.keySet().iterator();
+                while(keyIterator.hasNext()) {
+                        Savable key = keyIterator.next();
+                        Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY);
+                        write(key, XMLExporter.ELEMENT_KEY, null);
+                        Savable value = map.get(key);
+                        write(value, XMLExporter.ELEMENT_VALUE, null);
+                        currentElement = stringMap;
+                }
+
+                currentElement = (Element) stringMap.getParentNode();
+    }
+
+    public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException {
+        if (map == null) {
+            return;
+        }
+        if (map.equals(defVal)) {
+            return;
+        }
+                Element stringMap = appendElement(name);
+
+                Iterator<String> keyIterator = map.keySet().iterator();
+                while(keyIterator.hasNext()) {
+                        String key = keyIterator.next();
+                        Element mapEntry = appendElement("MapEntry");
+                        mapEntry.setAttribute("key", key);
+                        Savable s = map.get(key);
+                        write(s, "Savable", null);
+                        currentElement = stringMap;
+                }
+
+                currentElement = (Element) stringMap.getParentNode();
+    }
+
+    public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException {
+        if (map == null) {
+            return;
+        }
+        if (map.equals(defVal)) {
+            return;
+        }
+                Element stringMap = appendElement(name);
+
+                for(Entry<? extends Savable> entry : map) {
+                        int key = entry.getKey();
+                        Element mapEntry = appendElement("MapEntry");
+                        mapEntry.setAttribute("key", Integer.toString(key));
+                        Savable s = entry.getValue();
+                        write(s, "Savable", null);
+                        currentElement = stringMap;
+                }
+
+                currentElement = (Element) stringMap.getParentNode();
+    }
+
+    public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException {
+        if (value == null) {
+            return;
+        }
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.limit()));
+        StringBuilder buf = new StringBuilder();
+        int pos = value.position();
+        value.rewind();
+        int ctr = 0;
+        while (value.hasRemaining()) {
+            ctr++;
+            buf.append(value.get());
+            buf.append(" ");
+        }
+        if (ctr != value.limit())
+            throw new IOException("'" + name
+                + "' buffer contention resulted in write data consistency.  "
+                + ctr + " values written when should have written "
+                + value.limit());
+        buf.setLength(Math.max(0, buf.length() - 1));
+        value.position(pos);
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException {
+        if (value == null) {
+            return;
+        }
+        if (value.equals(defVal)) {
+            return;
+        }
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.limit()));
+        StringBuilder buf = new StringBuilder();
+        int pos = value.position();
+        value.rewind();
+        int ctr = 0;
+        while (value.hasRemaining()) {
+            ctr++;
+            buf.append(value.get());
+            buf.append(" ");
+        }
+        if (ctr != value.limit())
+            throw new IOException("'" + name
+                + "' buffer contention resulted in write data consistency.  "
+                + ctr + " values written when should have written "
+                + value.limit());
+        buf.setLength(buf.length() - 1);
+        value.position(pos);
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException {
+        if (value == null) return;
+        if (value.equals(defVal)) return;
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.limit()));
+        StringBuilder buf = new StringBuilder();
+        int pos = value.position();
+        value.rewind();
+        int ctr = 0;
+        while (value.hasRemaining()) {
+            ctr++;
+            buf.append(value.get());
+            buf.append(" ");
+        }
+        if (ctr != value.limit())
+            throw new IOException("'" + name
+                + "' buffer contention resulted in write data consistency.  "
+                + ctr + " values written when should have written "
+                + value.limit());
+        buf.setLength(buf.length() - 1);
+        value.position(pos);
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) el.getParentNode();
+    }
+
+    public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException {
+        if (value == null) {
+            return;
+        }
+        if (value.equals(defVal)) {
+            return;
+        }
+
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(value.limit()));
+        StringBuilder buf = new StringBuilder();
+        int pos = value.position();
+        value.rewind();
+        int ctr = 0;
+        while (value.hasRemaining()) {
+            ctr++;
+            buf.append(value.get());
+            buf.append(" ");
+        }
+        if (ctr != value.limit())
+            throw new IOException("'" + name
+                + "' buffer contention resulted in write data consistency.  "
+                + ctr + " values written when should have written "
+                + value.limit());
+        buf.setLength(buf.length() - 1);
+        value.position(pos);
+        el.setAttribute(dataAttributeName, buf.toString());
+        currentElement = (Element) el.getParentNode();
+    }
+
+        public void write(Enum value, String name, Enum defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+        currentElement.setAttribute(name, String.valueOf(value));
+
+        }
+
+        public void writeByteBufferArrayList(ArrayList<ByteBuffer> array,
+                        String name, ArrayList<ByteBuffer> defVal) throws IOException {
+        if (array == null) {
+            return;
+        }
+        if (array.equals(defVal)) {
+            return;
+        }
+        Element el = appendElement(name);
+        el.setAttribute("size", String.valueOf(array.size()));
+        for (ByteBuffer o : array) {
+            write(o, "ByteBuffer", null);
+        }
+        currentElement = (Element) el.getParentNode();
+
+        }
+}
\ No newline at end of file
diff --git a/engine/src/xml/com/jme3/export/xml/DOMSerializer.java b/engine/src/xml/com/jme3/export/xml/DOMSerializer.java
new file mode 100644
index 0000000..b30ba19
--- /dev/null
+++ b/engine/src/xml/com/jme3/export/xml/DOMSerializer.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2009-2010 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.export.xml;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import org.w3c.dom.*;
+
+/**
+ * The DOMSerializer was based primarily off the DOMSerializer.java class from the 
+ * "Java and XML" 3rd Edition book by Brett McLaughlin, and Justin Edelson. Some 
+ * modifications were made to support formatting of elements and attributes.
+ * 
+ * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book.
+ * @author Doug Daniels (dougnukem) - adjustments for XML formatting
+ * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $
+ */
+public class DOMSerializer {
+
+    /** The encoding to use for output (default is UTF-8) */
+    private Charset encoding = Charset.forName("utf-8");
+
+    /** The amount of indentation to use (default is 4 spaces). */
+    private int indent = 4;
+
+    /** The line separator to use (default is the based on the current system settings). */
+    private String lineSeparator = System.getProperty("line.separator", "\n");
+
+    private void escape(Writer writer, String s) throws IOException {
+        if (s == null) { return; }
+        for (int i = 0, len = s.length(); i < len; i++) {
+            char c = s.charAt(i);
+            switch (c) {
+            case '<':
+                writer.write("&lt;");
+                break;
+            case '>':
+                writer.write("&gt;");
+                break;
+            case '&':
+                writer.write("&amp;");
+                break;
+            case '\r':
+                writer.write("&#xD;");
+                break;
+            default:
+                writer.write(c);
+            }
+        }
+    }
+
+    /**
+     * Serialize {@code doc} to {@code out}
+     * 
+     * @param doc the document to serialize.
+     * @param file the file to serialize to.
+     * @throws IOException
+     */
+    public void serialize(Document doc, File file) throws IOException {
+        serialize(doc, new FileOutputStream(file));
+    }
+
+    /**
+     * Serialize {@code doc} to {@code out}
+     * 
+     * @param doc the document to serialize.
+     * @param out the stream to serialize to.
+     * @throws IOException
+     */
+    public void serialize(Document doc, OutputStream out) throws IOException {
+        Writer writer = new OutputStreamWriter(out, encoding);
+        write(doc, writer, 0);
+        writer.flush();
+    }
+
+    /**
+     * Set the encoding used by this serializer.
+     * 
+     * @param encoding the encoding to use, passing in {@code null} results in the
+     *  default encoding (UTF-8) being set.
+     * @throws IllegalCharsetNameException if the given charset name is illegal.
+     * @throws UnsupportedCharsetException if the given charset is not supported by the
+     *  current JVM.
+     */
+    public void setEncoding(String encoding) {
+        this.encoding = Charset.forName(encoding);
+    }
+
+    /**
+     * Set the number of spaces to use for indentation.
+     * <p>
+     * The default is to use 4 spaces.
+     * 
+     * @param indent the number of spaces to use for indentation, values less than or
+     *  equal to zero result in no indentation being used.
+     */
+    public void setIndent(int indent) {
+        this.indent = indent >= 0 ? indent : 0;
+    }
+
+    /**
+     * Set the line separator that will be used when serializing documents.
+     * <p>
+     * If this is not called then the serializer uses a default based on the
+     * {@code line.separator} system property. 
+     * 
+     * @param lineSeparator the line separator to set.
+     */
+    public void setLineSeparator(String lineSeparator) {
+        this.lineSeparator = lineSeparator;
+    }
+
+    private void write(Node node, Writer writer, int depth) throws IOException {
+        switch (node.getNodeType()) {
+        case Node.DOCUMENT_NODE:
+            writeDocument((Document) node, writer);
+            break;
+        case Node.ELEMENT_NODE:
+            writeElement((Element) node, writer, depth);
+            break;
+        case Node.TEXT_NODE:
+            escape(writer, node.getNodeValue());
+            break;
+        case Node.CDATA_SECTION_NODE:
+            writer.write("<![CDATA[");
+            escape(writer, node.getNodeValue());
+            writer.write("]]>");
+            break;
+        case Node.COMMENT_NODE:
+            for (int i = 0; i < depth; ++i) { writer.append(' '); }
+            writer.append("<!-- ").append(node.getNodeValue()).append(" -->").append(lineSeparator);
+            break;
+        case Node.PROCESSING_INSTRUCTION_NODE:
+            String n = node.getNodeName();
+            String v = node.getNodeValue();
+            for (int i = 0; i < depth; ++i) { writer.append(' '); }
+            writer.append("<?").append(n).append(' ').append(v).append("?>").append(lineSeparator);
+            break;
+        case Node.ENTITY_REFERENCE_NODE:
+            writer.append('&').append(node.getNodeName()).append(';');
+            break;
+        case Node.DOCUMENT_TYPE_NODE:
+            writeDocumentType((DocumentType) node, writer, depth);
+            break;
+        }
+    }
+
+    private void writeDocument(Document document, Writer writer) throws IOException {
+        String v = document.getXmlVersion();
+
+        writer.append("<?xml ");
+        writer.append(" version='").append(v == null ? "1.0" : v).append("'");
+        writer.append(" encoding='").append(encoding.name()).append("'");
+        if (document.getXmlStandalone()) {
+            writer.append(" standalone='yes'");
+        }
+        writer.append("?>").append(lineSeparator);
+
+        NodeList nodes = document.getChildNodes();
+        for (int i = 0, imax = nodes.getLength(); i < imax; ++i) {
+            write(nodes.item(i), writer, 0);
+        }
+    }
+
+    private void writeDocumentType(DocumentType docType, Writer writer, int depth) throws IOException {
+        String publicId = docType.getPublicId();
+        String internalSubset = docType.getInternalSubset();
+
+        for (int i = 0; i < depth; ++i) { writer.append(' '); }
+        writer.append("<!DOCTYPE ").append(docType.getName());
+        if (publicId != null) {
+            writer.append(" PUBLIC '").append(publicId).append("' ");
+        } else {
+            writer.write(" SYSTEM ");
+        }
+        writer.append("'").append(docType.getSystemId()).append("'");
+        if (internalSubset != null) {
+            writer.append(" [").append(internalSubset).append("]");
+        }
+        writer.append('>').append(lineSeparator);
+    }
+
+    private void writeElement(Element element, Writer writer, int depth) throws IOException {
+        for (int i = 0; i < depth; ++i) { writer.append(' '); }
+        writer.append('<').append(element.getTagName());
+        NamedNodeMap attrs = element.getAttributes();
+        for (int i = 0, imax = attrs.getLength(); i < imax; ++i) {
+            Attr attr = (Attr) attrs.item(i);
+            writer.append(' ').append(attr.getName()).append("='").append(attr.getValue()).append("'");
+        }
+        NodeList nodes = element.getChildNodes();
+        if (nodes.getLength() == 0) {
+            // no children, so just close off the element and return
+            writer.append("/>").append(lineSeparator);
+            return;
+        }
+        writer.append('>').append(lineSeparator);
+        for (int i = 0, imax = nodes.getLength(); i < imax; ++i) {
+            Node n = nodes.item(i);
+            if (n.getNodeType() == Node.ATTRIBUTE_NODE) { continue; }
+            write(n, writer, depth + indent);
+        }
+        for (int i = 0; i < depth; ++i) { writer.append(' '); }
+        writer.append("</").append(element.getTagName()).append('>').append(lineSeparator);
+    }
+
+}
diff --git a/engine/src/xml/com/jme3/export/xml/XMLExporter.java b/engine/src/xml/com/jme3/export/xml/XMLExporter.java
new file mode 100644
index 0000000..6e7c395
--- /dev/null
+++ b/engine/src/xml/com/jme3/export/xml/XMLExporter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 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.export.xml;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Part of the jME XML IO system as introduced in the google code jmexml project.
+ * 
+ * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project
+ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5
+ */
+public class XMLExporter implements JmeExporter {
+    
+    public static final String ELEMENT_MAPENTRY = "MapEntry";	
+    public static final String ELEMENT_KEY = "Key";	
+    public static final String ELEMENT_VALUE = "Value";
+    public static final String ELEMENT_FLOATBUFFER = "FloatBuffer";
+    public static final String ATTRIBUTE_SIZE = "size";		
+
+    private DOMOutputCapsule domOut;
+    
+    public XMLExporter() {
+       
+    }
+
+    public boolean save(Savable object, OutputStream f) throws IOException {
+        try {
+            //Initialize Document when saving so we don't retain state of previous exports
+            this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this);
+            domOut.write(object, object.getClass().getName(), null);
+            DOMSerializer serializer = new DOMSerializer();
+            serializer.serialize(domOut.getDoc(), f);
+            f.flush();
+            return true;
+        } catch (Exception ex) {
+            IOException e = new IOException();
+            e.initCause(ex);
+            throw e;
+        }
+    }
+
+    public boolean save(Savable object, File f) throws IOException {
+        return save(object, new FileOutputStream(f));
+    }
+
+    public OutputCapsule getCapsule(Savable object) {
+        return domOut;
+    }
+
+    public static XMLExporter getInstance() {
+            return new XMLExporter();
+    }
+    
+}
diff --git a/engine/src/xml/com/jme3/export/xml/XMLImporter.java b/engine/src/xml/com/jme3/export/xml/XMLImporter.java
new file mode 100644
index 0000000..7a46b17
--- /dev/null
+++ b/engine/src/xml/com/jme3/export/xml/XMLImporter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2009-2010 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.export.xml;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.SAXException;
+
+/**
+ * Part of the jME XML IO system as introduced in the google code jmexml project.
+ * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project
+ * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5
+ */
+public class XMLImporter implements JmeImporter {
+
+    private AssetManager assetManager;
+    private DOMInputCapsule domIn;
+    int formatVersion = 0;
+    
+    public XMLImporter() {
+    }
+
+    public int getFormatVersion() {
+        return formatVersion;
+    }
+    
+    public AssetManager getAssetManager(){
+        return assetManager;
+    }
+
+    public void setAssetManager(AssetManager assetManager){
+        this.assetManager = assetManager;
+    }
+
+    public Object load(AssetInfo info) throws IOException{
+        assetManager = info.getManager();
+        InputStream in = info.openStream();
+        Savable obj = load(in);
+        in.close();
+        return obj;
+    }
+    
+    public Savable load(File f) throws IOException {
+        FileInputStream fis = null; 
+        try {
+            fis = new FileInputStream(f);
+            Savable sav = load(fis);
+            return sav;
+        } finally {
+            if (fis != null) fis.close();
+        }
+    }
+
+    public Savable load(InputStream f) throws IOException {
+        try {
+            domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f), this);
+            return domIn.readSavable(null, null);
+        } catch (SAXException e) {
+            IOException ex = new IOException();
+            ex.initCause(e);
+            throw ex;
+        } catch (ParserConfigurationException e) {
+            IOException ex = new IOException();
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    public InputCapsule getCapsule(Savable id) {
+        return domIn;
+    }
+
+    public static XMLImporter getInstance() {
+        return new XMLImporter();
+    }
+
+}